[STK9-01] Backend: signal_snapshots 테이블 + CRUD API

작업 내용 (설계 의도)

ML 신호 결과를 영속화할 signal_snapshots, recommendation_snapshots 테이블을 신설한다. Aggregator가 ML 결과를 이 API에 저장하고, 이후 조회 시 ML 없이 DB에서 반환할 수 있게 한다.

스냅샷은 symbol당 최신 1건만 유지한다 (upsert). 이력 보관은 이번 범위에 없다.

DB 마이그레이션: V202606201800__create_signal_snapshots.sql

다이어그램

처리 흐름

sequenceDiagram
    participant AGG as Aggregator
    participant C as SignalSnapshotApiController
    participant UC as SaveSignalSnapshotUseCase
    participant DS as SignalSnapshotDomainService
    participant R as SignalSnapshotRepository
    AGG->>C: PUT /signal-snapshots/{symbol}
    C->>UC: execute(command)
    UC->>DS: upsert(command)
    DS->>R: findBy(symbol)
    DS->>R: save(snapshot)
    DS-->>UC: SignalSnapshot
    UC-->>C: SignalSnapshotResponse
    C-->>AGG: 200 OK

클래스 의존

flowchart LR
    SignalSnapshotApiController --> GetSignalSnapshotUseCase
    SignalSnapshotApiController --> SaveSignalSnapshotUseCase
    SignalSnapshotApiController --> GetRecommendationSnapshotUseCase
    SignalSnapshotApiController --> SaveRecommendationSnapshotUseCase
    GetSignalSnapshotUseCase --> SignalSnapshotDomainService
    SaveSignalSnapshotUseCase --> SignalSnapshotDomainService
    GetRecommendationSnapshotUseCase --> SignalSnapshotDomainService
    SaveRecommendationSnapshotUseCase --> SignalSnapshotDomainService
    SignalSnapshotDomainService --> SignalSnapshotRepository

테스트 케이스

  • 스냅샷이 없는 symbol 조회 시 404가 반환된다
  • 스냅샷 저장 후 동일 symbol 재조회 시 저장된 결과가 반환된다
  • 동일 symbol 두 번 저장 시 최신 값으로 갱신된다 (upsert)
  • 추천 스냅샷은 key=‘default’ 단건으로 유지되며 재저장 시 갱신된다
  • refreshed_at이 저장 시각으로 기록된다
DDL 참고
CREATE TABLE signal_snapshots (
    id              BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '기본키',
    symbol          VARCHAR(20) NOT NULL UNIQUE COMMENT '종목 코드',
    signal_json     TEXT NOT NULL COMMENT 'SignalResult JSON',
    prediction_json TEXT NULL COMMENT 'PredictionResult JSON',
    refreshed_at    DATETIME(6) NOT NULL COMMENT '마지막 ML 호출 시각',
    created_at      DATETIME(6) NOT NULL COMMENT '생성일시',
    updated_at      DATETIME(6) NOT NULL COMMENT '수정일시'
) COMMENT = 'ML 신호 스냅샷';
 
CREATE TABLE recommendation_snapshots (
    id                   BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '기본키',
    snapshot_key         VARCHAR(50) NOT NULL UNIQUE COMMENT '고정값 default',
    recommendations_json TEXT NOT NULL COMMENT 'RecommendationResult JSON',
    refreshed_at         DATETIME(6) NOT NULL COMMENT '마지막 ML 호출 시각',
    created_at           DATETIME(6) NOT NULL COMMENT '생성일시',
    updated_at           DATETIME(6) NOT NULL COMMENT '수정일시'
) COMMENT = 'ML 추천 스냅샷';