--- title: blog-topic / archunit-violations-as-data-pattern-2026-05-28 source_type: blog-topic status: raw related_branches: [feature-architecture-enforcement-rules, feature-application-port-usecase-contract] related_projects: [ca-tmpl] tags: [blog-topic, ca-tmpl, archunit, testing, fitness-function, spring-modulith, negative-test] created: 2026-05-28 status_label: ready-for-canonical target_audience: backend-engineer inspiration_url: archive_url: --- # blog-topic: archunit-violations-as-data-pattern-2026-05-28 > Layer: `raw/blog-topics/` — 채용공고가 아닌 작업·학습·트러블슈팅에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보이며, `wiki/blog/` 직접 생성 근거가 아니다. ## Parent / 부모 - [[raw/branch-notes/feature-architecture-enforcement-rules]] — `violations-as-data` pattern 을 Claims to Verify 의 `planned` 에서 `actually-implemented` 로 승급시킨 round 2 작업. - [[raw/branch-notes/feature-application-port-usecase-contract]] — D14 (KEYED idempotency freeze) 의 ArchUnit custom condition 도 같은 fixture 로 보증. ## 트리거 / Trigger - 트리거 유형: `branch-work` - 트리거 날짜: 2026-05-28 - 트리거 연결 노트: [[raw/branch-notes/feature-architecture-enforcement-rules]] — Claims to Verify 의 마지막 행 ("ArchUnit rule 이 위반을 실제로 catch 한다는 commit 된 증명") 을 Spring Modulith `example/ninvalid` 패턴으로 구현한 작업. ## 글감 / Topic seed - 한 문장 요지: ArchUnit rule 은 _없는 위반_ 에 대해 vacuously pass 한다 — production 코드에 위반이 우연히 없을 때도, 분석 scope 자체가 비어있을 때도 동일하게 SUCCESS. **위반 fixture + negative test** 로 _rule 이 실제로 catch 하는지_ 를 commit 으로 박아두지 않으면 silent regression 이 누적된다. - 떠오른 계기: round 1 작업에서 발견한 [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] (ArchUnit scope 가 production classpath 만 보고 vacuous pass 한 사례) + Spring Modulith 의 `example/ninvalid` 패턴 발견. - 예상 제목 후보: - ArchUnit rule 을 _믿을 수 있게_ 만드는 violations-as-data 패턴 - Spring Modulith 의 `example/ninvalid` 를 ca-skeleton 에 차용한 6개 negative test - "rule 이 작동하는지" 를 commit 으로 박아두기 — fitness function 의 self-verification ## 핵심 주장 후보 / Claim candidates - 사실 후보: - ArchUnit 의 vacuous pass 함정은 두 갈래로 발생 — (a) production code 에 위반이 우연히 없음, (b) `@AnalyzeClasses` 의 import scope 가 비어 있음 (classpath 누락) — 근거: [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] §직접 원인. - Spring Modulith 공식 incubator 가 _자기 rule 들을 검증_ 하기 위해 `example/ninvalid` fixture package 와 `modules.detectViolations().getMessages()` assertion 을 사용 — 근거: `raw/company-tech-blogs/spring-modulith-archunit-generated-exemption-and-violations-as-data.md#SPRING-MOD-AU-C2` (`SPRING-MOD-AU-C2`). - ca-tmpl 의 적용: `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/` 에 6 fixture (domain 1 + application 5) + `ArchitectureViolationFixtureTest` 에 6 negative test — 근거: `feature-architecture-enforcement-rules.md` 구현 결과 round 2 + Claims to Verify 마지막 행 → `actually-implemented`. - fixture 는 `src/test/...` 위치이므로 main `@AnalyzeClasses(importOptions = DoNotIncludeTests.class)` 에 _자동으로_ 제외됨 — main suite 가 fixture 때문에 실패하지 않음 — 근거: `feature-architecture-enforcement-rules.md` 진행 중 메모 round 2. - negative test 는 `new ClassFileImporter().importPackages("...violations")` 로 fixture _만_ 로드한 뒤 rule 의 `EvaluationResult.hasViolation() == true` 를 단순 assert — 근거: `src/app-bootstrap/src/test/.../ArchitectureViolationFixtureTest.java`. - 경험 후보: - KEYED idempotency rule (D14) 은 ArchUnit DSL 로 표현 불가능 → custom `ArchCondition` 작성. `JavaAnnotation.get("idempotency")` 가 `JavaEnumConstant` 를 반환하므로 bytecode 만으로 enum value 검사 가능 (reflection 없음) — 근거: `CleanArchitectureTest#notDeclareKeyedIdempotency` 메서드 + branch-note D14. - `TransactionalAnnotatedFixture` 가 `@Transactional` 을 import 하려면 `app-bootstrap/build.gradle` 의 `testCompileOnly 'org.springframework:spring-tx'` 가 필요. production scope 에 영향 없음 (test 만) — 근거: `feature-architecture-enforcement-rules.md` round 2 메모. - fixture 클래스를 `package-private` 으로 유지해 _외부 사용 불가_ 명시 + ArchUnit fixture import 만 동작 — 의도 노이즈 차단. - 의견 / 해석 후보: - ArchUnit rule 은 _코드_ 다. 코드는 테스트 없이 믿으면 안 된다 — fitness function 도 동일. - vacuous pass 는 _"rule 이 안 잡혔다"_ 가 아니라 _"rule 이 무엇을 잡는지 아무도 검증 안 했다"_ 의 신호. 본 패턴은 후자를 commit 으로 박는 게 목적. - **간단한 rule (`noClasses().that(pkg).should().dependOn(pkg2)`) 은 vacuous pass 위험이 _제일 큼_** — 술어가 단순할수록 production 매칭이 우연히 0개가 되기 쉽다. _복잡한 custom condition (D14) 은 명시적으로 짠 거니까 더 안전_ 이라는 직관과 반대. - Spring Modulith 가 _자기 자신_ 을 검증하는 데 쓰는 패턴이라는 점이 글의 강한 thesis — "rule 의 production-readiness 의 골든 스탠다드". ## Outline seed 1. 동기 — ArchUnit 의 vacuous pass 함정 두 갈래 → **rule 이 잡는다고 _믿는_ 것과 _증명_ 하는 것의 차이.** 2. Spring Modulith 의 self-verification 패턴 (`example/ninvalid` + `detectViolations().getMessages()`) → **OSS 공식 incubator 가 자기 rule 을 같은 방식으로 검증한다는 신뢰 신호.** 3. ca-tmpl 의 차용 — `violations/` package + `ArchitectureViolationFixtureTest` → **6 fixture × 6 negative test 의 1:1 매칭.** 4. fixture 의 위치 결정 — `src/test/...` 안에 두면 main `DoNotIncludeTests` 가 자동 제외 → **main suite 와 negative test 가 _서로를 깨뜨리지 않는_ 격리.** 5. custom ArchCondition (D14) — `JavaAnnotation.get(...)` 로 enum value 검사 → **reflection 없이 bytecode 만으로 annotation parameter catch 가능.** 6. fixture 의 deps — `testCompileOnly 'org.springframework:spring-tx'` 의 비대칭 의존 → **`@Transactional` 을 _import_ 만 하고 production scope 에는 안 들어감.** 7. 한계 — string-key bean lookup / `Class.forName(String)` 의 bypass 는 여전히 catch 불가 (D12) → **fitness function 의 정직한 한계.** 8. 정리 — rule 은 코드다. 코드는 negative test 없이 믿지 말자. ## Canonical 전환 후보 / Canonical extraction candidates - `wiki/projects/ca-tmpl/archunit-violations-as-data.md` 후보: - 실제 6 fixture + 6 negative test 의 코드 발췌. - `testCompileOnly 'org.springframework:spring-tx'` 비대칭 의존 패턴. - `JavaAnnotation.get("idempotency")` custom condition 코드. - `wiki/concepts/archunit-violations-as-data.md` 후보: - "vacuous pass 함정 두 갈래" 의 project-agnostic 정리. - Spring Modulith `example/ninvalid` 패턴의 일반화. - test-scope fixture + main DoNotIncludeTests 의 격리 패턴. - 필요한 추가 검증: - 본 패턴이 _큰 codebase_ (rule 수십 개) 에 적용했을 때 negative test 가 production rule 의 작은 변경에 같이 깨지는지 (regression sensitivity 측정). - `feature-archunit-negative-fixture-baseline` 같은 후속 branch 를 분리할 가치가 있는지. ## Sources / 근거 후보 - [[raw/branch-notes/feature-architecture-enforcement-rules]] — Claims to Verify 마지막 행 `actually-implemented` 승급 + 진행 중 메모 round 2 + Closure 갱신. - [[raw/branch-notes/feature-application-port-usecase-contract]] — D14 의 custom ArchCondition 으로 KEYED enum value catch. - [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] — vacuous pass 의 두 번째 갈래 (classpath scope) 의 실 사례. - [[raw/company-tech-blogs/spring-modulith-archunit-generated-exemption-and-violations-as-data]] — `example/ninvalid` 패턴 (`SPRING-MOD-AU-C2`) + `annotatedWith(Generated.class)` 예시 (`SPRING-MOD-AU-C1`). - [[raw/official-docs/archunit-user-guide]] — `JavaAnnotation` / `JavaEnumConstant` / `EvaluationResult.hasViolation()` 의 공식 API 근거 (`ARCHUNIT-UG-C5`). - [[raw/interviews/archunit-static-analysis-limits]] — 같은 작업에서 파생된 면접 질문. ## 미해결 / Unknown - 아직 확인해야 할 사실: negative test 가 _rule wording 변경_ 에 얼마나 민감하게 깨지는지 — false-positive regression 비용 vs 진짜 regression catch 비용의 균형. - 아직 확인해야 할 사실: fixture 클래스의 _수_ 가 늘어날 때 (예: 50 rule × 50 fixture) 관리 비용. Spring Modulith 의 실 fixture 디렉터리 크기와 비교 필요. - 과장하면 안 되는 부분: 본 패턴은 _ArchUnit static analysis 의 한계 (D12 string bypass)_ 를 보완하지 _않는다_. negative test 도 정적이라 reflection bypass 는 잡지 못함. - 과장하면 안 되는 부분: ca-tmpl 의 6 fixture / 6 test 는 _proof-of-concept_ 규모. 실 사업 도메인의 30+ rule 적용 시 관리 비용 측정 미수행. - 블로그로 쓰기 전에 필요한 canonical 정제: `wiki/projects/ca-tmpl/archunit-violations-as-data.md` + `wiki/concepts/archunit-violations-as-data.md` 정제. 1~2개 추가 branch 적용 사례 누적. ## Decision / 처리 결정 - 액션: `promote-to-canonical` - 이유: `wiki/projects/ca-tmpl/clean-architecture-package-layout.md` 와 `wiki/projects/ca-tmpl/skeleton-governance-registry-verification-test-scorecard.md` 에 violations-as-data / negative fixture 글감으로 반영한다. - 다음 단계: blogify 전 적용 branch 수와 fixture 관리 비용을 확인한다. ## Related / 관련 - 관련 branch: [[raw/branch-notes/feature-architecture-enforcement-rules]] (본 패턴의 직접 적용), [[raw/branch-notes/feature-application-port-usecase-contract]] (D14 custom condition 의 negative test). - 관련 errors: [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] (vacuous pass 의 다른 갈래). - 관련 interview prep: [[raw/interviews/archunit-static-analysis-limits]] (static analysis 한계 + violations-as-data 보완), [[raw/interviews/clean-architecture-boundary-enforcement]] (선행 — boundary 자동 검증의 자매 토픽). - 관련 blog topics: [[raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28]] (boundary 자동 검증의 자매 글감). - derived blog: 생성 전. 생성 시 `wiki/blog/archunit-violations-as-data-pattern-YYYY-MM-DD.md` 후보.