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} 응답 JSON
600s
prediction:{SYMBOL}
ML /predictions/{symbol} 응답 JSON
600s
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
docker-compose.yml에 Redis 추가 후 docker compose up -d redis
Aggregator 빌드·실행 (포트 8090)
FE NEXT_PUBLIC_API_BASE_URL → http://localhost:8090 변경 후 재시작
롤백: FE 환경변수를 기존 8080으로 되돌리고 재시작 (Aggregator·BE 변경 없음)