ADR-005 FR-7 정적 검증 = ArchUnit fitness function
- 상태: 채택
- 날짜: 2026-07-03
- 근거: PRD FR-7, FR-8, NFR(스캔 2분 이내) / TDD “FR-7 정적 검증 방법”
맥락
도메인 간 참조 규칙(domain 레이어 교차 import 금지, common 예외) 위반을 정적으로 탐지할 방법이 없다. 현재 위반 0건(베이스라인)이며 이 베이스라인 유지가 목표다. 스캔은 domain 레이어 Entity/VO의 타 도메인 import로 한정(application 조합은 정상).
결정
ArchUnit 규칙 테스트(Kotest)로 실행 가능하게 구현한다. 규칙 4종:
| 규칙 | 내용 |
|---|---|
| R1 | domain.X는 domain.Y(Y≠X, Y≠common)를 import하지 않는다 (FR-7 핵심, 베이스라인 0건) |
| R2 | domain.**는 infrastructure·application·presentation를 import하지 않는다 (레이어 방향) |
| R3 | 지원/서브시스템(operator/mcp/weather)은 코어를 동기 의존하지 않는다 |
| R4 | common은 어떤 도메인도 import하지 않는다 (공유 커널 순수성) |
R3 화이트리스트 (명시 예외 — 규칙에서 제외):
| 예외 | 방향 | 성격 | 근거 |
|---|---|---|---|
| dashboard | application.dashboard → 코어(booking/facility/goods/ticketing/user) | 읽기 조합 | Conformist read model (ADR-002 규칙 4) |
| partner (② B2B) | application.partner → domain.user | admin 프로비저닝 쓰기 오케스트레이션 | CreatePartnerUseCase가 연동 전용 User 계정 생성 — ADR-002 rule #2 범위. 이 예외가 없으면 ② 구현 시 R3가 false RED |
partner 예외는 사전 등록한다 — 현 시점
application.partner패키지는 미존재(② 과제에서 신설)이나, ArchUnit 규칙(resideInAnyPackage(...).ignoreDependency)에 partner→user 예외를 미리 배선해 ② 착수 시 규칙 수정 없이 통과하게 한다.application.partner → 코어(user 제외)접근은 여전히 R3 위반.
- 구현:
ClassFileImporter로com.sportsapp임포트 → 스펙 간 캐싱 공유 → Kotest FunSpec에서 assert. - 별도 Gradle 태스크
archTest로 분리. 초기 관찰 모드(check 미포함) → 베이스라인 0건 확인 후 게이트 승격.
근거
- 벤치마크 정합: Shopify Packwerk·Spring Modulith fitness function이 “물리 분리 없이 경계만 정적 강제”하는 접근. 본 과제 Non-Goals와 일치.
- 회귀 방지: 문서·리뷰만으로는 0건 회귀를 못 막음. 테스트가 위반을 빌드에서 잡는다.
- NFR 충족: ClassFileImporter 결과 캐싱으로 전체 스캔 2분 이내.
- FR-8 갱신 절차 연동: 패키지 prefix 스캔이라 신규 도메인 자동 포함. 예외(dashboard 읽기·partner→user 쓰기 화이트리스트)만 수동 갱신.
대안 (미채택)
- grep/셸 스크립트: import 문자열 매칭은 오탐·미탐(별칭·와일드카드 import) 위험. 유지보수 취약.
- Konsist: ArchUnit과 등가지만 팀 표준(테스트=Kotest)과 통합·성숙도에서 ArchUnit 우선. 필요 시 대체 가능.
- CI 게이트 즉시 강제: 예상 못한 위반이 CI를 막을 위험 → 관찰 모드 1단계 후 승격(Release Scenario).
영향
- Open Question(CI 게이트 vs 체크리스트)을 “게이트 강제(관찰 후 승격)“로 확정.
- BE-01(공통 fixture)·BE-02~05(규칙별 스펙)·BE-07(Gradle 태스크 와이어업) 티켓으로 구현.