[STK5-06] 주문 접수 API (매수·매도)

작업 내용 (설계 의도)

변경 사항

주문 접수 흐름을 구현한다. STK5-03(order 도메인·Gateway) 완료 후 착수한다. STK5-07(정정·취소)의 선행 티켓이다.

CUD 처리 원칙 (ADR-502): Toss API 호출 성공 → MySQL INSERT. Toss 실패 시 예외 전파, MySQL 미저장.

구성 범위:

  • application UseCase: PlaceOrderUseCase
  • application command: PlaceOrderCommand
  • presentation: OrderApiController 신설, PlaceOrderRequest
  • application response: PlaceOrderResponse

API 엔드포인트:

  • POST /api/v1/orders

요청 예시:

{
  "accountNumber": "1234567890",
  "symbol": "005930",
  "orderType": "BUY",
  "priceType": "LIMIT",
  "quantity": 10,
  "price": 75000
}

응답: 201 Created + { "id": 1, "tossOrderId": "...", "status": "PENDING", "orderedAt": "..." }

다이어그램

처리 흐름

sequenceDiagram
    participant C as OrderApiController
    participant U as PlaceOrderUseCase
    participant D as OrderDomainService
    participant G as OrderGateway
    participant R as OrderRepository
    C->>U: execute(PlaceOrderCommand)
    U->>D: placeOrder(command)
    D->>G: placeOrder(accountNumber, symbol, type, priceType, quantity, price)
    alt Toss 성공
        G-->>D: tossOrderId
        D->>R: save(Order PENDING)
        R-->>D: Order
        D-->>U: Order
        U-->>C: 201 PlaceOrderResponse
    else Toss 실패
        G-->>D: TossApiException
        D-->>U: 예외 전파
        U-->>C: 400/500
    end

클래스 의존

flowchart LR
    subgraph Presentation["presentation"]
        OC[OrderApiController]
        PR[PlaceOrderRequest]
    end
    subgraph Application["application"]
        POU[PlaceOrderUseCase]
        POC[PlaceOrderCommand]
        POR[PlaceOrderResponse]
    end
    subgraph Domain["domain"]
        ODS[OrderDomainService]
        OGW[OrderGateway]
        OR[OrderRepository]
        O[Order Entity]
    end
    subgraph Infra["infrastructure"]
        TOG[TossOrderGateway]
        ORI[OrderRepositoryImpl]
    end
    OC --> POU
    PR -->|toCommand| POC
    POU --> ODS
    ODS --> OGW
    ODS --> OR
    ODS --> O
    TOG -.->|implements| OGW
    ORI -.->|implements| OR

테스트 케이스

  • Toss OrderGateway가 성공 응답을 반환할 때 POST /api/v1/orders는 201을 반환하고 MySQL에 PENDING 상태 주문이 1건 저장된다.
  • Toss OrderGateway가 예외를 던질 때 MySQL에 주문이 저장되지 않고 500을 반환한다.
  • priceType=MARKET이고 price=0인 시장가 주문이 정상 접수된다.
  • 필수 필드(symbol)가 누락된 요청은 400을 반환하고 Toss API를 호출하지 않는다.
  • 동일 요청으로 2번 접수 시 toss_order_id가 다르면 MySQL에 2건이 저장된다 (멱등 아님, Toss 기준).