Files
llm-wiki/raw/branch-notes/feature-skeleton-package-blueprint-contract.md
T

496 lines
34 KiB
Markdown

---
title: branch / feature-skeleton-package-blueprint-contract
source_type: branch-note
status: verified
branch: feature-skeleton-package-blueprint-contract
related_projects: [ca-skeleton]
tags: [branch, ca-skeleton, package, module, blueprint]
created: 2026-05-22
last_reviewed: 2026-06-04
target_merge:
status_label: locally-verified
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-040
kind: project-work-item
project: ca-skeleton-operational-contract
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-040
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1, DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-BUILD-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
parent_branch:
contract_packet_sha256: 08f4adda9deebbce6ac685d214e739d3d086211429f2522a2db4886ca1f9cead
---
# branch: feature-skeleton-package-blueprint-contract
> Layer: `raw/branch-notes/` — 실제 구현 시 package/module 위치가 흔들리지 않도록 skeleton blueprint를 정의합니다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: Gradle module graph가 declared layout과 일치한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1` | Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-BUILD-001@1` | build tool은 Gradle Groovy DSL이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
좋은 원칙이 있어도 module boundary와 package 위치를 함께 고정하지 않으면 구현자는 자기 방식으로 구조를 만듭니다. 이 branch는 Gradle multi-module을 1차 경계로 두고, 각 module 내부 package 책임을 Clean Architecture / Hexagonal 규칙에 맞게 고정해 실제 도메인 기능이 바로 들어올 수 있게 합니다.
- 이슈:
- PR: (local branch only; remote PR not created in this session)
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- Gradle multi-module blueprint.
- module dependency direction.
- module 내부 package blueprint.
- shared/common module 허용 범위.
- sample module 격리 기준.
- architecture rule 연결 기준.
- single-module 축소형은 예외 mapping으로만 허용.
### 제외 범위
- build tool plugin 구현.
- code generator 구현.
## TODO
> TODO drained 2026-05-22, revised 2026-05-27 — 결정은 아래 "결정 사항" / "Default Module Blueprint" / "판정 기준" / "테스트 계약" 참조. Gradle multi-module blueprint, module dependency direction, module 내부 package 책임, shared/common 책임, sample 격리, architecture test 모두 결정 라인 또는 blueprint tree로 반영됨. 잔존 TODO 없음.
> 본 branch는 패키지 트리 자체가 결정 산출물. 별도 Decisionized Work Items 표는 작성하지 않음. 트리의 각 sub-package 책임은 결정 사항과 판정 기준이 등가로 정의.
## Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
| --- | --- | --- |
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
## 결정 사항
- 2026-05-22: 초기 문서의 기본 구조는 single-module feature-first package layout이었다.
- 2026-05-27: Phase C2 기본 구조는 **Gradle multi-module + Clean Architecture / Hexagonal boundary** 로 수정한다. module boundary가 1차 강제선이고, module 내부 package는 2차 책임 분류다.
- 2026-05-27: 기본 module은 `app-bootstrap`, `domain-core`, `application-core`, `adapter-web`, `adapter-persistence`, `adapter-outbound`, `shared-contract`, `sample-portfolio`으로 둔다.
- 2026-05-27: `application-core``domain-core``shared-contract`에만 의존한다. Spring Web / JPA / Redis / Kafka / outbound HTTP client 구현체는 adapter module 밖으로 들어오면 안 된다.
- 2026-05-27: `domain-core`는 framework-neutral POJO를 기본으로 하며 Spring annotation, JPA annotation, HTTP DTO를 알지 않는다.
- 2026-05-27: `shared-contract`에는 response envelope, error code, header/MDC/metric registry, 공통 annotation처럼 skeleton-wide operational contract만 둔다. business/domain concept는 넣지 않는다.
- 2026-05-27: single-module 구조는 학습/예제 축소형으로만 허용한다. Phase C2 기본값은 multi-module이다.
## 판정 기준
| 구분 | 기준 |
| --- | --- |
| Decision | Gradle multi-module blueprint를 skeleton contract의 기본값으로 관리 |
| Allowed | demo/readme용 single-module 축소형은 허용하되, 반드시 multi-module responsibility mapping을 보존 |
| Forbidden | `domain-core` 또는 `application-core`가 Spring Web/JPA/Redis/Kafka/outbound HTTP 구현체에 직접 의존 |
| Forbidden | business/domain concept가 `shared-contract` 또는 adapter module로 이동 |
| Required mapping | bootstrap, domain, application, inbound adapter, outbound adapter, shared contract, sample, architecture/contract test |
| Failure condition | 새 도메인 기능의 module 위치와 dependency direction을 blueprint로 판정할 수 없으면 실패 |
## Default Module Blueprint
```text
settings.gradle
rootProject.name = 'ca-skeleton'
include 'app-bootstrap'
include 'domain-core'
include 'application-core'
include 'adapter-web'
include 'adapter-persistence'
include 'adapter-outbound'
include 'shared-contract'
include 'sample-portfolio'
app-bootstrap/
src/main/java/{basePackage}/bootstrap/
CaSkeletonApplication
config/
src/test/java/{basePackage}/bootstrap/
smoke/
shared-contract/
src/main/java/{basePackage}/shared/
response/
error/
headers/
logging/
tracing/
metrics/
registry/
annotation/
src/test/java/{basePackage}/shared/
contract/
domain-core/
src/main/java/{basePackage}/domain/
model/
vo/
event/
service/
src/test/java/{basePackage}/domain/
unit/
application-core/
src/main/java/{basePackage}/application/
port/in/
port/out/
usecase/
command/
query/
policy/
src/test/java/{basePackage}/application/
usecase/
contract/
adapter-web/
src/main/java/{basePackage}/adapter/web/
controller/
dto/
mapper/
filter/
exception/
src/test/java/{basePackage}/adapter/web/
mvc/
contract/
adapter-persistence/
src/main/java/{basePackage}/adapter/persistence/
entity/
repository/
mapper/
migration/
src/test/java/{basePackage}/adapter/persistence/
integration/
adapter-outbound/
src/main/java/{basePackage}/adapter/outbound/
httpclient/
messaging/
cache/
notification/
src/test/java/{basePackage}/adapter/outbound/
contract/
sample-portfolio/
src/main/java/{basePackage}/sample/worklog/
domain/
application/
web/
persistence/
src/test/java/{basePackage}/sample/worklog/
contract/
```
## Module Dependency Rule
| Module | May depend on | Must not depend on |
| --- | --- | --- |
| `domain-core` | (none) or `shared-contract` value-only types | Spring, JPA, HTTP DTO, Redis/Kafka/client libraries, adapter modules |
| `application-core` | `domain-core`, `shared-contract` | `adapter-*`, `app-bootstrap`, Spring Web/JPA implementation APIs |
| `adapter-web` | `application-core`, `domain-core`, `shared-contract` | `adapter-persistence`, `adapter-outbound` direct implementation coupling |
| `adapter-persistence` | `application-core`, `domain-core`, `shared-contract` | `adapter-web`, `app-bootstrap` |
| `adapter-outbound` | `application-core`, `domain-core`, `shared-contract` | `adapter-web`, `app-bootstrap` |
| `app-bootstrap` | all runtime modules | domain policy implementation |
| `sample-portfolio` | all runtime modules only as fixture consumer | production module importing `sample-portfolio` |
single-module 문서가 필요하면 위 module responsibility mapping을 보존한 축소 변환표를 함께 둡니다. 단, Phase C2 기본 구현은 multi-module이다.
## 근거 (필수, 최소 1개+)
> 본 branch의 결정 근거. multi-module 기본값은 company-case-study 근거가 중심이므로, 공식 best practice가 아니라 사례 기반 프로젝트 결정으로 취급한다.
| Source | 정당화하는 결정 |
|---|---|
| [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] | Domain / Application / Framework / Bootstrap module로 hexagonal boundary를 물리 분리한 국내 사례 |
| [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] | Gradle multi-module + Hexagonal 위에서 application/adapter 계층을 물리 분리하고 Port로 통신한 사례 |
| [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]] | application core와 adapter를 port로 격리하는 Hexagonal / Ports and Adapters 원형 |
| [[raw/official-docs/hexagonal-thombergs-buckpal-github]] | feature/package 내부 port-adapter 책임 분리 참고 |
| [[raw/official-docs/arch-clean-architecture-uncle-bob]] | Dependency Rule과 Entities / Use Cases / Interface Adapters / Frameworks-Drivers 계층 사고 근거 (`engineering-blog`, official standard 아님) |
| [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]] | framework가 아니라 use case / business 영역이 구조에서 드러나야 한다는 feature-first 사상 근거 |
| [[raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature]] | layer-first 대비 feature 응집도 사례 |
| [[raw/official-docs/modulith-spring-official-doc]] | package/module boundary verification 대안. Phase C2 기본값은 아니며 후속 검토 후보 |
| [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] | Spring Modulith를 Gradle multi-module + Hexagonal 위에 체리픽한 사례 |
| [[raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring]] | Spring Modulith 이전 modular monolith reference 구현 사례 |
| [[raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot]] | layer-first / Clean Architecture 입문형 대안 비교 |
| [[raw/company-tech-blogs/layer-first-kamilmazurek-github-template]] | layer-first template 대안 비교 |
| [[raw/company-tech-blogs/hexagonal-woowahan-techblog-2023]] | hexagonal 적용 사례 비교 |
| [[raw/official-docs/onion-palermo-original-2008]] | Onion Architecture dependency direction 비교 |
| [[raw/company-tech-blogs/onion-allegro-tech-blog-2023]] | Onion Architecture 적용 사례 비교 |
## 외부 근거 / 대안 조사 (2026-05-22 — Topic 1)
본 branch의 청사진 결정은 2026-05-27에 single-module feature-first package 기본값에서 Gradle multi-module Clean Architecture / Hexagonal 기본값으로 수정되었다. 5종 대안 비교는 `wiki/concepts/clean-architecture-package-layout.md` 참조.
- **채택 결정 (multi-module Clean Architecture / Hexagonal)**:
- [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]] — Uncle Bob Screaming Architecture 원형
- [[raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature]] — feature vs layer 비교 사례
- [[raw/official-docs/hexagonal-thombergs-buckpal-github]] — feature/package 내부 port-adapter 책임 분리 참고
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] — Domain / Application / Framework / Bootstrap 4-module hexagonal 사례
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] — Gradle multi-module + Hexagonal + Spring Modulith 사례
- **검토한 대안**:
- **대안 1: layer-first** — [[raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot]], [[raw/company-tech-blogs/layer-first-kamilmazurek-github-template]]
- **대안 2: hexagonal pure** — [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]], [[raw/company-tech-blogs/hexagonal-woowahan-techblog-2023]]
- **대안 3: Spring Modulith** — [[raw/official-docs/modulith-spring-official-doc]], [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]], [[raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring]]
- **대안 4: onion** — [[raw/official-docs/onion-palermo-original-2008]], [[raw/company-tech-blogs/onion-allegro-tech-blog-2023]]
- **비교 핵심 (1줄)**: buckpal은 feature/package 내부 port-adapter 구조 참고로 유지하고, Phase C2 기본 구현은 우아한형제들/카카오뱅크 사례처럼 module boundary로 application/domain과 adapter를 물리 분리한다. Spring Modulith는 기본값이 아니라 향후 module verification 보강 대안으로 둔다.
## 결정-근거 매핑
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. `Decision ID` 는 본 branch-note 안에서 안정적으로 유지 (D1~D8). module tree 와 package 책임도 본 표의 row 로 매핑.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | Phase C2 기본 구조는 Gradle multi-module + Clean Architecture / Hexagonal boundary | `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1`, `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4` | `company-case-study` | 두 자료 모두 사례이며 공식 표준은 아님. ca-tmpl에 그대로 이식하려면 build.gradle dependency graph와 ArchUnit rule로 별도 검증 필요 |
| D2 | `domain-core`는 framework-neutral domain model을 담고 adapter/framework에 의존하지 않음 | `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1`, `raw/official-docs/arch-clean-architecture-uncle-bob.md` | `company-case-study + engineering-blog` | `shared-contract` value-only type까지 허용할지 여부는 ca-tmpl 자체 결정 |
| D3 | `application-core`는 domain에만 직접 의존하고 adapter 구현체와 통신하지 않음 | `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C2`, `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4` | `company-case-study` | Spring transaction boundary를 application port로 추상화할 때 `spring-tx` 의존을 어느 module에 둘지는 TransactionPort branch와 함께 검증 필요 |
| D4 | adapter module은 inbound(`adapter-web`)와 outbound(`adapter-persistence`, `adapter-outbound`)로 물리 분리 | `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4`, `raw/official-docs/hexagonal-cockburn-wikipedia-summary.md#HEX-WIKI-C5` | `company-case-study + official-reference` | adapter를 persistence/outbound로 나누는 세부 module 수는 ca-tmpl 자체 운영 결정 |
| D5 | module 간 통신은 public API / port interface를 통해서만 허용 | `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C2`, `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4` | `company-case-study` | Spring Modulith를 바로 도입하지 않으면 public API 강제는 ArchUnit/package-private convention으로 보완해야 함 |
| D6 | `shared-contract`에는 skeleton-wide operational contract만 두고 business/domain concept는 금지 | `raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011.md#SCREAM-C1` | `engineering-blog` | `shared-contract`가 커지면 de-facto common dumping ground가 될 수 있음. registry owner와 forbidden import rule 필요 |
| D7 _(UNSUPPORTED_DECISION)_ | `sample-portfolio`은 fixture module이며 production module이 import하면 실패 | D1~D5에서 파생된 ca-tmpl 자체 결정 — 외부 공식 근거 없음 | `project-decision` | 외부 직접 근거 부족. ArchUnit + Gradle dependency rule로 실증 필요. **UNSUPPORTED_DECISION** — external official-doc/company-tech-blog claim 없음. 외부 근거 보강 시 갱신 예정 |
| D8 | single-module 구조는 축소형 문서/예제로만 허용하고 Phase C2 기본값은 multi-module | `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1`, `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4` | `company-case-study` | 작은 프로젝트에서는 single-module이 비용이 낮을 수 있음. ca-tmpl은 skeleton template이므로 boundary 학습/검증 비용을 감수한다는 프로젝트 결정 |
| D9 | module 간 dependency 선언 기본값은 `implementation`이며, 소비자 module의 public ABI(port interface 반환·파라미터 타입)에 타 module type이 노출될 때만 `api` 사용 | `raw/official-docs/gradle-java-library-api-vs-implementation.md#GRADLE-JAVALIB-C2`, `raw/official-docs/gradle-java-library-api-vs-implementation.md#GRADLE-JAVALIB-C3`, `raw/official-docs/gradle-java-library-api-vs-implementation.md#GRADLE-JAVALIB-C4` | `official-vendor-doc` | 각 module의 `build.gradle` dependency 선언 시 `api` vs `implementation` 구분 기준이 없어 정책 미정이었던 구멍을 해소. ca-tmpl 8개 module 모두에 적용 | 어느 module이 실제로 `api`를 써야 하는지(예: `application-core``domain-core``api`로 선언해야 하는지)는 port interface 설계 완료 후 검증 필요. `bootJar` 런타임 포함 여부는 별도 확인 |
| D10 | `@SpringBootApplication``app-bootstrap` 모듈의 `dev.caskeleton.bootstrap` (root package) 에 배치한다. default package 사용 금지. | `raw/official-docs/spring-boot-structuring-your-code.md#SB-STRUCT-C1`, `raw/official-docs/spring-boot-structuring-your-code.md#SB-STRUCT-C2`, `raw/official-docs/spring-boot-structuring-your-code.md#SB-STRUCT-C3`, `raw/official-docs/spring-boot-structuring-your-code.md#SB-STRUCT-C4` | `official-vendor-doc` | multi-module 구조에서 `@SpringBootApplication` 이 어느 모듈·패키지에 위치해야 하는지는 이 공식 근거로 직접 뒷받침되지 않음 (단일 모듈 기준 설명). `scanBasePackages` 추가 설정 필요 여부는 integration test로 검증 필요 |
## 검증해야 할 주장
> 공식 문서나 사례는 근거지만, 내 프로젝트의 실 동작을 자동으로 보장하지 않음.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| Gradle multi-module이 application/domain과 adapter 의존을 build graph 수준에서 차단한다 | 우아한형제들/카카오뱅크 사례는 구조 사례이며 ca-tmpl build.gradle이 아직 없음 | `./gradlew verifyCleanArchitectureDependencies`. `application-core``adapter-*`에 의존하면 실패 | `locally-verified` |
| `domain-core`가 framework-neutral POJO로 유지된다 | domain module에 Spring/JPA annotation이 들어오는 순간 Clean Architecture 경계가 약해짐 | ArchUnit: `domain-core`에서 `org.springframework..`, `jakarta.persistence..`, `javax.persistence..` import 금지 | `locally-verified` |
| `application-core`가 outbound 구현체가 아닌 port interface만 사용한다 | multi-module이어도 project dependency를 잘못 열면 adapter 구현체 직접 호출이 가능 | ArchUnit + Gradle: `application-core` -> `adapter-*` dependency 금지. 현재 production `application-core`는 anchor 중심이며, reference repository port는 `sample-portfolio/domain/repository`에 격리됨 | `locally-verified` |
| `shared-contract`가 business common으로 오염되지 않는다 | shared module은 커지기 쉬워 domain concept가 흘러들 위험이 있음 | ArchUnit package rule: `shared`에는 response/error/header/logging/tracing/metrics/registry/annotation만 허용. 현재는 package anchor만 존재 | `locally-verified-empty-anchor` |
| `sample-portfolio`이 production module로 역수입되지 않는다 | sample은 fixture이지만 편의상 production에서 import할 위험이 있음 | Gradle dependency rule + ArchUnit: production modules must not depend on `sample-portfolio` | `locally-verified` |
| Spring Modulith를 도입하지 않아도 최소 module boundary 검증이 가능하다 | Modulith verifier를 쓰지 않으면 public API/named interface 검증이 약할 수 있음 | 1차는 Gradle dependency + ArchUnit으로 검증. named interface/public API 검증은 Spring Modulith 없이 아직 약함 | `locally-verified-minimum-boundary` |
## 테스트 계약
- `domain-core`가 Spring / JPA / HTTP DTO / adapter module type을 참조하면 실패.
- `application-core``adapter-web`, `adapter-persistence`, `adapter-outbound`, `app-bootstrap`에 의존하면 실패.
- adapter module끼리 직접 의존하면 실패. 공유가 필요하면 application port 또는 shared operational contract로 승격해야 함.
- production module이 `sample-portfolio`을 import하거나 dependency로 선언하면 실패.
- `shared-contract`에 business/domain package 또는 domain-specific class가 추가되면 실패.
- 새 도메인 기능의 module 위치를 Default Module Blueprint로 판정할 수 없으면 review 실패.
## 완료 후 wiki 추출 대상
- [[wiki/projects/ca-tmpl/clean-architecture-package-layout]]의 skeleton package/module blueprint canonical section.
## 구현 결과
### D9 — `api` vs `implementation` 정책
- 결정: 모듈 간 의존은 기본 `implementation`. 소비자의 public ABI 가 다른 모듈 타입을 노출할 때만 `api`.
- 구현: `CLAUDE.md` (root) §"Gradle `api` vs `implementation` policy" 에 명시. 현재 ca-tmpl 의 모든 `*/build.gradle``implementation` 사용 — 별도 코드 변경 없이 정책 충족 (`actually-implemented`).
- 검증: `cd src && ./gradlew verifyCleanArchitectureDependencies` 통과 + `./gradlew check` 통과.
- 잔여: port interface design 완료 후 `api` 가 필요한 모듈이 등장하면 build.gradle 갱신 + 사용 사례를 본 brunch 의 후속 메모로 기록.
### D10 — `@SpringBootApplication` root package 배치
- 결정: `dev.caskeleton.bootstrap` 에 배치. default package 사용 금지.
- 구현: `CaSkeletonApplication``dev.caskeleton.bootstrap` package 에 있음 — 충족 (`actually-implemented`).
- 추가 설정: `@SpringBootApplication(scanBasePackages = "dev.caskeleton")` 으로 다른 모듈 (sample-portfolio 포함) 의 component 도 scan 가능. component scan default base package 가 `dev.caskeleton.bootstrap` 이지만 multi-module 구조라서 `scanBasePackages` 명시.
- 검증: `cd src && ./gradlew bootRun` 시 sample-portfolio 의 Spring component 가 자동 등록되는지 확인 (별도 integration test 미수행, `documented-only`).
## 마주친 문제
> 짧은 메모만 둔다. 깊이 있는 트러블슈팅은 `raw/errors/` 로 분리하고 아래 Cluster에 연결한다.
- 2026-05-27: B안 구현 중 빈 anchor module은 ArchUnit 검사 대상 class가 없어 empty should failure가 발생했다.
- 원인: skeleton production package가 비어 있는 것이 의도된 상태인데 rule이 empty state를 허용하지 않았다.
- 해결: 빈 anchor가 유효한 rule에만 `allowEmptyShould(true)`를 적용했다.
- 별도 에러 노트로 분리됨: [[raw/errors/archunit-empty-should-anchor-2026-05-27]]
- 2026-05-27: reference code를 `sample-portfolio`으로 격리한 뒤 `InvalidBearerTokenException` compile error가 발생했다.
- 원인: sample module에 `spring-boot-starter-oauth2-resource-server` dependency가 없었다.
- 해결: `sample-portfolio/build.gradle`에 resource-server starter를 추가했다.
- 별도 에러 노트로 분리됨: [[raw/errors/sample-portfolio-oauth2-resource-server-dependency-2026-05-27]]
- 2026-05-27: reference blog의 repository port는 아직 branch-note blueprint의 `application/port/out`이 아니라 `sample-portfolio/domain/repository`에 남아 있다. 이는 reference implementation 격리를 우선한 B안 범위의 잔여 차이이며, production use case port 정리는 `feature-application-port-usecase-contract` branch에서 수행한다.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: sources:start -->
- [[raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature]]
- [[raw/company-tech-blogs/hexagonal-woowahan-techblog-2023]]
- [[raw/company-tech-blogs/layer-first-kamilmazurek-github-template]]
- [[raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring]]
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]]
- [[raw/company-tech-blogs/onion-allegro-tech-blog-2023]]
- [[raw/official-docs/adapter-java-spi-serviceloader]]
- [[raw/official-docs/adapter-spring-boot-autoconfig-custom-starter]]
- [[raw/official-docs/arch-clean-architecture-uncle-bob]]
- [[raw/official-docs/domain-fowler-anemic-vs-rich-model]]
- [[raw/official-docs/domain-vaughn-vernon-aggregate-root]]
- [[raw/official-docs/dx-devcontainer-spring-boot]]
- [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]]
- [[raw/official-docs/gradle-java-library-api-vs-implementation]]
- [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]]
- [[raw/official-docs/hexagonal-thombergs-buckpal-github]]
- [[raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot]]
- [[raw/official-docs/modulith-spring-official-doc]]
- [[raw/official-docs/onion-palermo-original-2008]]
- [[raw/official-docs/spring-boot-structuring-your-code]]
<!-- GENERATED: sources:end -->
<!-- GENERATED: interviews:start -->
- [[raw/interviews/clean-architecture-module-blueprint]]
- [[raw/interviews/shared-contract-and-sample-isolation]]
<!-- GENERATED: interviews:end -->
<!-- GENERATED: errors:start -->
- [[raw/errors/archunit-empty-should-anchor-2026-05-27]]
- [[raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27]]
<!-- GENERATED: errors:end -->
<!-- GENERATED: daily-notes:start -->
- [[raw/daily-notes/2026-05-27]]
<!-- GENERATED: daily-notes:end -->
<!-- GENERATED: blog-topics:start -->
- [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]]
<!-- GENERATED: blog-topics:end -->
> 이 branch는 package/module skeleton blueprint의 entry point다. 자식 branch는 없지만, local implementation 중 발생한 error note와 면접 준비 raw note는 아래에 명시적으로 묶는다.
### Sub-branches (세부 작업)
- (없음 — project 직접 자식 branch이며 하위 branch 없음)
### 근거 자료
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]]
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]]
- [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]]
- [[raw/official-docs/arch-clean-architecture-uncle-bob]]
- [[raw/official-docs/modulith-spring-official-doc]]
- [[raw/official-docs/gradle-java-library-api-vs-implementation]] — `api` vs `implementation` 선언 정책의 Gradle 공식 근거
- [[raw/official-docs/spring-boot-structuring-your-code]] — `@SpringBootApplication` root package 배치 및 component scan default base package 정책 공식 근거
### 오류 기록 (이 branch 작업 중 발생)
- [[raw/errors/archunit-empty-should-anchor-2026-05-27]] — 빈 skeleton anchor package가 ArchUnit empty should failure로 처리된 문제.
- [[raw/errors/sample-portfolio-oauth2-resource-server-dependency-2026-05-27]] — sample-portfolio 격리 후 OAuth2 resource-server dependency 누락으로 compile 실패한 문제.
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- [[raw/interviews/clean-architecture-module-blueprint]] — 왜 단일 모듈 package 구조 대신 Gradle multi-module skeleton을 선택했는가.
- [[raw/interviews/shared-contract-and-sample-isolation]] — `shared-contract``sample-portfolio`의 책임을 production domain과 왜 분리했는가.
### 강의 (이 작업을 위해 학습한 강의)
- (없음 — 이번 branch는 official-doc/company-tech-blog raw 근거 기반이며 별도 lecture note 없음)
### job-posting tie-ins (이 작업에서 파생된 글감)
- [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]] — Clean Architecture skeleton의 Gradle multi-module package blueprint와 sample-portfolio 격리에서 파생된 블로그 글감.
- job-posting tie-ins: (없음)
## 관련 일일 노트
- [[raw/daily-notes/2026-05-27]] — skeleton package/module blueprint 구현 및 local verification.
- [[raw/daily-notes/2026-05-28]] — 후속 architecture enforcement 착수 전 blueprint 문서 정합성 점검.
## Ground-truth 대조
> ca-tmpl 실제 레포(`/home/donghyeon/workspace/ca-tmpl` @ `5d89766`)와 대조하여 `status: raw → verified` 승급. 근거: actual code + passing test. 등급은 `locally-verified` 유지(운영 배포·로그 없음).
| 주장 | ca-tmpl 실재 증거 | 판정 |
|---|---|---|
| D1 multi-module 8개 | `settings.gradle` include 8개 일치 | ✅ |
| D9 전 module `implementation`, `api` 0개 | 9개 `build.gradle` 모두 `api` 선언 없음 | ✅ |
| D10 `CaSkeletonApplication` @ `dev.caskeleton.bootstrap` + `scanBasePackages="dev.caskeleton"` | `app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java` | ✅ |
| `verifyCleanArchitectureDependencies` task | root `build.gradle:53` 등록 | ✅ |
| `CleanArchitectureTest` | `app-bootstrap/.../architecture/CleanArchitectureTest.java` | ✅ |
| anchor `package-info.java` | domain-core·shared-contract·adapter-* 존재 | ✅ |
| sample reference 격리 | `dev.caskeleton.sample.portfolio.*.worklog` | ✅ |
**관찰된 drift (이 branch 결정 범위 밖 — 추출 시 보정):**
- **Drift① — 9번째 module `adapter-identifier`**: 실제 `settings.gradle`에는 blueprint 8개 + `adapter-identifier`가 있음. 본 branch 결정이 아니라 후속 `feature-resource-identifier-contract`(commit `c36b764`)가 추가. blueprint 결정으로 흡수하지 않고 OUT_OF_BRANCH_SCOPE로 기록. canonical 블루프린트 추출 시 "adapter module은 책임별로 확장 가능(예: `adapter-identifier`)"으로만 각주.
- **Drift② — sample package 경로**: blueprint tree는 `{basePackage}/sample/worklog/`이나 실제는 `dev.caskeleton.sample.portfolio.{domain,application}.worklog`(중간 `portfolio.` 한 단계 추가). 계획 대비 구현 divergence. **canonical 추출 시 실제 경로 사용.**
## 진행 중 메모
- module blueprint와 dependency rule의 적용 상태는 구현 결과 및 ground-truth 대조 절에서 추적한다.
## 구현 가이드
- `domain-core`·`application-core`·`adapter-*`·`shared-contract`·`app-bootstrap`의 책임을 Gradle module과 package 양쪽에 고정한다.
- 허용 dependency는 한 방향으로만 선언하고 forbidden fixture가 architecture gate에서 실패해야 한다.
- 신규 domain onboarding은 blueprint를 복사하지 않고 이 문서의 module 책임을 참조한다.
## 엣지·실패·의존
- 순환 module dependency·bootstrap 역참조·shared-contract의 구현 의존 유입은 build 또는 architecture test에서 차단한다.
- onboarding·application port·architecture enforcement 계약이 본 blueprint를 소비한다.
## 완료 후 정리
> 2026-05-27 local implementation 기준 정리. 원격 PR/머지는 이 세션에서 수행하지 않음.
- PR 링크: (미생성 — local branch `feature/skeleton-package-blueprint-contract`)
- 리뷰 메모: Gradle module rename, package anchor, ArchUnit/Gradle boundary rule, README/agent rule update, `dev.caskeleton` skeleton package rename, sample-portfolio reference 격리까지 B안 범위로 반영.
- 머지 결과 / 배포 환경: 미머지, 미배포. ca-tmpl template local verification만 수행.
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목:
- `settings.gradle` include가 `app-bootstrap`, `domain-core`, `application-core`, `adapter-web`, `adapter-persistence`, `adapter-outbound`, `shared-contract`, `sample-portfolio`로 전환됨.
- production package root가 `dev.caskeleton`로 전환되고 `BlogApplication``CaSkeletonApplication`, `CmdSettings``BootstrapSettings`, `blog.*` 설정 prefix는 `ca-skeleton.*`로 전환됨.
- 기존 reference code는 production module에서 `sample-portfolio` 내부 `dev.caskeleton.sample.worklog.*` package로 격리됨.
- `domain-core`, `application-core`, `adapter-persistence`, `adapter-outbound`, `shared-contract`는 skeleton anchor package와 `package-info.java` 중심으로 유지됨.
- `AGENTS.md`, `CLAUDE.md`, module `CLAUDE.md`, README가 새 module vocabulary로 갱신됨.
- `locally-verified` 항목:
- `./gradlew verifyCleanArchitectureDependencies` 통과.
- `./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` 통과.
- `./gradlew :adapter-web:test --tests '*SettingsTest'` 통과.
- `./gradlew test` 통과.
- `prod-verified` 항목:
- 없음. ca-tmpl은 template repository이며 운영 배포/운영 로그 검증 없음.
- **추출하지 않을 항목** (planned / documented-only / abandoned):
- Spring Modulith named interface 검증 도입은 후속 검토 후보.
- `application/port/in`, `application/port/out`로 reference blog port를 완전히 재배치하는 작업은 `feature-application-port-usecase-contract` branch에서 수행.
- `sample-portfolio` 실제 worklog fixture 구현은 후속 sample fixture branch에서 수행.