[STK5-04] 계좌 목록·선택 API + 매수가능·매도가능·수수료 조회 API

작업 내용 (설계 의도)

변경 사항

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

구성 범위:

  • application UseCase: ListAccountsUseCase, SelectAccountUseCase, GetPurchasableAmountUseCase, GetSellableQuantityUseCase, GetTradingFeeUseCase
  • presentation: AccountApiController
  • application dto: TradableAmountResponse, TradingFeeResponse

API 엔드포인트:

  • GET /api/v1/accounts — 계좌 목록 조회 (Toss fetch → MySQL upsert → 응답)
  • PUT /api/v1/accounts/{accountNumber}/select — 활성 계좌 선택
  • GET /api/v1/accounts/purchasable?symbol=&quantity=&price= — 매수 가능 금액
  • GET /api/v1/accounts/sellable?symbol=&quantity= — 매도 가능 수량
  • GET /api/v1/accounts/fee?symbol=&quantity=&price=&orderType= — 수수료 조회

가능금액·수수료 3개 API는 MySQL 저장 없이 Toss API를 즉시 프록시한다.

다이어그램

처리 흐름 (계좌 목록 조회)

sequenceDiagram
    participant C as AccountApiController
    participant U as ListAccountsUseCase
    participant DS as AccountDomainService
    participant G as AccountGateway
    participant R as AccountRepository
    C->>U: execute()
    U->>DS: listAccounts()
    DS->>G: fetchAccounts()
    G-->>DS: List<AccountData>
    DS->>R: upsertAll(accounts)
    R-->>DS: List<Account>
    DS-->>U: List<Account>
    U-->>C: List<AccountResponse>

처리 흐름 (활성 계좌 선택)

sequenceDiagram
    participant C as AccountApiController
    participant U as SelectAccountUseCase
    participant DS as AccountDomainService
    participant R as AccountRepository
    C->>U: execute(accountNumber)
    U->>DS: selectAccount(accountNumber)
    DS->>R: findSelected()
    R-->>DS: previousAccount or null
    alt 기존 선택 계좌 존재
        DS->>R: save(previousAccount.deselect())
    end
    DS->>R: findBy(accountNumber)
    DS->>R: save(account.select())
    R-->>DS: Account
    DS-->>U: Account
    U-->>C: 200 OK

클래스 의존

flowchart LR
    subgraph Presentation["presentation"]
        AC[AccountApiController]
    end
    subgraph Application["application"]
        LAU[ListAccountsUseCase]
        SAU[SelectAccountUseCase]
        PAU[GetPurchasableAmountUseCase]
        SEU[GetSellableQuantityUseCase]
        FEU[GetTradingFeeUseCase]
    end
    subgraph Domain["domain"]
        ADS[AccountDomainService]
        TQS[TradableQueryService]
    end
    AC --> LAU
    AC --> SAU
    AC --> PAU
    AC --> SEU
    AC --> FEU
    LAU --> ADS
    SAU --> ADS
    PAU --> TQS
    SEU --> TQS
    FEU --> TQS

테스트 케이스

  • PUT /api/v1/accounts/{accountNumber}/select 호출 후 GET /api/v1/accounts를 조회하면 해당 계좌만 selected=true이고 나머지는 false이다.
  • 존재하지 않는 계좌번호로 PUT /api/v1/accounts/{accountNumber}/select를 호출하면 404를 반환한다.
  • GET /api/v1/accounts/purchasable?symbol=005930&quantity=10&price=75000은 Toss API 결과를 그대로 응답하고 MySQL에 저장하지 않는다.
  • Toss API가 오류를 반환할 때 GET /api/v1/accounts/purchasable은 500을 반환한다.
  • GET /api/v1/accounts/fee?symbol=005930&quantity=10&price=75000&orderType=BUY는 수수료 금액과 수수료율을 포함한 응답을 반환한다.