STK11-03 자정 스케줄러·수동 갱신 트리거
작업 내용 (설계 의도)
변경 사항
매일 00:00 KST에 관심종목·추천을 ML fresh 계산 후 스냅샷에 적재하는 writer를 신설한다. 자동 스케줄러와 수동 트리거 두 진입점이 동일 RefreshSnapshotsUseCase를 호출한다. (선행: STK11-01 SnapshotGateway, STK11-02 getSignal(refresh))
- domain:
WatchlistGateway인터페이스 +BackendWatchlistGatewayImpl— backendGET /api/v1/watchlist에서 적재 대상 symbol 목록 추출 - application:
RefreshSnapshotsUseCase—WatchlistGateway.findSymbols()순회하며SignalDomainService.getSignal(symbol, refresh=true)로 ML 계산·적재, 이어서getRecommendations(refresh=true)1회. 종목별 실패는 격리(한 종목 실패 시 나머지 계속), 결과 요약 반환 - presentation:
SnapshotRefreshScheduler—@Scheduled(cron="0 0 0 * * *", zone="Asia/Seoul"), 활성/비활성 플래그(aggregator.snapshot.refresh.enabled) - presentation:
SnapshotRefreshApiController—POST /api/v1/snapshots/refresh수동 트리거 (데이터 누락·즉시 갱신용, 멱등 upsert라 중복 안전)
모니터링 (TDD Observability)
- 적재 시작/종료/부분실패 종목 로그, ML 호출 시점(claude 비용 추적)
- 메트릭:
aggregator.snapshot.refresh.duration,aggregator.snapshot.refresh.failure{symbol} - 전체 실패 시 Discord 알림
롤백
aggregator.snapshot.refresh.enabled=false로 즉시 비활성화, 또는 aggregator 이전 버전 재배포.
다이어그램
처리 흐름
sequenceDiagram participant Entry as Scheduler / ManualTrigger participant UC as RefreshSnapshotsUseCase participant WL as WatchlistGateway participant DS as SignalDomainService Entry->>UC: execute() UC->>WL: findSymbols() WL-->>UC: symbols loop 종목별 (실패 격리) UC->>DS: getSignal(symbol, refresh=true) end UC->>DS: getRecommendations(limit, refresh=true) UC-->>Entry: RefreshSummary
클래스 의존
flowchart LR Sched[SnapshotRefreshScheduler] --> UC[RefreshSnapshotsUseCase] Ctrl[SnapshotRefreshApiController] --> UC UC --> WL[WatchlistGateway] UC --> DS[SignalDomainService]
테스트 케이스
- 스케줄러가 cron 발화 시
RefreshSnapshotsUseCase를 1회 호출한다 - 수동 트리거
POST /api/v1/snapshots/refresh가 동일 UseCase를 호출한다 - 관심종목 전체에
getSignal(refresh=true)가 호출되고 추천이 1회 적재된다 - 일부 종목 ML 실패 시 나머지 종목은 적재된다 (부분 실패 격리)
enabled=false면 스케줄러가 UseCase를 호출하지 않는다- 동일 시점 재실행(수동) 시 멱등하게 처리된다 (upsert)
- 전체 실패 시 Discord 알림이 호출된다
- 적재 소요·실패 메트릭이 계측된다