14 KiB
14 KiB
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 / clean-architecture-boundary-enforcement-2026-05-28 | blog-topic | raw |
|
|
|
2026-05-28 | ready-for-canonical | backend-engineer |
blog-topic: clean-architecture-boundary-enforcement-2026-05-28
Layer:
raw/blog-topics/— 채용공고가 아닌 작업·학습·트러블슈팅에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보이며,wiki/blog/직접 생성 근거가 아니다.
Parent / 부모
- raw/branch-notes/feature-architecture-enforcement-rules — Clean Architecture 경계를 Gradle / ArchUnit fitness function 으로 실 강제한 구현·검증 경험 (Decisions D1~D10 + Claims to Verify 표).
- raw/project-notes/ca-skeleton-operational-contract — ca-tmpl skeleton 의 module boundary / operational contract SSOT.
트리거 / Trigger
- 트리거 유형:
branch-work - 트리거 날짜: 2026-05-28
- 트리거 연결 노트: raw/branch-notes/feature-architecture-enforcement-rules —
Decision D1(CA 경계는 architecture test 로 강제) 을 실제 코드와 테스트로 붙인 작업.
글감 / Topic seed
- 한 문장 요지: Clean Architecture 는 문서로만 선언하면 시간이 지나면 무너지므로, Gradle module dependency rule (1차) + ArchUnit bytecode fitness function (2차) 으로 역할을 나눠 자동 검증해야 한다. 한쪽만으로는 항상 false-pass 가 남는다.
- 떠오른 계기:
feature-architecture-enforcement-rules작업에서 ca-tmplsrc/build.gradle의verifyCleanArchitectureDependencies와src/app-bootstrap/src/test/.../CleanArchitectureTest.java를 함께 보강하고, 임시 위반 코드로 red/green 검증까지 마침. - 예상 제목 후보:
- Clean Architecture 경계를 문서가 아니라 테스트 로 지키기 — Gradle + ArchUnit 의 분업
- Gradle 이 잡는 것 vs ArchUnit 이 잡는 것 — module graph 와 bytecode rule 의 역할 분리
- 빈 anchor module 도 ArchUnit 으로 검증할 수 있을까? —
allowEmptyShould(true)의 정직한 사용
핵심 주장 후보 / Claim candidates
아직 canonical 이 아니다. 사실/경험/의견 후보를 분리한다. 각 항목은 branch-note Decision ID 또는 외부 source claim ID 로 근거를 인용한다.
- 사실 후보:
- ca-tmpl 의 boundary 강제는 Gradle 의 project-dependency 매트릭스 + ArchUnit fitness function 두 층으로 구성된다. Gradle 은 build graph 수준, ArchUnit 은 bytecode/import 수준 을 막는다. 둘은 잡는 위반의 종류가 다르다 — 근거:
feature-architecture-enforcement-rules.mdD1 (CA 경계 = architecture test 강제), D2 (package rule = Gradle multi-module boundary). 외부 근거:raw/official-docs/governance-archunit-official.md#AU-OFF-C1,raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C5. domain-core는org.springframework..,jakarta.persistence..,..adapter..,..application..,..bootstrap..import 모두 금지 — 근거:feature-architecture-enforcement-rules.mdD3 (domain-core forbidden import). 외부 근거:raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1(Domain / Application / Framework / Bootstrap 4-module 격리 사례).application-core가org.springframework.transaction.annotation.Transactional을 직접 import 하면 ArchUnit rule 이 실패. Spring 공식은@Transactional직접 부착을 권고 (다수파) 이므로 ca-tmpl 은 의도적 소수파 결정 — 근거:feature-architecture-enforcement-rules.mdD8 (application@Transactional직접 import 금지) +feature-application-port-usecase-contract.mdD3 (TransactionPort abstraction). 외부 근거:raw/official-docs/at-transactional-spring-official.md#AT-TX-C1(Spring 공식 권고).production_code_does_not_depend_on_sample_ticketArchUnit rule + GradleverifyCleanArchitectureDependencies가 동시에 sample-ticket 역수입을 차단 — 근거:feature-architecture-enforcement-rules.mdD7 (sample-ticket production 역수입 금지) + Claims to Verify "production module 이sample-ticket에 의존하면 실패한다" →locally-verified.
- ca-tmpl 의 boundary 강제는 Gradle 의 project-dependency 매트릭스 + ArchUnit fitness function 두 층으로 구성된다. Gradle 은 build graph 수준, ArchUnit 은 bytecode/import 수준 을 막는다. 둘은 잡는 위반의 종류가 다르다 — 근거:
- 경험 후보:
- 임시 위반 코드 (
shared.ticketpackage, controller 의 domain return, application 의@Transactional,app-bootstrap -> sample-ticketGradle dep) 를 각각 추가해 신규 rule 이 실패함을 red/green 으로 확인 → 위반 제거 후cd src && ./gradlew :app-bootstrap:test verifyCleanArchitectureDependencies와cd src && ./gradlew test모두 통과 — 근거:feature-architecture-enforcement-rules.md§진행 중 메모 2026-05-28 항목 + Closure §locally-verified. - 빈 skeleton anchor module 이 ArchUnit empty-should failure 를 일으켜
allowEmptyShould(true)를 빈 상태가 의도된 rule 에만 선별 적용 — 근거:feature-architecture-enforcement-rules.md§마주친 문제 + 파생 에러 raw/errors/archunit-empty-should-anchor-2026-05-27. - Codex sandbox 의 read-only
~/.gradle권한 때문에 wrapper 가 lock 파일을 못 만들어 실행이 실패 → 사용자 승인 escalation 으로 재실행 — 근거: 파생 에러 raw/errors/gradle-wrapper-readonly-cache-2026-05-28.
- 임시 위반 코드 (
- 의견 / 해석 후보:
- Gradle 의 project dependency 매트릭스만으로는 세부 import (e.g., controller 가 JPA entity 를 return type 으로 노출) 를 못 잡는다. ArchUnit 이 이걸 보완. 반대로 ArchUnit 만으로는 module 간 build-graph 사이클 을 깔끔하게 못 잡는다. 둘을 분업하는 게 작은 skeleton 에서는 Spring Modulith 도입보다 가볍다 — 근거:
feature-architecture-enforcement-rules.md§결정 사항 2026-05-27 "Spring Modulith verifier 도입은 out of scope §범위" + D5 Open Risk ("Spring Modulith 없이 public API 강제는 약함"). - ArchUnit 은 reflection / runtime lookup 우회를 잡지 못한다 (
ApplicationContext#getBean류). 이건 ArchUnit 의 한계로 솔직히 인정해야 하며, Sonar custom rule 또는 review checklist 로 보완 — 근거:feature-architecture-enforcement-rules.mdClaims to Verify "runtime lookup 우회는 ArchUnit 으로 잡히지 않는다" →planned. - 다수파 (
@Transactional직접 부착) 도 합리적이다. ca-tmpl 의 boundary 강제는 template repository 라서 의도적으로 엄격한 소수파. 단일 DB / 단일 transaction manager 상황의 작은 팀은 다수파가 boilerplate 비용 측면에서 낫다 — 근거:feature-architecture-enforcement-rules.mdD8 Open Risk +feature-application-port-usecase-contract.md외부 근거 §대안 비교.
- Gradle 의 project dependency 매트릭스만으로는 세부 import (e.g., controller 가 JPA entity 를 return type 으로 노출) 를 못 잡는다. ArchUnit 이 이걸 보완. 반대로 ArchUnit 만으로는 module 간 build-graph 사이클 을 깔끔하게 못 잡는다. 둘을 분업하는 게 작은 skeleton 에서는 Spring Modulith 도입보다 가볍다 — 근거:
Outline seed
각 섹션 옆에
→ 핵심 메시지를 함께 명시한다.
- 동기 — Clean Architecture 를 "했다" 고 말하지만 controller 가 repository 를 import 해도 빌드가 도는 흔한 상황 → 문서만으로는 boundary drift 가 누적된다.
- 두 층의 분업 — Gradle project-dependency matrix vs ArchUnit bytecode rule → 각자 잡는 위반 종류가 다르고 한쪽만으로는 false-pass 가 남는다.
- 실제 구현 스케치 —
verifyCleanArchitectureDependencies의 allowed map +CleanArchitectureTest의 12개 rule → rule 은 도메인 추가 보다 도메인 누락 으로 더 자주 깨진다 (예: 새 module 추가 시 양쪽 다 업데이트 필요). - red/green 으로 rule 을 믿을 수 있게 만들기 — 임시 위반 코드 4종 추가 → 실패 확인 → 제거 → 통과 → rule 이 진짜로 잡는지 를 매번 검증하지 않으면 silent regression 이 생긴다.
- 빈 anchor 문제 — skeleton template 에서 production 코드가 비어 있는 상태도 valid →
allowEmptyShould(true)는 빈 상태가 의도된 rule 에만 선별 적용. 모든 rule 에 일괄 적용하면 안 됨. - ArchUnit 의 한계와 보완 — runtime reflection / MapStruct generated path / Spring Modulith → 솔직한 한계 인정 + 보완 도구 (Sonar / review checklist / Modulith) 의 분업.
- template repository 라서 가능한 엄격함 — production project 와의 trade-off → boundary 비용을 learning cost 로 흡수할 수 있는 환경에서 강제하라.
Canonical 전환 후보 / Canonical extraction candidates
wiki/blog/로 바로 가지 않는다. 먼저 어떤 canonical 문서로 정제할지 기록한다.
wiki/projects/ca-tmpl/architecture-enforcement-rules.md후보:- 실제 적용된 Gradle dependency matrix (모듈별 allowed list).
- 실제 작성된 ArchUnit rule 12종 (이름 + 잡는 위반).
- red/green 검증 절차 (
feature-architecture-enforcement-rules.mdClosure 의locally-verified5개 항목).
wiki/concepts/architecture-enforcement-testing.md후보:- Gradle build-graph rule 과 ArchUnit bytecode rule 의 역할 분리 패턴 (project-agnostic).
allowEmptyShould와 skeleton template 의 빈 anchor 처리 패턴.- "ArchUnit 의 한계: runtime reflection / generated code" 일반 원칙.
- 필요한 추가 검증:
- runtime lookup / reflection 우회가 현재 rule 을 실제로 false-pass 하는지 PoC 실험 (
feature-architecture-enforcement-rules.mdClaims to Verify 의planned항목). - MapStruct generated mapper exemption 경로 확인 (동일 표의
needs-confirmation항목). - Spring Modulith 를 도입했을 때 ArchUnit rule 과의 중복/대체 관계.
- runtime lookup / reflection 우회가 현재 rule 을 실제로 false-pass 하는지 PoC 실험 (
Sources / 근거 후보
- raw/branch-notes/feature-architecture-enforcement-rules — 구현 결정 (D1~D10), 검증 결과, Claims to Verify 의 status grading, Closure 의
locally-verified/documented-only분리. - raw/branch-notes/feature-skeleton-package-blueprint-contract — module boundary 의 자매 결정 (D1~D8). 본 글의 Gradle dependency matrix 항목은 두 branch 결정의 교집합.
- raw/branch-notes/feature-application-port-usecase-contract — application 의
@Transactional직접 import 금지 결정 (D3) 와 TransactionPort 추상화. 본 글의 다수파 vs 소수파 trade-off 단락 근거. - raw/errors/archunit-empty-should-anchor-2026-05-27 — 빈 anchor 와
allowEmptyShould(true)의 선별 적용 사례. - raw/errors/gradle-wrapper-readonly-cache-2026-05-28 — sandbox 환경에서 build tool 실행 검증의 함정.
- raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28 —
production_code_does_not_depend_on_sample_ticketrule 의 test-scope inclusion 미묘함. - raw/official-docs/archunit-user-guide — ArchUnit rule DSL / JUnit 통합 (
ARCHUNIT-UG-C5,ARCHUNIT-UG-C6). - raw/official-docs/governance-archunit-official — architecture test 로 governance 강제하는 일반 근거 (
AU-OFF-C1,AU-OFF-C2). - raw/official-docs/archunit-conditional-on-property-3-layer-pattern — predicate/condition 기반 fitness function 의 bytecode 모델 (
AUCP-C1~AUCP-C5). - raw/company-tech-blogs/woowahan-hexagonal-multimodule — Domain / Application / Framework / Bootstrap 4-module 분리 사례 (
WW-HEX-C1~WW-HEX-C5). - raw/company-tech-blogs/modulith-kakaobank-techblog-2025 — Gradle multi-module + Hexagonal + Spring Modulith 사례 (
KAKAOBANK-MOD-C2,KAKAOBANK-MOD-C4). - raw/interviews/clean-architecture-boundary-enforcement — 같은 경험에서 파생된 예상 면접 질문.
미해결 / Unknown
- 아직 확인해야 할 사실: runtime lookup / reflection 우회가 현재 ArchUnit rule 을 실제로 false-pass 하는지 실험 미수행 (
feature-architecture-enforcement-rules.mdClaims to Verifyplanned). - 아직 확인해야 할 사실: MapStruct generated mapper exemption 의 build path 가 실제 빌드 도구 설정에 따라 어떻게 달라지는지 (
feature-architecture-enforcement-rules.mdD9UNSUPPORTED_DECISION). - 과장하면 안 되는 부분: 본 글의 모든 검증은
locally-verified다. ca-tmpl 은 template repository 이고 prod 운영 검증은 없다. 글에 "운영에서 검증된" 같은 표현 금지. - 과장하면 안 되는 부분: "Gradle + ArchUnit 분업이 Spring Modulith 보다 우월하다" 가 아니라 "작은 skeleton 에서는 가볍다" 까지만 주장 가능. Modulith verifier 를 도입한 사례 (kakaobank) 도 동등한 합리성을 가짐.
- 블로그로 쓰기 전에 필요한 canonical 정제:
wiki/projects/ca-tmpl/architecture-enforcement-rules.md를 최신 코드 상태 (모듈 매트릭스, ArchUnit rule 12종 이름) 로 맞춘 뒤 verified 항목만 blog 초안으로 이동.
Decision / 처리 결정
- 액션:
promote-to-canonical - 이유:
wiki/projects/ca-tmpl/clean-architecture-package-layout.md에 Gradle + ArchUnit boundary enforcement 글감으로 반영한다. - 다음 단계: runtime lookup PoC / MapStruct exemption 같은 planned 항목은 blogify 전 과장 금지로 유지한다.
Related / 관련
- 관련 branch: raw/branch-notes/feature-architecture-enforcement-rules, raw/branch-notes/feature-skeleton-package-blueprint-contract, raw/branch-notes/feature-application-port-usecase-contract (자매 결정).
- 관련 errors: raw/errors/archunit-empty-should-anchor-2026-05-27, raw/errors/gradle-wrapper-readonly-cache-2026-05-28, raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28.
- 관련 interview prep: raw/interviews/clean-architecture-boundary-enforcement, raw/interviews/clean-architecture-module-blueprint (자매 질문), raw/interviews/transaction-port-vs-spring-transactional (
@Transactional다수파 vs 소수파 trade-off 단락의 자매). - 관련 blog topics: raw/blog-topics/clean-architecture-module-blueprint-2026-05-28 (자매 글감), raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28 (다수파/소수파 trade-off 글감), raw/blog-topics/post-implementation-knowledge-capture-workflow-2026-05-28 (캡처 워크플로우 자매).
- derived blog: 생성 전. 생성 시
wiki/blog/clean-architecture-boundary-enforcement-YYYY-MM-DD.md후보.