Files
llm-wiki/raw/blog-topics/clean-architecture-module-blueprint-2026-05-28.md
T

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
feature-skeleton-package-blueprint-contract
ca-skeleton
blog-topic
ca-skeleton
architecture
gradle
clean-architecture
hexagonal
multi-module
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 / 부모

트리거 / 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-ticket 8개. module boundary 가 1차 강제선이고 module 내부 package 는 2차 책임 분류 — 근거: feature-skeleton-package-blueprint-contract.md D1 (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.md D2 (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-coredomain-coreshared-contract 에만 의존. adapter 구현체 / Spring Web / JPA / Redis / Kafka / outbound HTTP client 모두 adapter 밖으로 들어오면 안 됨 — 근거: feature-skeleton-package-blueprint-contract.md D3 (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.md D6 (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.md D7 (sample-ticket production 역수입 금지) + feature-architecture-enforcement-rules.md D7 (자매 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 기본 구조는 ... 로 수정한다").
  • 경험 후보:
    • 기존 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.md Closure §actually-implemented "기존 reference code는 production module에서 sample-ticket 내부 dev.caskeleton.sample.ticket.* package로 격리됨".
    • production package root 를 dev.caskeleton 으로 rename + BlogApplicationCaSkeletonApplication + 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 내부 GlobalExceptionHandlerInvalidBearerTokenException 을 import 하지만 sample build.gradle 에 spring-boot-starter-oauth2-resource-server 가 없어서 compile 실패 → starter 명시 추가 — 근거: 파생 에러 raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27 §해결.
  • 의견 / 해석 후보:
    • 템플릿 프로젝트의 완성도 기준은 "예시 도메인이 잘 돈다" 가 아니라 "예시를 통째로 들어내도 경계가 남는다" 이다. 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.md D8 Open Risk ("작은 프로젝트에서는 single-module이 비용이 낮을 수 있음. ca-tmpl은 skeleton template이므로 boundary 학습/검증 비용을 감수").
    • company-tech-blog 사례 (우아한형제들 / 카카오뱅크) 는 best practice 가 아니라 case study 다. 본 글에서 이 두 사례를 인용하되 "공식 표준" 으로 승격하지 않는 정직함이 중요 — 근거: feature-skeleton-package-blueprint-contract.md Decision Evidence Map 의 Evidence Strength 컬럼 (company-case-study).

Outline seed

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

  1. 의사결정의 뒤집힘 — 2026-05-22 의 single-module feature-first 결정을 2026-05-27 에 명시적으로 수정한 과정 → template 의 첫 결정도 case study 검토 뒤 뒤집을 수 있다는 정직함.
  2. module boundary 가 1차 강제선인 이유 — package convention 만으로는 import 가 자유롭다 → Gradle dependency graph 가 컴파일 단계에서 위반을 막는다.
  3. 8개 module 의 책임 — domain-core, application-core, adapter-{web,persistence,outbound}, shared-contract, sample-ticket, app-bootstrapdependency direction 표 + 각 모듈의 forbidden import 매트릭스.
  4. shared-contract 를 좁게 잡는 이유 — 8개 sub-package allowlist (response/error/headers/...) → business common dumping ground 방지가 편의 보다 우선.
  5. sample-ticket 격리 — production module 의 ArchUnit rule + Gradle dependency rule + 별도 *Application없음"예시를 들어내도 경계가 남는다" 가 template repository 의 완성도 기준.
  6. 구현 중 드러난 작은 실패들 — 빈 anchor 의 allowEmptyShould 선별 적용 + sample-ticket compile classpath 누락 → template repository 의 "비어 있음" 은 의도된 상태일 수 있다.
  7. 다른 선택지의 정직한 비교 — 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').
  • 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-contractapplication-core 패키지 구조) 까지 반영하는지.
    • Spring Modulith named interface 를 후속 도입했을 때 Gradle multi-module + ArchUnit 구성과의 중복/대체 관계.

Sources / 근거 후보

미해결 / 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.md Decision 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 완료처럼 쓰지 않는다.