Aggregator Server TDD

Background

PRD 참조. FE가 BE·ML을 직접 호출하는 구조를 Aggregator 단일 진입점으로 전환한다.

Overview

  • aggregator/ 신규 Kotlin + Spring Boot 서비스 (포트 8090)
  • ML 시그널·예측 응답을 Redis에 캐시, ML 장애 시 Redis에서 반환
  • 나머지 /api/v1/** 요청은 BE(8080)로 reverse proxy
  • FE는 8090만 바라본다

Terminology

용어정의
AggregatorFE의 단일 진입점 서버. ML 캐시 + BE 프록시 역할
Reverse ProxyAggregator가 요청을 BE로 투명하게 전달하는 패턴
Cache-first FallbackML 성공 → Redis 저장, ML 실패 → Redis 반환
Redis TTLML 응답 캐시 만료 시간. 기본 10분 (600s)

Define Problem

AS-IS

FE ──► BE(8080)  : 도메인 API 직접 호출
FE ──► ML(8000)  : 시그널·예측 직접 호출 (장애 대응 없음)

TO-BE

FE ──► Aggregator(8090) ──► BE(8080)   : /api/v1/** reverse proxy
                        ──► ML(8000)   : /signals, /predictions (Redis fallback)
                        ──► Redis      : ML 응답 캐시

Possible Solutions

방안설명채택 여부
Aggregator 별도 서비스 (선택)새 Spring Boot 서비스 8090채택 — 관심사 분리, BE 무변경
BE에 통합기존 BE가 ML 프록시 겸임미채택 — BE 변경 필요, 단일 장애점
API Gateway (Kong/Spring Cloud GW)오픈소스 게이트웨이미채택 — 운영 복잡성, 현재 규모 과잉
Redis 캐시ML 응답 JSON을 symbol 키로 저장채택 — 서버 재시작 후에도 유지
In-memory 캐시Caffeine미채택 — 재시작 시 유실

Detail Design

Component Diagram

flowchart LR
    FE["FE (3000)"]

    subgraph Aggregator["Aggregator (8090)"]
        SAC["SignalAggregatorController"]
        PC["ProxyController"]
        GSU["GetSignalUseCase"]
        GPU["GetPredictionUseCase"]
        MlGW["MlGateway"]
        SCR["SignalCacheRepository"]
        MlGWImpl["MlGatewayImpl"]
        RedisCR["RedisCacheRepositoryImpl"]
        BeRC["BeRestClient"]
    end

    BE["BE (8080)"]
    ML["ML (8000)"]
    Redis[("Redis")]

    FE --> SAC
    FE --> PC
    SAC --> GSU
    SAC --> GPU
    GSU --> MlGW
    GSU --> SCR
    GPU --> MlGW
    GPU --> SCR
    MlGWImpl -.->|implements| MlGW
    RedisCR -.->|implements| SCR
    MlGWImpl --> ML
    RedisCR --> Redis
    PC --> BeRC
    BeRC --> BE

Sequence Diagram — 정상 흐름

sequenceDiagram
    participant FE
    participant AGG as SignalAggregatorController
    participant UC as GetSignalUseCase
    participant GW as MlGatewayImpl
    participant CR as RedisCacheRepositoryImpl
    participant ML
    participant Redis

    FE->>AGG: GET /api/v1/signals/005930
    AGG->>UC: execute("005930")
    UC->>GW: fetchSignal("005930")
    GW->>ML: GET /signals/005930
    ML-->>GW: 200 JSON
    GW-->>UC: SignalResult
    UC->>CR: save("signal:005930", result, TTL=600s)
    CR->>Redis: SET signal:005930 ... EX 600
    UC-->>AGG: SignalResult
    AGG-->>FE: 200 SignalResponse

Sequence Diagram — ML 장애 fallback

sequenceDiagram
    participant FE
    participant AGG as SignalAggregatorController
    participant UC as GetSignalUseCase
    participant GW as MlGatewayImpl
    participant CR as RedisCacheRepositoryImpl
    participant ML
    participant Redis

    FE->>AGG: GET /api/v1/signals/005930
    AGG->>UC: execute("005930")
    UC->>GW: fetchSignal("005930")
    GW->>ML: GET /signals/005930
    ML-->>GW: 연결 실패 / 5xx
    GW-->>UC: MlGatewayException
    UC->>CR: find("signal:005930")
    CR->>Redis: GET signal:005930
    Redis-->>CR: cached JSON
    CR-->>UC: SignalResult (cached)
    UC-->>AGG: SignalResult
    AGG-->>FE: 200 (X-Cache: HIT)

Sequence Diagram — Reverse Proxy

sequenceDiagram
    participant FE
    participant PC as ProxyController
    participant BeRC as BeRestClient
    participant BE

    FE->>PC: GET /api/v1/watchlist
    PC->>BeRC: forward(GET, /api/v1/watchlist, headers, body)
    BeRC->>BE: GET /api/v1/watchlist
    BE-->>BeRC: 200 JSON
    BeRC-->>PC: ResponseEntity
    PC-->>FE: 200 (pass-through)

ERD

해당 없음 — Aggregator는 DB를 보유하지 않는다. ML 캐시는 Redis에만 저장.

Redis Key 설계

키 패턴TTL
signal:{SYMBOL}ML /signals/{symbol} 응답 JSON600s
prediction:{SYMBOL}ML /predictions/{symbol} 응답 JSON600s

Testing Plan

  • GetSignalUseCase: ML 성공 시 Redis에 저장 후 결과 반환, ML 실패 시 Redis 캐시 반환, ML 실패 + 캐시 없음 시 SignalUnavailableException 발생 (GetPredictionUseCase도 동일 3케이스)
  • MlGatewayImpl: 200 응답 역직렬화 정합성 확인, 4xx/5xx 응답 시 MlGatewayException 발생
  • RedisCacheRepositoryImpl: save → find 정합성 검증 (Testcontainers Redis), TTL 만료 후 find → null 반환
  • SignalAggregatorController: 200 + 응답 구조 검증, fallback 시 X-Cache: HIT 헤더 포함, 캐시 없음 시 503 반환
  • ProxyController: BE 응답 상태코드·본문 pass-through 검증

Release Scenario

  1. docker-compose.yml에 Redis 추가 후 docker compose up -d redis
  2. Aggregator 빌드·실행 (포트 8090)
  3. FE NEXT_PUBLIC_API_BASE_URLhttp://localhost:8090 변경 후 재시작
  4. 롤백: FE 환경변수를 기존 8080으로 되돌리고 재시작 (Aggregator·BE 변경 없음)

Project Information

  • 신규 서비스: aggregator/ (monorepo 내 신규 디렉토리)
  • 포트: 8090
  • 티켓 prefix: STK7