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

11 KiB
Raw Blame History

title, source_type, status, related_branches, related_projects, tags, created, status_label, target_audience, inspiration_url, archive_url
title source_type status related_branches related_projects tags created status_label target_audience inspiration_url archive_url
blog-topic / archunit-violations-as-data-pattern-2026-05-28 blog-topic raw
feature-architecture-enforcement-rules
feature-application-port-usecase-contract
ca-tmpl
blog-topic
ca-tmpl
archunit
testing
fitness-function
spring-modulith
negative-test
2026-05-28 ready-for-canonical backend-engineer

blog-topic: archunit-violations-as-data-pattern-2026-05-28

Layer: raw/blog-topics/ — 채용공고가 아닌 작업·학습·트러블슈팅에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보이며, wiki/blog/ 직접 생성 근거가 아니다.

Parent / 부모

트리거 / 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 testrule 이 실제로 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.gradletestCompileOnly '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 + ArchitectureViolationFixtureTest6 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' 의 비대칭 의존 → @Transactionalimport 만 하고 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 / 근거 후보

미해결 / 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.mdwiki/projects/ca-tmpl/skeleton-governance-registry-verification-test-scorecard.md 에 violations-as-data / negative fixture 글감으로 반영한다.
  • 다음 단계: blogify 전 적용 branch 수와 fixture 관리 비용을 확인한다.