34 KiB
title, source_type, status, branch, related_projects, tags, created, last_reviewed, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, parent_branch, contract_packet_sha256
| title | source_type | status | branch | related_projects | tags | created | last_reviewed | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | parent_branch | contract_packet_sha256 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-skeleton-package-blueprint-contract | branch-note | verified | feature-skeleton-package-blueprint-contract |
|
|
2026-05-22 | 2026-06-04 | locally-verified | BR-CA-SKELETON-OPERATIONAL-CONTRACT-040 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-040 |
|
1 | 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
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 모두에 적용 |
| 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) §"Gradleapivsimplementationpolicy" 에 명시. 현재 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.bootstrappackage 에 있음 — 충족 (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으로 격리한 뒤InvalidBearerTokenExceptioncompile error가 발생했다.- 원인: sample module에
spring-boot-starter-oauth2-resource-serverdependency가 없었다. - 해결:
sample-portfolio/build.gradle에 resource-server starter를 추가했다. - 별도 에러 노트로 분리됨: raw/errors/sample-portfolio-oauth2-resource-server-dependency-2026-05-27
- 원인: sample module에
- 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-contractbranch에서 수행한다.
묶음 (이 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
이 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 —
apivsimplementation선언 정책의 Gradle 공식 근거 - raw/official-docs/spring-boot-structuring-your-code —
@SpringBootApplicationroot 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(commitc36b764)가 추가. 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.caskeletonskeleton package rename, sample-portfolio reference 격리까지 B안 범위로 반영. - 머지 결과 / 배포 환경: 미머지, 미배포. ca-tmpl template local verification만 수행.
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:settings.gradleinclude가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, moduleCLAUDE.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-contractbranch에서 수행.sample-portfolio실제 worklog fixture 구현은 후속 sample fixture branch에서 수행.