[BE-09] 데모 게이팅 기능 (featuredemo 슬라이스)

작업 내용 (설계 의도)

변경 사항

FR-9 완료 조건 — 신규 데모 엔드포인트 GET /feature-demo/helloRELEASE 플래그 demo.feature.hello로 게이팅한다. 근거 TDD: ../TDD.md “데모 게이팅 대상 결정”·API 계약. **별도 소비 도메인 featuredemo**로 두어 common.FeatureFlagEvaluator만 주입 — domain.featureflag를 import하지 않아 도메인 격리·FR-12 보일러플레이트 없음을 스스로 증명한다.

  • FeatureDemoDomainService (domain): greet(userId)evaluator.isEnabled("demo.feature.hello", FeatureContext.of(userId), default=false)가 false면 FeatureDisabledException(503) throw, true면 Greeting 반환.
  • FeatureDisabledException (domain, BusinessException 확장, ErrorStatus.SERVICE_UNAVAILABLE) — BE-01에서 추가한 503 매핑으로 GlobalExceptionHandler가 자동 503.
  • GetDemoGreetingUseCase (application): DomainService 위임.
  • FeatureDemoApiController (presentation, ~ApiController.kt): GET /feature-demo/hello, X-User-Id 헤더(optional)로 컨텍스트, 200 {message, flagKey, userId, servedAt}.
  • 게이팅 대상 자체는 부작용 0(고정 인사 응답) — 킬스위치·퍼센티지 sticky 증명에 집중.
  • 롤백: 플래그 archive/OFF 한 번으로 즉시 503(재배포 없음).

의존

  • BE-06 (FeatureFlagEvaluator 구현 빈)
  • BE-01 (common.FeatureFlagEvaluator·FeatureContext·ErrorStatus.SERVICE_UNAVAILABLE)

다이어그램

처리 흐름

sequenceDiagram
    participant C as FeatureDemoApiController
    participant U as GetDemoGreetingUseCase
    participant D as FeatureDemoDomainService
    participant E as FeatureFlagEvaluator
    C->>U: execute(userId)
    U->>D: greet(userId)
    D->>E: isEnabled("demo.feature.hello", ctx, false)
    alt ON
        E-->>D: true → Greeting
        D-->>C: 200
    else OFF/미존재
        E-->>D: false → throw
        D-->>C: 503
    end

테스트 케이스

  • 플래그 미존재 상태에서 GET /feature-demo/hello는 503을 반환한다(기본값 다크)
  • GlobalToggle ON 플래그에서 200과 인사 메시지를 반환한다
  • 플래그를 archive하면 즉시 503을 반환한다(킬스위치)
  • PercentageRollout 50%에서 동일 X-User-Id를 반복 호출하면 200/503 판정이 일관된다(sticky)
  • X-User-Id 없이 PercentageRollout 플래그 호출 시 default(false)로 503을 반환한다
  • FeatureDemoDomainService는 domain.featureflag를 import하지 않는다(도메인 격리 — 정적 검증)