[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% 근거)