Files
llm-wiki/raw/blog-topics/archunit-violations-as-data-pattern-2026-05-28.md
T

112 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<JavaClass>` 작성. `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` 후보.