[STK5-05] 보유종목 조회 + 거래내역 조회 API

작업 내용 (설계 의도)

변경 사항

holding 도메인을 외부로 노출하는 UseCase·Controller를 구성한다. STK5-02(account 패키지)·STK5-03(holding 패키지) 완료 후 착수한다. STK5-04·STK5-06과 독립적인 파일(별도 컨트롤러 또는 AccountApiController 내 별도 메서드)이므로 병렬 진행 가능하다.

구성 범위:

  • application UseCase: GetHoldingsUseCase, GetTradeHistoryUseCase
  • presentation: AccountApiController에 엔드포인트 2개 추가 (STK5-04와 같은 컨트롤러 파일 — Wave 3에서 STK5-04·STK5-05가 같은 파일을 수정하므로 STK5-04 완료 후 착수하거나 역할 분담 필요)

파일 충돌 주의: AccountApiController.kt는 STK5-04도 수정한다. STK5-04를 먼저 머지하고 STK5-05를 후행 착수하거나, STK5-05 전용 HoldingApiController.kt를 신설한다. 후자를 권장.

구성 범위 (조정):

  • presentation: HoldingApiController 신설 (AccountApiController와 파일 분리)

API 엔드포인트:

  • GET /api/v1/accounts/{accountNumber}/holdings — 보유종목 조회 (Toss fetch → upsert → 응답)
  • GET /api/v1/accounts/{accountNumber}/trade-history?from=&to= — 거래내역 조회 (Toss fetch → upsert → 응답, 기본 최근 30일)

다이어그램

처리 흐름 (보유종목 조회)

sequenceDiagram
    participant C as HoldingApiController
    participant U as GetHoldingsUseCase
    participant D as HoldingDomainService
    participant G as HoldingGateway
    participant R as HoldingRepository
    C->>U: execute(accountNumber)
    U->>D: getHoldings(accountNumber)
    D->>G: fetchHoldings(accountNumber)
    G-->>D: List<HoldingData>
    D->>R: upsertAll(holdings)
    R-->>D: List<Holding>
    D-->>U: List<Holding>
    U-->>C: List<HoldingResponse>
    alt Toss API 오류
        G-->>D: 예외
        D-->>U: 예외 전파
        U-->>C: 500
    end

처리 흐름 (거래내역 조회)

sequenceDiagram
    participant C as HoldingApiController
    participant U as GetTradeHistoryUseCase
    participant D as HoldingDomainService
    participant G as HoldingGateway
    participant R as TradeHistoryRepository
    C->>U: execute(accountNumber, from, to)
    U->>D: getTradeHistory(accountNumber, from, to)
    D->>G: fetchTradeHistory(accountNumber, from, to)
    G-->>D: List<TradeHistoryData>
    D->>R: upsertAll(tradeHistories)
    R-->>D: List<TradeHistory>
    D-->>U: List<TradeHistory>
    U-->>C: List<TradeHistoryResponse>

클래스 의존

flowchart LR
    subgraph Presentation["presentation"]
        HC[HoldingApiController]
    end
    subgraph Application["application"]
        GHU[GetHoldingsUseCase]
        GTU[GetTradeHistoryUseCase]
    end
    subgraph Domain["domain"]
        HDS[HoldingDomainService]
        HGW[HoldingGateway]
        HR[HoldingRepository]
        THR[TradeHistoryRepository]
    end
    subgraph Infra["infrastructure"]
        THG[TossHoldingGateway]
        HRI[HoldingRepositoryImpl]
        THRI[TradeHistoryRepositoryImpl]
    end
    HC --> GHU
    HC --> GTU
    GHU --> HDS
    GTU --> HDS
    HDS --> HGW
    HDS --> HR
    HDS --> THR
    THG -.->|implements| HGW
    HRI -.->|implements| HR
    THRI -.->|implements| THR

테스트 케이스

  • 동일 계좌·종목으로 GET /api/v1/accounts/{accountNumber}/holdings를 2회 조회하면 MySQL에 중복 없이 1건만 존재하고 refreshed_at이 최신값으로 갱신된다.
  • 보유종목이 없는 계좌를 조회하면 빈 배열을 반환하고 200을 응답한다.
  • GET /api/v1/accounts/{accountNumber}/trade-history 호출 시 from·to를 생략하면 기본 최근 30일 범위로 조회된다.
  • 동일 toss_transaction_id를 가진 거래내역이 2회 upsert되더라도 MySQL에 1건만 존재한다.
  • Toss API 호출이 실패하면 MySQL upsert를 실행하지 않고 500을 반환한다.