--- 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를 정의합니다. ## 부모 (필수) - **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]] > ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다. ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: Gradle module graph가 declared layout과 일치한다 ### 상속한 프로젝트 결정 | 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]] | ### 브랜치 지역 결정 > 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다. | Decision ID | Decision | Relation | Supporting Claims | Status | |---|---|---|---|---| ### 선언한 예외 | Override ID | Overrides | Reason | Approval | Status | |---|---|---|---|---| ## 목표 좋은 원칙이 있어도 module boundary와 package 위치를 함께 고정하지 않으면 구현자는 자기 방식으로 구조를 만듭니다. 이 branch는 Gradle multi-module을 1차 경계로 두고, 각 module 내부 package 책임을 Clean Architecture / Hexagonal 규칙에 맞게 고정해 실제 도메인 기능이 바로 들어올 수 있게 합니다. - 이슈: - PR: (local branch only; remote PR not created in this session) ## 범위 ### 포함 범위 - 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에서 파생된 자료) - [[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]] - [[raw/interviews/clean-architecture-module-blueprint]] - [[raw/interviews/shared-contract-and-sample-isolation]] - [[raw/errors/archunit-empty-should-anchor-2026-05-27]] - [[raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27]] - [[raw/daily-notes/2026-05-27]] - [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]] > 이 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에서 수행.