15 KiB
15 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-module-blueprint-2026-05-28 | blog-topic | raw |
|
|
|
2026-05-28 | ready-for-canonical | backend-engineer |
blog-topic: clean-architecture-module-blueprint-2026-05-28
Layer:
raw/blog-topics/— 채용공고가 아닌 작업·학습·트러블슈팅에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보이며,wiki/blog/직접 생성 근거가 아니다.
Parent / 부모
- raw/branch-notes/feature-skeleton-package-blueprint-contract — Clean Architecture skeleton 의 package/module blueprint 를 single-module feature-first 에서 Gradle multi-module Hexagonal boundary 로 수정 한 결정 (Decisions D1~D8 + Default Module Blueprint tree).
- raw/project-notes/ca-skeleton-operational-contract — ca-tmpl skeleton 의 운영 계약 SSOT (§20 Skeleton Blueprint Contract 영역).
트리거 / Trigger
- 트리거 유형:
branch-work - 트리거 날짜: 2026-05-27
- 트리거 연결 노트: raw/branch-notes/feature-skeleton-package-blueprint-contract — 초기 single-module feature-first 결정 (
결정 사항 2026-05-22) 을결정 사항 2026-05-27에서 Gradle multi-module Hexagonal 로 명시적으로 수정 한 점이 글감의 핵심 사건.
글감 / Topic seed
- 한 문장 요지: Clean Architecture skeleton 은 패키지 이름을 예쁘게 나누는 것만으로는 부족하다. Gradle module boundary 가 1차 강제선, package 내부 책임이 2차 분류 가 되어야 새 도메인 기능이 들어와도 경계가 무너지지 않는다.
- 떠오른 계기: ca-tmpl 의 초기 결정이 single-module feature-first 였다가 우아한형제들 / 카카오뱅크 사례 검토 후 multi-module Hexagonal 로 명시적으로 수정 된 과정 — 의사결정의 뒤집힘 자체가 글감.
- 예상 제목 후보:
- Clean Architecture 템플릿에서 package 이름보다 먼저 정해야 할 것 — module boundary
- 우리는 왜 single-module feature-first 에서 multi-module Hexagonal 로 바꿨나
- reference code 를 production 에서 빼고
sample-ticket으로 격리한 이유
핵심 주장 후보 / Claim candidates
각 항목은 branch-note Decision ID 또는 외부 source claim ID 로 근거를 인용한다.
- 사실 후보:
- ca-tmpl 의 기본 module 은
app-bootstrap,domain-core,application-core,adapter-web,adapter-persistence,adapter-outbound,shared-contract,sample-ticket8개. module boundary 가 1차 강제선이고 module 내부 package 는 2차 책임 분류 — 근거:feature-skeleton-package-blueprint-contract.mdD1 (Phase C2 기본 구조 = Gradle multi-module + Clean Architecture / Hexagonal),결정 사항 2026-05-27("module boundary 가 1차 강제선이고, module 내부 package 는 2차 책임 분류"). 외부 근거:raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1,raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4. domain-core는 framework-neutral POJO 로 유지. Spring annotation, JPA annotation, HTTP DTO 를 모두 모름 — 근거:feature-skeleton-package-blueprint-contract.mdD2 (domain-core = framework-neutral). 외부 근거:raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1,raw/official-docs/arch-clean-architecture-uncle-bob.md(Dependency Rule).application-core는domain-core와shared-contract에만 의존. adapter 구현체 / Spring Web / JPA / Redis / Kafka / outbound HTTP client 모두 adapter 밖으로 들어오면 안 됨 — 근거:feature-skeleton-package-blueprint-contract.mdD3 (application-core = domain + shared only). 외부 근거:raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C2,raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4.shared-contract는 response envelope / error code / header / MDC / metric / registry / annotation 같은 skeleton-wide operational contract 만 허용. business / domain concept 는 금지 — 근거:feature-skeleton-package-blueprint-contract.mdD6 (shared-contract = operational contract only). 외부 근거:raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011.md#SCREAM-C1.sample-ticket은 fixture/sample consumer 이며 production module 이 import 하거나 dependency 로 선언하면 실패 — 근거:feature-skeleton-package-blueprint-contract.mdD7 (sample-ticket production 역수입 금지) +feature-architecture-enforcement-rules.mdD7 (자매 ArchUnit rule). 외부 근거: ca-tmpl 자체 결정 (project-decision).- 초기 결정 (single-module feature-first) 은 2026-05-22, 수정 결정 (Gradle multi-module + Clean Architecture / Hexagonal) 은 2026-05-27 — 근거:
feature-skeleton-package-blueprint-contract.md§결정 사항 ("2026-05-22: 초기 문서의 기본 구조는 single-module feature-first package layout이었다", "2026-05-27: Phase C2 기본 구조는 ... 로 수정한다").
- ca-tmpl 의 기본 module 은
- 경험 후보:
- 기존 reference code (blog domain) 를 production module 에서
sample-ticket/src/main/java/dev/caskeleton/sample/ticket/...로 격리. production module 은package-info.java+ skeleton anchor 중심으로 정리 — 근거:feature-skeleton-package-blueprint-contract.mdClosure §actually-implemented"기존 reference code는 production module에서sample-ticket내부dev.caskeleton.sample.ticket.*package로 격리됨". - production package root 를
dev.caskeleton으로 rename +BlogApplication→CaSkeletonApplication+blog.*설정 prefix →ca-skeleton.*전환 — 근거: 동일 Closure 항목. - 빈 skeleton anchor module 이 ArchUnit empty should failure 를 일으켜
allowEmptyShould(true)를 빈 상태가 의도된 rule 에만 선별 적용 — 근거: 파생 에러 raw/errors/archunit-empty-should-anchor-2026-05-27 §해결 ("빈 상태가 skeleton contract상 유효한 rule에만allowEmptyShould(true)"). sample-ticket격리 후 sample 내부GlobalExceptionHandler가InvalidBearerTokenException을 import 하지만 sample build.gradle 에spring-boot-starter-oauth2-resource-server가 없어서 compile 실패 → starter 명시 추가 — 근거: 파생 에러 raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27 §해결.
- 기존 reference code (blog domain) 를 production module 에서
- 의견 / 해석 후보:
- 템플릿 프로젝트의 완성도 기준은 "예시 도메인이 잘 돈다" 가 아니라 "예시를 통째로 들어내도 경계가 남는다" 이다. ca-tmpl 의
sample-ticket격리는 이 기준의 직접 검증. common/shared모듈은 편의 보다 오염 방지 규칙 을 먼저 가져야 한다. ca-tmpl 의shared-contract는 8개 sub-package allowlist (response/error/headers/logging/tracing/metrics/registry/annotation) 로 명시 제한.- single-module feature-first 도 작은 프로젝트엔 합리적이다. ca-tmpl 이 multi-module 을 택한 건 template repository 라서 새 프로젝트가 시작될 때 경계가 흐트러지지 않도록 학습 비용을 미리 흡수한다는 결정 — 근거:
feature-skeleton-package-blueprint-contract.mdD8 Open Risk ("작은 프로젝트에서는 single-module이 비용이 낮을 수 있음. ca-tmpl은 skeleton template이므로 boundary 학습/검증 비용을 감수"). - company-tech-blog 사례 (우아한형제들 / 카카오뱅크) 는 best practice 가 아니라 case study 다. 본 글에서 이 두 사례를 인용하되 "공식 표준" 으로 승격하지 않는 정직함이 중요 — 근거:
feature-skeleton-package-blueprint-contract.mdDecision Evidence Map 의 Evidence Strength 컬럼 (company-case-study).
- 템플릿 프로젝트의 완성도 기준은 "예시 도메인이 잘 돈다" 가 아니라 "예시를 통째로 들어내도 경계가 남는다" 이다. ca-tmpl 의
Outline seed
각 섹션 옆에
→ 핵심 메시지를 함께 명시한다.
- 의사결정의 뒤집힘 — 2026-05-22 의 single-module feature-first 결정을 2026-05-27 에 명시적으로 수정한 과정 → template 의 첫 결정도 case study 검토 뒤 뒤집을 수 있다는 정직함.
- module boundary 가 1차 강제선인 이유 — package convention 만으로는 import 가 자유롭다 → Gradle dependency graph 가 컴파일 단계에서 위반을 막는다.
- 8개 module 의 책임 —
domain-core,application-core,adapter-{web,persistence,outbound},shared-contract,sample-ticket,app-bootstrap→ dependency direction 표 + 각 모듈의 forbidden import 매트릭스. shared-contract를 좁게 잡는 이유 — 8개 sub-package allowlist (response/error/headers/...) → business common dumping ground 방지가 편의 보다 우선.sample-ticket격리 — production module 의 ArchUnit rule + Gradle dependency rule + 별도*Application가 없음 → "예시를 들어내도 경계가 남는다" 가 template repository 의 완성도 기준.- 구현 중 드러난 작은 실패들 — 빈 anchor 의
allowEmptyShould선별 적용 + sample-ticket compile classpath 누락 → template repository 의 "비어 있음" 은 의도된 상태일 수 있다. - 다른 선택지의 정직한 비교 — single-module / layer-first / pure hexagonal / Spring Modulith → ca-tmpl 의 선택이 유일한 정답 이 아니라 이 맥락에서의 최적 임을 명시.
Canonical 전환 후보 / Canonical extraction candidates
wiki/projects/ca-tmpl/clean-architecture-package-layout.md후보:- 실제 적용된 8 module + 각 모듈의 sub-package 책임 트리 (
feature-skeleton-package-blueprint-contract.md§Default Module Blueprint). - dependency direction 매트릭스 (
Module Dependency Rule표). dev.caskeleton으로의 package rename +CaSkeletonApplication/BootstrapSettings/ca-skeleton.*설정 prefix 전환.- local verification 결과 4종 (
./gradlew test,verifyCleanArchitectureDependencies,:app-bootstrap:test --tests '*CleanArchitectureTest',:adapter-web:test --tests '*SettingsTest').
- 실제 적용된 8 module + 각 모듈의 sub-package 책임 트리 (
wiki/concepts/clean-architecture-package-layout.md후보:- multi-module Hexagonal-inspired skeleton layout 의 일반화 원칙 (project-agnostic).
- "module boundary 1차, package convention 2차" 분업 원칙.
shared모듈을 좁게 잡는 operational contract only rule.- "예시를 들어내도 경계가 남는다" 의 template completeness 기준.
- 필요한 추가 검증:
- canonical 문서가 최신 코드 상태 (
dev.caskeleton,sample-ticket격리, Spring Boot 3.5.14, 새로 추가된feature-application-port-usecase-contract의application-core패키지 구조) 까지 반영하는지. - Spring Modulith named interface 를 후속 도입했을 때 Gradle multi-module + ArchUnit 구성과의 중복/대체 관계.
- canonical 문서가 최신 코드 상태 (
Sources / 근거 후보
- raw/branch-notes/feature-skeleton-package-blueprint-contract — 결정 D1~D8, Default Module Blueprint tree, Module Dependency Rule 표, Closure §
actually-implemented/locally-verified. - raw/branch-notes/feature-architecture-enforcement-rules — 자매 결정 D1~D10. 본 글의 ArchUnit 단락 근거.
- raw/branch-notes/feature-application-port-usecase-contract —
application-corepackage 구조의 후속 결정 (D1: inbound*UseCase/ outbound*Portnaming). canonical 정제 시 통합 필요. - raw/errors/archunit-empty-should-anchor-2026-05-27 — 빈 anchor 와
allowEmptyShould(true)선별 적용. - raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27 — sample 격리 후 compile dependency 누락.
- raw/interviews/clean-architecture-module-blueprint — 같은 작업에서 파생된 예상 면접 질문.
- raw/interviews/shared-contract-and-sample-isolation — shared/sample 책임 경계 예상 질문.
- raw/company-tech-blogs/woowahan-hexagonal-multimodule — Domain / Application / Framework / Bootstrap 4-module 격리 사례 (
WW-HEX-C1,WW-HEX-C2). - raw/company-tech-blogs/modulith-kakaobank-techblog-2025 — Gradle multi-module + Hexagonal + Spring Modulith 사례 (
KAKAOBANK-MOD-C2,KAKAOBANK-MOD-C4). - raw/official-docs/hexagonal-cockburn-wikipedia-summary — Ports & Adapters 원형 (
HEX-WIKI-C5). - raw/official-docs/arch-clean-architecture-uncle-bob — Dependency Rule 의 클래식 근거 (
engineering-blog, official standard 아님). - raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011 — feature/use-case 가 framework 위에 드러나야 한다는 사상 (
SCREAM-C1). - raw/official-docs/hexagonal-thombergs-buckpal-github — feature/package 내부 port-adapter 책임 분리 참고.
- raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot — 대안 1: layer-first 의 입문형 사례.
- raw/official-docs/onion-palermo-original-2008 — 대안 4: onion 의 원형.
미해결 / Unknown
- 아직 확인해야 할 사실: ca-tmpl 의 8 module 구조가 실제 사업 도메인 이 들어왔을 때 module 분할 또는 새 adapter (e.g.,
adapter-messaging) 추가가 자연스럽게 가능한지. 현재는 reference (sample-ticket) 만 검증. - 아직 확인해야 할 사실: Spring Modulith 의 named interface 검증을 추가하면 ArchUnit rule 중 어느 것이 중복 이고 어느 것이 보완 인지.
- 과장하면 안 되는 부분: 본 글의 모든 검증은
locally-verified이며 prod 운영 검증 없음. "운영에서 검증됐다" 라는 표현 금지. - 과장하면 안 되는 부분: 우아한형제들 / 카카오뱅크 사례는 case study 다. "대기업에서 표준" 또는 "industry standard" 같은 표현으로 격상시키지 말 것 —
feature-skeleton-package-blueprint-contract.mdDecision Evidence Map Open Risk 컬럼이 이 한계를 명시. - 블로그로 쓰기 전에 필요한 canonical 정제:
wiki/projects/ca-tmpl/clean-architecture-package-layout.md를 최신 코드 상태 (특히feature-application-port-usecase-contract작업으로 추가된application-core패키지 구조) 까지 반영한 뒤 verified 항목만 글로 이동.
Decision / 처리 결정
- 액션:
promote-to-canonical - 이유:
wiki/projects/ca-tmpl/clean-architecture-package-layout.md에 module/package blueprint 글감으로 반영했다. - 다음 단계: source canonical이
verified상태이므로 이후blogify대상으로 삼을 수 있다. 단 운영 검증이나 전체 Phase C2 완료처럼 쓰지 않는다.
Related / 관련
- 관련 branch: raw/branch-notes/feature-skeleton-package-blueprint-contract, raw/branch-notes/feature-architecture-enforcement-rules (자매 — boundary 강제), raw/branch-notes/feature-application-port-usecase-contract (후속 — application 내부 패키지 구조).
- 관련 errors: raw/errors/archunit-empty-should-anchor-2026-05-27, raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27.
- 관련 interview prep: raw/interviews/clean-architecture-module-blueprint, raw/interviews/shared-contract-and-sample-isolation, raw/interviews/clean-architecture-boundary-enforcement.
- 관련 blog topics: raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28 (자매 글감 — boundary 강제), raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28 (자매 글감 — application 의 framework 격리).
- derived blog: 생성 전. 생성 시
wiki/blog/clean-architecture-module-blueprint-YYYY-MM-DD.md후보.