[STK5-03] order/holding 도메인 Entity·DomainService + 토스 주문·보유 Gateway 구현

작업 내용 (설계 의도)

변경 사항

order·holding 도메인 패키지 2개를 신규 구성한다. STK5-02(account 도메인)과 별도 패키지이므로 병렬 진행 가능하다.

구성 범위:

  • order domain: Order Entity, OrderType·OrderPriceType·OrderStatus(canTransitTo 포함) + OrderDomainService + OrderGateway·OrderRepository interface
  • order infrastructure: TossOrderGateway (주문 접수·정정·취소 Toss API 호출) + OrderRepositoryImpl + OrderJpaRepository + OrderEntity
  • holding domain: Holding Entity, TradeHistory Entity, TradeType + HoldingDomainService + HoldingGateway·HoldingRepository·TradeHistoryRepository interface
  • holding infrastructure: TossHoldingGateway (보유종목·거래내역 Toss API 호출) + HoldingRepositoryImpl·TradeHistoryRepositoryImpl + JPA 구현체
  • application: OrderResponse, HoldingResponse, TradeHistoryResponse

TossOrderGatewayTossHoldingGateway 모두 TOSS_USER_TOKEN 헤더를 사용한다.

다이어그램

클래스 의존

flowchart LR
    subgraph OrderDomain["order domain"]
        ODS[OrderDomainService]
        OGW[OrderGateway]
        OR[OrderRepository]
        Order[Order Entity]
        OS[OrderStatus]
    end
    subgraph HoldingDomain["holding domain"]
        HDS[HoldingDomainService]
        HGW[HoldingGateway]
        HR[HoldingRepository]
        THR[TradeHistoryRepository]
        Holding[Holding Entity]
        TH[TradeHistory Entity]
    end
    subgraph OrderInfra["order infrastructure"]
        TOG[TossOrderGateway]
        ORI[OrderRepositoryImpl]
    end
    subgraph HoldingInfra["holding infrastructure"]
        THG[TossHoldingGateway]
        HRI[HoldingRepositoryImpl]
        THRI[TradeHistoryRepositoryImpl]
    end
    ODS --> OGW
    ODS --> OR
    ODS --> Order
    Order --> OS
    HDS --> HGW
    HDS --> HR
    HDS --> THR
    HDS --> Holding
    HDS --> TH
    TOG -.->|implements| OGW
    ORI -.->|implements| OR
    THG -.->|implements| HGW
    HRI -.->|implements| HR
    THRI -.->|implements| THR

처리 흐름 (주문 접수)

sequenceDiagram
    participant DS as OrderDomainService
    participant G as OrderGateway
    participant R as OrderRepository
    DS->>G: placeOrder(accountNumber, symbol, type, priceType, quantity, price)
    alt Toss 성공
        G-->>DS: tossOrderId
        DS->>R: save(Order(tossOrderId, PENDING))
        R-->>DS: Order
    else Toss 실패
        G-->>DS: 예외 (TossApiException)
        DS-->>DS: 예외 전파 (save 미실행)
    end

처리 흐름 (보유종목 fetch·upsert)

sequenceDiagram
    participant DS as HoldingDomainService
    participant G as HoldingGateway
    participant HR as HoldingRepository
    participant THR as TradeHistoryRepository
    DS->>G: fetchHoldings(accountNumber)
    G-->>DS: List<HoldingData>
    DS->>HR: upsertAll(holdings)
    HR-->>DS: List<Holding>
    DS->>G: fetchTradeHistory(accountNumber, from, to)
    G-->>DS: List<TradeHistoryData>
    DS->>THR: upsertAll(tradeHistories)
    THR-->>DS: List<TradeHistory>

테스트 케이스

  • COMPLETED 상태의 Ordercancel()을 호출하면 InvalidOrderStateException이 발생하고 상태가 변경되지 않는다.
  • PENDING 상태의 Ordercancel()을 호출하면 상태가 CANCELLED로 전이된다.
  • PENDING 상태의 Ordercorrect(newQuantity, newPrice)를 호출하면 상태가 CORRECTED로 전이되고 수량·가격이 갱신된다.
  • CANCELLED 상태의 Ordercorrect()를 호출하면 InvalidOrderStateException이 발생한다.
  • OrderStatus.canTransitTo()에서 COMPLETED → PENDING 전이는 false를 반환한다.
  • Toss OrderGateway 실패 시 OrderDomainServiceOrderRepository.save()를 호출하지 않는다.
  • 동일 (account_number, symbol) 보유종목을 2회 upsert하면 행이 1건으로 유지되고 refreshed_at이 최신값으로 갱신된다.
  • 동일 toss_transaction_id 거래내역 upsert 2회 시 중복 저장 없이 멱등하게 처리된다.