[STK8-01] Backend: ManualHolding 도메인 + DB 스키마 + CRUD API

작업 내용 (설계 의도)

변경 사항

portfolio 패키지를 새로 만들고 수동 포트폴리오(ManualHolding) 도메인을 구현한다. 사용자가 “삼성전자, 50,000원, 100주”처럼 수동으로 입력한 내용을 DB에 저장하고, 현재가(TossAPI)와 함께 손익을 계산하는 것이 이 티켓의 핵심이다.

기존 holding 도메인(토스 실계좌 보유 스냅샷)과는 완전히 분리된 신규 도메인이다.

DB 마이그레이션: V202606201700__create_manual_holdings.sql

다이어그램

처리 흐름

sequenceDiagram
    participant C as ManualHoldingApiController
    participant UC as AddManualHoldingUseCase
    participant DS as ManualHoldingDomainService
    participant R as ManualHoldingRepository
    C->>UC: execute(command)
    UC->>DS: addHolding(command)
    DS->>R: findBy(symbol) — 중복 체크
    DS->>R: save(entity)
    DS-->>UC: ManualHolding
    UC-->>C: ManualHoldingResponse

클래스 의존

flowchart LR
    Controller --> AddManualHoldingUseCase
    Controller --> UpdateManualHoldingUseCase
    Controller --> DeleteManualHoldingUseCase
    Controller --> ListManualHoldingsUseCase
    AddManualHoldingUseCase --> ManualHoldingDomainService
    UpdateManualHoldingUseCase --> ManualHoldingDomainService
    DeleteManualHoldingUseCase --> ManualHoldingDomainService
    ListManualHoldingsUseCase --> ManualHoldingDomainService
    ManualHoldingDomainService --> ManualHoldingRepository
    ManualHoldingDomainService --> TossStockGateway

테스트 케이스

  • 정상 종목을 추가하면 ManualHolding이 DB에 저장된다
  • 동일 symbol을 중복 추가하면 예외가 발생한다
  • 매수가·수량 수정 시 기존 항목이 갱신된다
  • 존재하지 않는 symbol 삭제 시 예외가 발생한다
  • 목록 조회 시 현재가가 포함된 손익이 계산되어 반환된다
  • 현재가 > 매수가이면 손익이 양수로 반환된다
  • 현재가 < 매수가이면 손익이 음수로 반환된다
  • 매수가 0 이하 입력 시 예외가 발생한다
  • 수량 0 이하 입력 시 예외가 발생한다
DDL 참고
CREATE TABLE manual_holdings (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '기본키',
    symbol      VARCHAR(20) NOT NULL UNIQUE COMMENT '종목 코드',
    name        VARCHAR(100) NOT NULL COMMENT '종목명',
    avg_price   DECIMAL(20, 4) NOT NULL COMMENT '평균 매수가',
    quantity    INT NOT NULL COMMENT '보유 수량',
    created_at  DATETIME(6) NOT NULL COMMENT '생성일시',
    updated_at  DATETIME(6) NOT NULL COMMENT '수정일시'
) COMMENT = '수동 입력 포트폴리오';