[BE-04] FeatureFlagDomainService (CRUD·감사·전파 오케스트레이션)

작업 내용 (설계 의도)

변경 사항

플래그 생성/수정/아카이브/재활성 도메인 오케스트레이션 + 감사 로그 기록 + 변경 이벤트 발행 + 전파(propagate). 근거 TDD: ../TDD.md “서비스 클래스”·상태 전이 표·Sequence(변경 전파). UseCase는 이 서비스만 주입(Repository/Gateway 직접 주입 금지).

  • create(command): existsByKey 중복 검증(DuplicateKey 400) → FeatureFlag.create → save → 감사(CREATED, before=null, after=snapshot) → 변경 이벤트 register/pull/publishAll.
  • update(command): findByKey(없으면 404) → flag.updateStrategy(ARCHIVED면 409) → save → 감사(UPDATED, before→after).
  • archive(command)/activate(command): 상태 전이(canTransitTo 위반 409) → save → 감사(ARCHIVED/ACTIVATED).
  • getByKey/findAll: 조회 위임(404 처리).
  • getAuditLogs(command): 감사 페이징 조회.
  • propagate(key): findByKey → cacheStore.put(snapshot) + broadcaster.broadcast(key) (AFTER_COMMIT 트리거용, BE-07이 호출).
  • 감사 actor는 command에 담긴 actorUserId(presentation에서 SecurityContext principal.id 주입).
  • 롤백: 상태 전이·감사는 UseCase @Transactional 경계 내 원자. 전파(propagate)는 커밋 후 별개 실행이라 실패해도 SSOT(MySQL) 무영향, @Scheduled 리프레시가 수렴.

의존

  • BE-01 (Repository·Gateway·이벤트·엔티티 인터페이스)

다이어그램

클래스 의존

flowchart LR
    DomainService["FeatureFlagDomainService"] --> Repo["FeatureFlagRepository"]
    DomainService --> AuditRepo["FeatureFlagAuditLogRepository"]
    DomainService --> Cache["FeatureFlagCacheStore"]
    DomainService --> Broadcaster["FeatureFlagChangeBroadcaster"]
    DomainService --> Publisher["common.DomainEventPublisher"]
    DomainService --> Flag["FeatureFlag"]

테스트 케이스 (MockK)

  • create 성공 시 save·감사(CREATED)·FeatureFlagChangedEvent 발행이 모두 일어난다
  • 중복 key로 create 시 DuplicateFeatureFlagKeyException(400)을 던지고 save하지 않는다
  • update 시 감사 로그의 before는 수정 전, after는 수정 후 스냅샷이다
  • 존재하지 않는 key의 update/archive는 FeatureFlagNotFoundException(404)을 던진다
  • 이미 ARCHIVED인 플래그의 archive는 FeatureFlagStatusConflictException(409)을 던진다
  • ARCHIVED 플래그의 update는 409를 던진다(수정 불가)
  • propagate(key)는 cacheStore.put과 broadcaster.broadcast를 각각 1회 호출한다
  • 관리 변경 1회당 감사 로그가 정확히 1건 적재된다(커버리지 100% 근거)