11 KiB
11 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label
| title | source_type | status | related_branches | related_projects | tags | created | status_label | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / archunit-test-scope-sample-ticket-inclusion-2026-05-28 | error-note | raw |
|
|
|
2026-05-28 | resolved |
error: archunit-test-scope-sample-ticket-inclusion-2026-05-28
Layer:
raw/errors/— 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw 에 영구 보관.
Parent / 부모
- raw/branch-notes/feature-application-port-usecase-contract —
application_does_not_use_spring_transactional_annotationArchUnit rule 추가 중 vacuously 통과한 함정 +testImplementation project(':sample-ticket')으로 해결한 경험. - raw/project-notes/ca-skeleton-operational-contract — ca-tmpl skeleton 의 boundary 자동 검증 정책 맥락.
증상 / Symptom
- 에러 메시지 (원문 그대로):
기대값 은
BUILD SUCCESSFUL in 5s 16 actionable tasks: 3 executed, 13 up-to-dateapplication_does_not_use_spring_transactional_annotationrule 의 실패 (당시sample-ticket/.../UserService와PostService가org.springframework.transaction.annotation.Transactional을 import 중). 그러나 BUILD SUCCESSFUL — rule 이 vacuously 통과. ArchUnit 의 "failed to check any classes" 에러조차 뜨지 않음 (rule 이 정상 평가됐다고 인식). - 발생 컨텍스트:
cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'. 새 ArchUnit rule 3종 (application_does_not_use_spring_transactional_annotation,inbound_port_implementations_end_with_use_case,inbound_port_implementations_declare_capability) 추가 직후 첫 실행. - 발생 시점: 2026-05-28.
- 발생 환경: ca-tmpl repository, local Linux.
- 재현 가능 여부:
always—app-bootstrap의 production dependency 매트릭스에sample-ticket이 없는 상태에서 ArchUnit rule 이..application..패키지를 검사하면 재현.
재현 절차 / Reproduction
- ca-tmpl
src/build.gradle의allowedProjectDependencies매트릭스에서app-bootstrap의 allowed 목록에sample-ticket이 없음 을 확인. src/sample-ticket/.../application/UserService.java에import org.springframework.transaction.annotation.Transactional;가 있음 을 확인.src/app-bootstrap/.../CleanArchitectureTest.java에 다음 rule 을 추가:@ArchTest static final ArchRule application_does_not_use_spring_transactional_annotation = noClasses() .that().resideInAPackage("..application..") .should().dependOnClassesThat().haveFullyQualifiedName( "org.springframework.transaction.annotation.Transactional" ) .allowEmptyShould(true);cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'실행.- 기대 결과:
application_does_not_use_spring_transactional_annotationrule 이 실패 (UserService / PostService 가 위반). - 실제 결과: BUILD SUCCESSFUL. rule 이 vacuously 통과.
조사 단계 / Investigation log
- 2026-05-28 — ArchUnit test 실행 → 모든 rule 통과 → 의외.
sample-ticket의@Transactional이 분명히 남아 있는데? - 2026-05-28 —
CleanArchitectureTest.java의@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ImportOption.DoNotIncludeTests.class)확인 → 패키지 필터는dev.caskeleton이지만, 실제 import 대상은app-bootstrap의 test classpath 에 존재 하는 클래스 중 패키지 필터 매치 부분이다. - 2026-05-28 —
src/build.gradle의allowedProjectDependencies매트릭스 확인 →app-bootstrap의 allowed =['domain-core', 'application-core', 'adapter-web', 'adapter-persistence', 'adapter-outbound', 'shared-contract']—sample-ticket은 의도적으로 부재 (production 역수입 금지의 자매 결정). - 2026-05-28 — 결론:
sample-ticket의 main 클래스는app-bootstrap의 test JVM classpath 에 없음. ArchUnit 가 패키지 필터dev.caskeleton으로 import 해도sample-ticket의 클래스를 못 봄. 그래서 rule 이 0 개의 application 클래스 를 평가했고,allowEmptyShould(true)가 true 로 해석. - 2026-05-28 — 해결 후보 검토:
- (a)
app-bootstrap에implementation project(':sample-ticket')추가 → production dependency 매트릭스 위반,verifyCleanArchitectureDependencies실패. 채택 안 함. - (b)
app-bootstrap에testImplementation project(':sample-ticket')추가 → production 매트릭스 영향 없음 (['api', 'implementation', 'compileOnly', 'runtimeOnly']만 검사). ArchUnitproduction_code_does_not_depend_on_sample_ticket도ImportOption.DoNotIncludeTests로 test 클래스 제외 하므로 production drift 로 잘못 보고 안 됨. 채택.
- (a)
- 2026-05-28 —
app-bootstrap/build.gradle에testImplementation project(':sample-ticket')추가 후 재실행 → 이제는sample-ticket클래스가 ArchUnit scope 에 잡혀서application_does_not_use_spring_transactional_annotationrule 이 실패 (예상대로). - 2026-05-28 —
sample-ticket의UserService/PostService의@Transactional을 모두TransactionPort호출로 치환 → 재실행 → BUILD SUCCESSFUL. red/green 검증 완료.
근본 원인 / Root cause
- 직접 원인:
app-bootstrap의 production dependency 매트릭스에sample-ticket이 없어서sample-ticket의 main 클래스가app-bootstrap의 test JVM classpath 에 없었음. ArchUnit 의@AnalyzeClasses(packages = "dev.caskeleton")는 패키지 필터 일 뿐 classpath scan source 가 아님. - 근본 원인: ArchUnit 의 import scope 가 현재 모듈의 컴파일 + 런타임 classpath 에 의존한다는 점을 패키지 필터 만 보면 놓치기 쉬움. 패키지 필터가 "이 패키지를 검사한다" 의 전제 가 아니라 필터 임을 인식 못 함.
- 트리거 조건:
sample-ticket이 production 역수입 금지 정책에 따라app-bootstrap의 production dep 가 아님 (의도된 정책) + ArchUnit rule 이..application..패키지를 검사 (sample-ticket 도 이 패키지에 포함됨) 의 교차 상황.
Sources / 근거
- raw/branch-notes/feature-application-port-usecase-contract — 본 에러를 발견한 작업의 branch-note + Decisions 2026-05-28.
- raw/branch-notes/feature-architecture-enforcement-rules —
production_code_does_not_depend_on_sample_ticketrule 의ImportOption.DoNotIncludeTests사용 (D7). - raw/branch-notes/feature-skeleton-package-blueprint-contract —
sample-ticket의 production 역수입 금지 결정 (D7). - raw/errors/archunit-empty-should-anchor-2026-05-27 — ArchUnit 의 빈 평가 와
allowEmptyShould(true)의 또 다른 함정 사례. - ca-tmpl 코드:
src/build.gradleverifyCleanArchitectureDependenciestask —['api', 'implementation', 'compileOnly', 'runtimeOnly']만 검사.testImplementation은 production 매트릭스에서 제외. - ca-tmpl 코드:
src/app-bootstrap/.../CleanArchitectureTest.java@AnalyzeClasses(importOptions = ImportOption.DoNotIncludeTests.class).
해결 / Resolution
- 적용한 조치:
src/app-bootstrap/build.gradle의dependencies블록에testImplementation project(':sample-ticket')추가. 주석으로 비대칭 의존의 의도를 명시:// sample-ticket is on the test classpath only so the ArchUnit suite can analyse the // template's reference implementation. Production scope MUST NOT depend on // sample-ticket; that rule is enforced by `production_code_does_not_depend_on_sample_ticket`. testImplementation project(':sample-ticket') - 검증 방법:
cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'—application_does_not_use_spring_transactional_annotation가 실패 시키는지 확인 (sample-ticketmigration 전).- sample-ticket migration 후 동일 명령 실행 → BUILD SUCCESSFUL. red/green 양쪽 확인.
cd src && ./gradlew verifyCleanArchitectureDependencies— production 매트릭스 영향 없음 확인.cd src && ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'의production_code_does_not_depend_on_sample_ticketrule — 여전히 통과 (test 클래스 제외 옵션 때문).
- 잔여 위험 / 후속 작업:
- 다른 sample / fixture 모듈이 추가될 경우 동일 패턴 (
testImplementation project(':<fixture>')) 을 명시적으로 적용해야 함. 누락되면 vacuously pass 재발 가능. - ArchUnit rule 을 추가할 때 수반 해야 할 체크리스트 (해당 rule 이 잡으려는 violating example 이 test classpath 에 있는지) 가 명시화되지 않음 — 후속 review checklist 후보.
- 다른 sample / fixture 모듈이 추가될 경우 동일 패턴 (
회고 / Lessons
- 빨리 감지하는 신호:
- ArchUnit rule 을 추가했는데 실패할 거라고 100% 확신 한 케이스가 통과하면 rule 이 잘못된 게 아니라 import scope 가 비어 있을 가능성을 첫 의심.
BUILD SUCCESSFUL+ 새 rule 의failed to check any classes경고조차 없음 → 패키지 필터에 매치되는 클래스가 classpath 에 없는 상태.- 새 rule 을 PR 에 넣기 전 임시 violating code 를 추가해 red 가 되는지 확인 (
feature-architecture-enforcement-rules.md의 red/green 패턴과 동일).
- 예방 체크리스트 항목 후보:
- 새 ArchUnit rule 추가 시 이 rule 이 잡으려는 위반 예시가 ArchUnit 의 import scope (= 현재 모듈의 test JVM classpath) 에 실제로 존재하는가 를 먼저 확인.
- 새 sample / fixture 모듈 추가 시
app-bootstrap/build.gradle의testImplementation에 명시 추가 + 주석으로 비대칭 의존 이유 기록. - ArchUnit
@AnalyzeClasses(packages = ...)가 필터 일 뿐 scan source 가 아니라는 사실을 PR 리뷰 checklist 에 추가.
- wiki 로 끌어올릴 가치가 있는 일반화된 교훈:
- ArchUnit 의 scope = classpath ∩ package filter. 둘 중 하나가 비어 있으면 vacuously pass.
- production dependency 차단 (
production_code_does_not_depend_on_sample_ticket) 과 test-scope inclusion (testImplementation project(':sample-ticket')) 의 비대칭 의존 패턴은 sample / fixture module 이 있는 multi-module repo 에 일반적으로 적용 가능.
Related / 관련
- 트리거된 daily note: raw/daily-notes/2026-05-28.
- 관련 branch note: raw/branch-notes/feature-application-port-usecase-contract, raw/branch-notes/feature-architecture-enforcement-rules (자매 — boundary 자동 검증).
- 관련 errors: raw/errors/archunit-empty-should-anchor-2026-05-27 (ArchUnit empty pass 의 또 다른 변종).
- 관련 wiki 개념: wiki/concepts/clean-architecture-package-layout (아직 갱신 전), 후보 wiki/concepts/archunit-scope-classpath-vs-package-filter (정제 시 신규).
- 관련 blog topics: raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28 (본 에러를 발견한 작업의 글감), raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28 (boundary 자동 검증 글감).