목표가 알림 TDD (Technical Design Document)

Background

PRD 참조. 토스 시세를 주기 폴링해 목표가 도달을 감지하고 멀티 채널로 1회 발송한다. Kotlin/Spring Boot, Hexagonal Architecture + Rich Domain Model.

Overview

  • alert / watchlist / stock / common 4개 도메인 패키지.
  • 폴링 스케줄러(presentation) → UseCase → DomainService → 토스 Gateway·Notification Gateway·Repository.
  • 알림 발송은 Composite Gateway로 Discord·Mac을 동시 호출.

Terminology

PRD의 Terminology를 따른다. 추가:

용어정의
CompositeNotificationGateway등록된 알림 채널을 모두 호출하는 @Primary 게이트웨이
TossPriceGateway토스 OAuth·시세 호출을 담당하는 domain Gateway 구현

Define Problem

AS-IS

  • 신규 프로젝트. 알림 감시·발송 인프라 없음.

TO-BE

  • Hexagonal 레이어로 분리: presentation(Controller·Scheduler) → application(UseCase) → domain(Service·Entity·Gateway interface) ← infrastructure(구현).

Possible Solutions

방안 비교

방안설명채택미채택 사유
주기 폴링 + 스케줄러@Scheduled로 N초마다 ACTIVE 알림 일괄 평가
WebSocket 실시간 구독시세 push 구독토스 API가 WebSocket·push 미제공
채널별 개별 호출UseCase가 Discord·Mac을 각각 호출채널 추가 시 변경 전파. Composite로 캡슐화

Detail Design

Component Diagram

flowchart LR
    subgraph Presentation["Presentation"]
        Controller[AlertApiController]
        Scheduler[PriceAlertScheduler]
    end
    subgraph Application["Application"]
        UseCase[Create/Get/Delete UseCase]
    end
    subgraph Domain["Domain"]
        Service[PriceAlertDomainService]
        Entity[PriceAlert]
        PGW[PriceGateway]
        NGW[NotificationGateway]
        Repo[AlertRepository]
    end
    subgraph Infra["Infrastructure"]
        Toss[TossPriceGateway]
        Composite[CompositeNotificationGateway]
        RepoImpl[AlertRepositoryImpl]
    end
    Controller --> UseCase
    Scheduler --> UseCase
    UseCase --> Service
    Service --> Entity
    Service --> PGW
    Service --> NGW
    Service --> Repo
    Toss -.->|implements| PGW
    Composite -.->|implements| NGW
    RepoImpl -.->|implements| Repo

Sequence Diagram — 폴링 평가

sequenceDiagram
    participant S as PriceAlertScheduler
    participant U as EvaluateAlertsUseCase
    participant D as PriceAlertDomainService
    participant T as PriceGateway
    participant N as NotificationGateway
    participant R as AlertRepository
    S->>U: poll() (30초 주기)
    U->>D: evaluateActiveAlerts()
    D->>R: findActive()
    D->>T: getPrices(symbols)
    T-->>D: prices
    loop 도달한 알림
        D->>N: notify(message)
        D->>R: save(TRIGGERED) + history
    end

ERD

erDiagram
    PRICE_ALERT {
        bigint id PK
        varchar symbol
        decimal target_price
        varchar direction
        varchar status
        datetime created_at
        datetime triggered_at
    }
    ALERT_HISTORY {
        bigint id PK
        bigint alert_id
        varchar symbol
        varchar direction
        decimal target_price
        decimal triggered_price
        datetime triggered_at
    }
    WATCHLIST {
        bigint id PK
        varchar symbol UK
        datetime added_at
    }
    STOCK {
        varchar symbol PK
        varchar name
        varchar market
    }
  • 인덱스: price_alert.idx_status, alert_history.idx_triggered_at·idx_alert_id, watchlist.uk_symbol, stock.idx_name.
  • FK 컬럼은 두지 않고 애플리케이션 레벨에서 관리 (alert_history.alert_id는 단순 참조 컬럼).

상태 전이

ACTIVE ──목표 도달──▶ TRIGGERED (터미널)
ACTIVE ──사용자──▶ DISABLED
DISABLED ──재활성화──▶ ACTIVE

도달 판정: ABOVE → currentPrice >= targetPrice, BELOW → currentPrice <= targetPrice. 판정·전이는 PriceAlert Entity 내부에 캡슐화.

Testing Plan

  • PriceAlert Entity: ABOVE/BELOW 도달 판정, ACTIVE→TRIGGERED 전이 규칙, 중복 trigger 방지 검증
  • PriceAlertDomainService: ACTIVE 알림 일괄 평가, 도달 시 발송 1회·이력 생성, 미도달 시 발송 없음
  • CreatePriceAlertUseCase: 생성 커맨드 → DomainService 위임 흐름 단위 검증
  • AlertRepositoryImpl + TossPriceGateway: TestContainers(MySQL) DB 영속화 검증, 토큰 갱신·401 재시도 검증
  • AlertApiController: MockMvc로 POST/GET/DELETE 엔드포인트 통합 검증
  • 프레임워크: Kotest(BehaviorSpec) + MockK + TestContainers

Release Scenario

  1. docker compose up -d (MySQL 3308).
  2. Flyway 마이그레이션 자동 적용 (price_alert, alert_history, watchlist, stock).
  3. 환경변수(TOSS_API_KEY, TOSS_SECRET_KEY, 선택 Discord) 주입 후 ./gradlew bootRun.
  4. 롤백: 스케줄러 비활성(alert.polling 미설정/프로퍼티 OFF), 마이그레이션 역방향 DDL로 테이블 제거.

Observability

  • 폴링 1회당 평가 알림 수·발송 수 로그.
  • 토스 API 호출 실패·토큰 갱신 로그.

Project Information

  • 담당: biuea
  • 스택: Kotlin / Spring Boot / Hexagonal Architecture / MySQL 8.0 / Flyway
  • DB 포트: 3308 (Docker)
  • 알림 채널: Discord Webhook + Mac osascript

관련 문서