Files
llm-wiki/raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28.md

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
feature-architecture-enforcement-rules
ca-tmpl
blog-topic
ca-tmpl
architecture
testing
archunit
clean-architecture
gradle
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 / 부모

트리거 / Trigger

글감 / Topic seed

  • 한 문장 요지: Clean Architecture 는 문서로만 선언하면 시간이 지나면 무너지므로, Gradle module dependency rule (1차) + ArchUnit bytecode fitness function (2차) 으로 역할을 나눠 자동 검증해야 한다. 한쪽만으로는 항상 false-pass 가 남는다.
  • 떠오른 계기: feature-architecture-enforcement-rules 작업에서 ca-tmpl src/build.gradleverifyCleanArchitectureDependenciessrc/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.md D1 (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-coreorg.springframework.., jakarta.persistence.., ..adapter.., ..application.., ..bootstrap.. import 모두 금지 — 근거: feature-architecture-enforcement-rules.md D3 (domain-core forbidden import). 외부 근거: raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1 (Domain / Application / Framework / Bootstrap 4-module 격리 사례).
    • application-coreorg.springframework.transaction.annotation.Transactional직접 import 하면 ArchUnit rule 이 실패. Spring 공식은 @Transactional 직접 부착을 권고 (다수파) 이므로 ca-tmpl 은 의도적 소수파 결정 — 근거: feature-architecture-enforcement-rules.md D8 (application @Transactional 직접 import 금지) + feature-application-port-usecase-contract.md D3 (TransactionPort abstraction). 외부 근거: raw/official-docs/at-transactional-spring-official.md#AT-TX-C1 (Spring 공식 권고).
    • production_code_does_not_depend_on_sample_ticket ArchUnit rule + Gradle verifyCleanArchitectureDependencies 가 동시에 sample-ticket 역수입을 차단 — 근거: feature-architecture-enforcement-rules.md D7 (sample-ticket production 역수입 금지) + Claims to Verify "production module 이 sample-ticket 에 의존하면 실패한다" → locally-verified.
  • 경험 후보:
    • 임시 위반 코드 (shared.ticket package, controller 의 domain return, application 의 @Transactional, app-bootstrap -> sample-ticket Gradle dep) 를 각각 추가해 신규 rule 이 실패함을 red/green 으로 확인 → 위반 제거 후 cd src && ./gradlew :app-bootstrap:test verifyCleanArchitectureDependenciescd 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.md Claims to Verify "runtime lookup 우회는 ArchUnit 으로 잡히지 않는다" → planned.
    • 다수파 (@Transactional 직접 부착) 도 합리적이다. ca-tmpl 의 boundary 강제는 template repository 라서 의도적으로 엄격한 소수파. 단일 DB / 단일 transaction manager 상황의 작은 팀은 다수파가 boilerplate 비용 측면에서 낫다 — 근거: feature-architecture-enforcement-rules.md D8 Open Risk + feature-application-port-usecase-contract.md 외부 근거 §대안 비교.

Outline seed

각 섹션 옆에 → 핵심 메시지 를 함께 명시한다.

  1. 동기 — Clean Architecture 를 "했다" 고 말하지만 controller 가 repository 를 import 해도 빌드가 도는 흔한 상황 → 문서만으로는 boundary drift 가 누적된다.
  2. 두 층의 분업 — Gradle project-dependency matrix vs ArchUnit bytecode rule → 각자 잡는 위반 종류가 다르고 한쪽만으로는 false-pass 가 남는다.
  3. 실제 구현 스케치 — verifyCleanArchitectureDependencies 의 allowed map + CleanArchitectureTest 의 12개 rule → rule 은 도메인 추가 보다 도메인 누락 으로 더 자주 깨진다 (예: 새 module 추가 시 양쪽 다 업데이트 필요).
  4. red/green 으로 rule 을 믿을 수 있게 만들기 — 임시 위반 코드 4종 추가 → 실패 확인 → 제거 → 통과 → rule 이 진짜로 잡는지 를 매번 검증하지 않으면 silent regression 이 생긴다.
  5. 빈 anchor 문제 — skeleton template 에서 production 코드가 비어 있는 상태도 valid → allowEmptyShould(true)빈 상태가 의도된 rule 에만 선별 적용. 모든 rule 에 일괄 적용하면 안 됨.
  6. ArchUnit 의 한계와 보완 — runtime reflection / MapStruct generated path / Spring Modulith → 솔직한 한계 인정 + 보완 도구 (Sonar / review checklist / Modulith) 의 분업.
  7. 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.md Closure 의 locally-verified 5개 항목).
  • 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.md Claims to Verify 의 planned 항목).
    • MapStruct generated mapper exemption 경로 확인 (동일 표의 needs-confirmation 항목).
    • Spring Modulith 를 도입했을 때 ArchUnit rule 과의 중복/대체 관계.

Sources / 근거 후보

미해결 / Unknown

  • 아직 확인해야 할 사실: runtime lookup / reflection 우회가 현재 ArchUnit rule 을 실제로 false-pass 하는지 실험 미수행 (feature-architecture-enforcement-rules.md Claims to Verify planned).
  • 아직 확인해야 할 사실: MapStruct generated mapper exemption 의 build path 가 실제 빌드 도구 설정에 따라 어떻게 달라지는지 (feature-architecture-enforcement-rules.md D9 UNSUPPORTED_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 전 과장 금지로 유지한다.