목표가 알림·시그널·추천을 구현했으나 종목 데이터를 MySQL에 영속화하지 않았다. 종목 리스트 기능은 앱 전체의 종목 데이터 소스를 단일화하고, 리스트·검색·현재가 표시 기반을 마련한다.
Overview
항목
내용
핵심 목표
종목 마스터 MySQL 영속화 + 가격 캐시 + 리스트·검색 API + FE 리스트 화면
현재가 전략
SSE + BE 30초 스케줄 캐시 (ADR-1)
검색 전략
DB 우선 + 토스 API fallback (ADR-3)
수정 레이어
BE(backend/), FE(frontend/)
Terminology
용어
정의
Stock Master
종목 기본 정보 (name·code·market·currency 등)
Price Cache
현재가·등락률을 30초 단위로 캐싱한 데이터
Seed Symbols
앱 기동 시 자동으로 로드하는 주요 종목 코드 목록
Watchlist
사용자 관심종목 목록 (기존에서 구현됨)
Custom Field
토스 API 제공 외 자체 정의 필드 (sector, description 등)
Define Problem
AS-IS
FE 컴포넌트 → (각자) 토스 API 직접 호출
- 종목 정보 DB 없음
- 검색 UI 없음
- 현재가 매번 API 호출
- 관심종목 추가 경로 단일(시그널 탭)
TO-BE
FE 리스트/검색 → BE /api/v1/stocks (DB 캐시)
FE 현재가 → SSE EventSource (BE push, 30초 주기)
BE 스케줄러 → 토스 API 30초 배치 → 캐시 갱신 → SSE emitter 전송
BE 초기화 → 토스 API → stocks 테이블 시드
sequenceDiagram
participant FE as FE (EventSource)
participant Ctrl as StockApiController
participant Registry as SseEmitterRegistry
participant Sched as StockPriceScheduler
participant Toss as TossStockGateway
participant Cache as StockPriceCacheRepository
FE->>Ctrl: GET /api/v1/stocks/prices/stream
Ctrl->>Registry: register(emitter)
Ctrl-->>FE: SseEmitter 연결 수립
loop 30초마다
Sched->>Toss: fetchPrices(allSymbols)
Toss-->>Sched: List<TossPriceDto>
Sched->>Cache: upsertAll(prices)
Sched->>Registry: broadcast(prices)
Registry-->>FE: SSE event {prices:[...]}
FE->>FE: setPrices(event.data)
end
FE->>FE: 페이지 unmount
FE->>Ctrl: 연결 종료
Ctrl->>Registry: remove(emitter)
Sequence Diagram — 초기 로딩 (REST 스냅샷)
sequenceDiagram
participant FE as FE
participant Ctrl as StockApiController
participant UC as GetStockPricesUseCase
participant Cache as StockPriceCacheRepository
note over FE: 페이지 마운트 시 즉시 스냅샷 조회 (SSE 첫 이벤트 전 공백 방지)
FE->>Ctrl: GET /api/v1/stocks/prices?symbols=A,B,C
Ctrl->>UC: execute(symbols)
UC->>Cache: findBySymbols(symbols)
Cache-->>UC: List<StockPriceCache>
UC-->>Ctrl: List<StockPriceResponse>
Ctrl-->>FE: 200 [{symbol, lastPrice, changeRate, updatedAt}]
note over FE: 이후 SSE 이벤트로 자동 갱신
Sequence Diagram — 종목 검색 (DB 우선 + fallback)
sequenceDiagram
participant FE as FE
participant Ctrl as StockApiController
participant UC as SearchStocksUseCase
participant DS as StockDomainService
participant DB as StockRepository
participant Toss as TossStockGateway
FE->>Ctrl: GET /api/v1/stocks/search?q=삼성
Ctrl->>UC: execute("삼성")
UC->>DS: searchStocks("삼성")
DS->>DB: findByNameOrSymbolContaining("삼성")
alt DB 결과 있음
DB-->>DS: List<Stock>
DS-->>UC: results
else DB 결과 없음
DS->>Toss: searchStocks("삼성")
Toss-->>DS: List<TossStock>
DS->>DB: upsertAll(stocks)
DS-->>UC: results
end
UC-->>Ctrl: List<StockResponse>
Ctrl-->>FE: 200 stocks
ERD
erDiagram
stocks {
bigint id PK
varchar symbol UK "종목코드 (005930)"
varchar name "종목명 (삼성전자)"
varchar english_name "영문명"
varchar market "KOSPI/KOSDAQ/NYSE/NASDAQ"
varchar currency "KRW/USD"
varchar sector "업종 (nullable)"
text description "회사 설명 (nullable)"
tinyint is_active "상장 여부"
json metadata "확장 필드"
datetime created_at
datetime updated_at
}
stock_price_cache {
bigint id PK
varchar symbol UK
varchar last_price "현재가 (문자열, 토스 원본)"
varchar change_rate "등락률"
varchar change_amount "등락폭"
datetime updated_at "마지막 갱신"
}
watchlist {
bigint id PK
varchar symbol UK
datetime created_at
}
stocks ||--o| stock_price_cache : "symbol 1:1"
stocks ||--o| watchlist : "symbol 1:0..1"