427 lines
45 KiB
Markdown
427 lines
45 KiB
Markdown
---
|
|
title: branch / feature-domain-feature-onboarding-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-034
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-034
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
branch: feature-domain-feature-onboarding-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton]
|
|
governing_docs: [wiki/projects/ca-tmpl/clean-architecture-package-layout, wiki/projects/ca-tmpl/sample-fixture-and-adoption]
|
|
tags: [branch, ca-skeleton, domain-onboarding, module-boundary, clean-architecture]
|
|
created: 2026-05-22
|
|
target_merge:
|
|
status_label: in-progress
|
|
contract_packet_sha256: 80c4d9f23b6ee00310f6c605ffe62bf8caaec262afa9719ecf7fda9c724fed83
|
|
---
|
|
|
|
# branch: feature-domain-feature-onboarding-contract
|
|
|
|
> Layer: `raw/branch-notes/` — 실제 도메인 기능을 skeleton에 얹을 때 따라야 하는 multi-module onboarding 기준을 정의합니다.
|
|
|
|
<!-- 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 의 운영 계약 중 domain onboarding / sample adoption / implementation readiness 영역을 정제한다.
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: 신규 domain slice가 module·test checklist를 통과한다
|
|
|
|
<!-- 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 책임을 분리한다 | 신규 domain slice의 module별 배치와 의존 방향에 적용 | [[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 -->
|
|
## 목표
|
|
|
|
이 skeleton은 도메인 로직을 제거하지만, 실제 프로젝트 시작 시 도메인을 바로 얹을 수 있어야 합니다. Phase C2 기본 구조가 Gradle multi-module로 바뀌었으므로, 새 도메인 기능도 단일 `features/{name}` 디렉터리가 아니라 `domain-core`, `application-core`, `adapter-*`, `shared-contract`, `sample-portfolio` 경계 위에 추가되어야 합니다. controller만 추가하거나 repository만 추가하는 식으로 경계가 무너지지 않도록 최소 onboarding slice를 고정합니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- 새 도메인 기능 추가 시 module별 최소 변경 기준.
|
|
- read-only / write use case 차이.
|
|
- `domain-core` / `application-core` / `adapter-web` / `adapter-persistence` / `adapter-outbound` 책임 분리.
|
|
- `shared-contract` 변경이 필요한 조건.
|
|
- `sample-portfolio` 참조/복제/삭제 기준.
|
|
- onboarding dry-run checklist SSOT.
|
|
|
|
### 제외 범위
|
|
|
|
- 특정 비즈니스 도메인 선택.
|
|
- code generator 구현.
|
|
- IDE template 제공.
|
|
- Spring Modulith `@ApplicationModule` 도입.
|
|
- sample-portfolio 실제 scenario 구현. 이 항목은 `feature-sample-domain-contract-fixture`가 owner.
|
|
- sample-off profile / dual-mode CI matrix / removal lifecycle / reference scaffolding. 이 항목은 `feature-sample-removal-adoption-contract`가 owner.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 본 branch의 결정 근거. multi-module 기본값은 company-case-study 근거가 중심이므로, 공식 best practice가 아니라 사례 기반 프로젝트 결정으로 취급한다.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | Phase C2 기본 module boundary와 dependency direction SSOT |
|
|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | onboarding 결과를 Gradle/ArchUnit rule로 검증하는 enforcement 기준 |
|
|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | inbound `*UseCase` / outbound `*Port` 명명, `TransactionPort`, `@UseCaseCapability`, read-only 캡션 contract SSOT |
|
|
| [[raw/branch-notes/feature-sample-domain-contract-fixture]] | sample-portfolio scenario/minimum-model owner (본 branch 는 consume only) |
|
|
| [[raw/branch-notes/feature-sample-removal-adoption-contract]] | sample-off lifecycle / dual-mode CI matrix / removal / reference scaffolding owner (본 branch 는 consume only) |
|
|
| [[raw/branch-notes/feature-resource-identifier-contract]] | 새 entity PK/ID 생성 정책(ULID server-assigned via domain `*IdFactory` port) owner — write slice 가 consume |
|
|
| [[raw/branch-notes/feature-implementation-readiness-scorecard]] | 본 branch 의 dry-run checklist 를 consume 하는 readiness 게이트 |
|
|
| [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] | Domain / Application / Framework / Bootstrap multi-module hexagonal 사례 |
|
|
| [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] | Gradle multi-module + Hexagonal에서 application/adapter 물리 분리와 Port 통신 사례 |
|
|
| [[raw/official-docs/arch-hexagonal-cockburn]] | port와 adapter 분리의 원형 (`engineering-blog`, official standard 아님) |
|
|
| [[raw/official-docs/arch-clean-architecture-uncle-bob]] | Dependency Rule 및 use case 중심 구조 사고 근거 (`engineering-blog`, official standard 아님) |
|
|
| [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]] | framework가 아니라 use case / business 영역이 구조에서 드러나야 한다는 보조 근거 |
|
|
| [[raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature]] | layer-first 대비 feature 응집도 사례. module 내부 package 책임 참고용 |
|
|
|
|
## TODO
|
|
|
|
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
|
|
|
|
- [x] New Domain Module Slice 표를 기준으로 read-only/write onboarding checklist 정의 — 등급: `locally-verified`
|
|
- [x] `domain-core` domain model/rule 추가 기준 정의 — 등급: `locally-verified`
|
|
- [x] `application-core` use case / command-query / port 추가 기준 정의 — 등급: `locally-verified`
|
|
- [x] `adapter-web` DTO / mapper / controller / contract test 추가 기준 정의 — 등급: `locally-verified`
|
|
- [x] `adapter-persistence` entity / repository / mapper / migration 추가 기준 정의 — 등급: `locally-verified`
|
|
- [x] `adapter-outbound` optional adapter 추가 조건 정의 — 등급: `documented-only` (optional 조건만 정의; 이번 dry-run 에 외부 adapter 없음)
|
|
- [x] `shared-contract` 변경 승인 조건 정의 — 등급: `locally-verified`
|
|
- [x] `sample-portfolio`을 import하지 않고 구조만 참조하는 dry-run 검증 정의 — 등급: `locally-verified`
|
|
|
|
## 진행 중 메모
|
|
|
|
- 기존 문서의 `features/{featureName}/{presentation,application,domain,infrastructure}` 기준은 2026-05-28부로 이전 기준으로 내린다.
|
|
- 새 기본값은 module-first onboarding이다. 같은 도메인 기능의 파일이 여러 module에 생기더라도 dependency direction이 유지되면 정상이다.
|
|
- onboarding checklist는 실제 code generator가 아니라 review/build 기준이다.
|
|
- 2026-06-15 (branch-spec): 본 노트의 추상 모델(`*QueryUseCase`, "transaction/idempotency/capability declaration")은 그 이후 ca-tmpl 에서 `@UseCaseCapability` + `@RequiresPermission` + `TransactionPort` 로 구체화됐다. §구현 가이드가 이 실제 메커니즘을 anchor 로 쓰고, drift 는 §Audit & Findings 에 기록한다. capability 어휘 자체의 owner 는 본 branch 가 아니라 `feature-application-port-usecase-contract` + `feature-repository-access-permission-contract`(capabilities.yaml).
|
|
- 2026-06-25 (implementation): `app-bootstrap` ArchUnit/JUnit 테스트에 `DomainFeatureOnboardingContractTest`와 test-only `dev.caskeleton.onboarding.*` FeatureAggregate dry-run slice를 추가해 read-only/write onboarding 성공 경로를 검증했다. `CleanArchitectureTest`에는 repository-backed `@UseCaseCapability`가 대응 `TransactionPort` 경계(`inRead`/`inWrite`/`inNew`)를 직접 호출하는지 검사하는 rule을 추가했다.
|
|
- 2026-06-25 (cleanup): dry-run fixture 이름을 `Ticket`에서 `FeatureAggregate`로 바꿨다. 이유: app-bootstrap test fixture가 특정 업무 도메인을 skeleton production concept처럼 보이게 만들 수 있어, 온보딩 계약용 중립 명칭으로 정리했다.
|
|
- 2026-06-25 (cleanup): onboarding positive fixture 파일을 `src/app-bootstrap/src/test/java/dev/caskeleton/onboarding/` 아래로 이동해 package 선언(`dev.caskeleton.onboarding.*`)과 파일 경로를 일치시켰다. 이유: `bootstrap/architecture/allowed/onboarding` 경로와 synthetic package가 어긋나 `sampleOffTest` 컴파일과 IDE 해석에서 혼선을 만들었기 때문이다.
|
|
|
|
## 결정 사항
|
|
|
|
- 2026-05-22: 초기 문서의 onboarding 기준은 feature-first package slice였다.
|
|
- 2026-05-28: onboarding 기준을 Gradle multi-module slice로 수정한다. / 이유: Phase C2 기본 구조가 `domain-core` / `application-core` / `adapter-*` / `shared-contract` / `app-bootstrap` / `sample-portfolio`로 바뀜. / 근거: [[raw/branch-notes/feature-skeleton-package-blueprint-contract]].
|
|
- 2026-05-28: 새 도메인 기능의 기본 흐름은 domain model/rule → application use case/port → adapter-web/persistence/outbound 구현 → contract/architecture test 순서로 둔다. / 이유: 안쪽 module이 바깥 adapter를 알지 않게 하기 위함. / 근거: [[raw/official-docs/arch-clean-architecture-uncle-bob]], [[raw/official-docs/arch-hexagonal-cockburn]].
|
|
- 2026-05-28: read-only feature는 write command, idempotency, outbox, persistence mutation을 생략할 수 있다. 단 query use case, inbound port, response mapper, contract test는 필수다. / 근거: `project-decision`.
|
|
- 2026-05-28: write feature는 command, use case, outbound persistence port, transaction/idempotency decision, persistence adapter, contract test를 함께 추가해야 한다. / 근거: [[raw/branch-notes/feature-application-port-usecase-contract]].
|
|
- 2026-05-28: `shared-contract` 변경은 response/error/header/logging/tracing/metrics/registry/annotation 같은 skeleton-wide contract일 때만 허용한다. 도메인 전용 타입은 `domain-core` 또는 adapter DTO에 둔다. / 근거: [[raw/branch-notes/feature-skeleton-package-blueprint-contract]].
|
|
- 2026-05-28: `sample-portfolio`은 import 대상이 아니라 구조 참고 fixture다. production module이 sample-portfolio을 dependency로 선언하면 실패해야 한다. / 근거: [[raw/branch-notes/feature-architecture-enforcement-rules]].
|
|
- 2026-05-28: dry-run checklist SSOT = 본 branch의 New Domain Module Slice + Read/Write Difference Table. `feature-implementation-readiness-scorecard`는 consume only로 둔다. / 근거: `project-decision`.
|
|
- 2026-06-25: onboarding checklist는 문서 표만이 아니라 `DomainFeatureOnboardingContractTest`의 read-only/write FeatureAggregate dry-run fixture와 ArchUnit negative fixture로 검증한다. / 이유: controller-only 또는 transaction-less write 같은 누락을 리뷰 기억이 아니라 테스트 실패로 잡기 위함. / 검토한 대안: README 체크리스트만 유지. / 근거: `project-decision` + 로컬 검증(`./gradlew test`).
|
|
|
|
## New Domain Module Slice
|
|
|
|
| Module | Read-only feature | Write feature | Forbidden |
|
|
|---|---|---|---|
|
|
| `domain-core` | query response에 필요한 domain model / value object only as needed | aggregate/entity/value object/domain rule/domain event as needed | Spring/JPA/HTTP DTO/import, adapter type import |
|
|
| `application-core` | query object, `*UseCase` inbound port, read outbound port if persistence needed, read-only use case | command object, `*UseCase` inbound port, outbound port, use case, transaction/idempotency/capability declaration | adapter implementation import, Spring Web/JPA implementation API, direct `@Transactional` |
|
|
| `adapter-web` | request params/response DTO, mapper, controller, validation error mapping, contract test | request DTO, response DTO, mapper, controller, validation, idempotency/header handling, contract test | domain object direct response, persistence adapter direct call |
|
|
| `adapter-persistence` | read entity/projection/repository/mapper only if DB read is needed | entity/repository/mapper/migration/write adapter implementation | controller/web DTO import, application use case import beyond port implementation |
|
|
| `adapter-outbound` | optional; only if read use case calls external dependency | optional HTTP/messaging/cache/notification adapter implementation | direct adapter-to-adapter coupling |
|
|
| `shared-contract` | normally no change | only if new skeleton-wide response/error/header/log/metric/registry contract is required | domain-specific type, business enum, feature-specific DTO |
|
|
| `app-bootstrap` | bean wiring/profile update only when needed | bean wiring/profile update only when needed | domain policy implementation |
|
|
| `sample-portfolio` | reference only; no production dependency | reference only; no production dependency | production module import/dependency |
|
|
|
|
## Read/Write Difference Table
|
|
|
|
| Slice item | Read-only | Write |
|
|
|---|---|---|
|
|
| inbound port | `*QueryUseCase` or query-specific `*UseCase` | command-specific `*UseCase` |
|
|
| input model | query object or request parameters mapped in adapter | command object |
|
|
| outbound port | read port only when persistence/external read needed | write port required when persistence/external mutation needed |
|
|
| transaction | `readOnly` decision if DB read exists | `required` decision; propagation/isolation explicit when non-default |
|
|
| idempotency | normally N/A | required decision for retryable external command / create command |
|
|
| domain model/rule | as needed | required when invariant or state transition exists |
|
|
| adapter-web test | response/validation contract | response/validation/idempotency/header contract |
|
|
| persistence test | query mapping if DB read exists | mutation/rollback/constraint mapping |
|
|
| architecture test | module boundary + no sample dependency | module boundary + no sample dependency + transaction/capability rule |
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. `Decision ID` 는 이 branch-note 안에서 안정적으로 유지한다.
|
|
> `선택 조건` 열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면 `N/A`.
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | onboarding 기준은 Gradle multi-module slice | Phase C2 multi-module skeleton 기준일 때. 학습/예제용 single-module 축소형이면 [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] D8 의 responsibility-mapping 보존 변환표로 대체 | `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C1`, `raw/company-tech-blogs/modulith-kakaobank-techblog-2025.md#KAKAOBANK-MOD-C4`, `raw/branch-notes/feature-skeleton-package-blueprint-contract.md` | `company-case-study + project-decision` | company-tech-blog 사례를 공식 표준으로 승격 금지. ca-tmpl dry-run으로 별도 검증 필요 |
|
|
| D2 | domain -> application -> adapter 방향으로 추가 | N/A (모든 새 도메인 기능 — inner module 이 outer adapter 를 알지 않게) | `raw/official-docs/arch-clean-architecture-uncle-bob.md`, `raw/official-docs/arch-hexagonal-cockburn.md#HEX-COCKBURN-ORIG-C3`, `raw/company-tech-blogs/woowahan-hexagonal-multimodule.md#WW-HEX-C2` | `engineering-blog + company-case-study` | 구체 file set은 ca-tmpl 자체 결정 |
|
|
| D3 | read-only feature는 write/idempotency/outbox 생략 가능 | query-only feature(DB/외부 상태 mutation 없음)일 때 생략. mutation 발생 시 D4 | `project-decision`; `raw/branch-notes/feature-application-port-usecase-contract.md` (D9: read-only query use case = `readOnly` tx + `READ_REPOSITORY` capability) | `project-decision + sibling-branch-decision` | read-only 기준이 모호하면 기능별 임의 판단이 생길 수 있음 |
|
|
| D4 | write feature는 command/use case/port/persistence/transaction/idempotency decision을 함께 요구 | state mutation / persistence write 가 있을 때. read-only면 D3 | `raw/branch-notes/feature-application-port-usecase-contract.md` (D1 `*UseCase`/`*Port`, D3 `TransactionPort`, D14 idempotency 게이트) | `project-decision + sibling-branch-decision` | TransactionPort 세부 옵션은 아직 `needs-confirmation` 항목이 남아 있음. idempotency=KEYED 는 [[raw/branch-notes/feature-rate-limit-idempotency-contract]] merge 전까지 금지([[raw/branch-notes/feature-application-port-usecase-contract]] D14) |
|
|
| D5 | `shared-contract`는 skeleton-wide operational contract만 허용 | 새 계약이 skeleton-wide(response/error/header/log/tracing/metrics/registry/annotation)일 때만 변경. domain-specific 타입이면 domain-core 또는 adapter DTO | `raw/branch-notes/feature-skeleton-package-blueprint-contract.md`, `raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011.md#SCREAM-C1` | `project-decision + engineering-blog` | shared module이 common dumping ground가 될 위험 |
|
|
| D6 | `sample-portfolio`은 구조 참고 fixture이며 production dependency 금지 | N/A (항상 — production module 의 sample-portfolio dependency 금지) | `raw/branch-notes/feature-architecture-enforcement-rules.md` (D7 `production_code_does_not_depend_on_sample_portfolio` ArchUnit rule), `raw/branch-notes/feature-skeleton-package-blueprint-contract.md` | `project-decision (ArchUnit-enforced)` | 외부 직접 근거는 약함. Gradle/ArchUnit failure로 실증 필요 |
|
|
| D7 | readiness scorecard는 본 branch checklist를 consume only | N/A (항상 — dry-run checklist SSOT 는 본 branch; scorecard 는 consume) | `project-decision`; `raw/branch-notes/feature-implementation-readiness-scorecard.md` (D5: real-domain dry-run checklist 가 onboarding branch 를 consume) | `project-decision + sibling-branch-decision` | scorecard branch가 자체 checklist를 유지하면 SSOT 충돌 발생 |
|
|
| D8 | onboarding checklist는 executable dry-run fixture + ArchUnit negative fixture로 검증 | ca-tmpl template branch 에서 새 도메인 온보딩 계약을 release-blocking guardrail 로 다룰 때. 단순 문서 안내만 필요한 fork 에서는 문서 체크리스트로 축소 가능 | `UNSUPPORTED_DECISION` — source 는 port/adapter 분리 원칙을 말하지만 test fixture 방식은 ca-tmpl 구현 선택; supporting project evidence: `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java`, `CleanArchitectureTest.java` | `project-decision + locally-verified` | ArchUnit 정적 분석은 direct call 만 확인한다. helper 로 숨긴 transaction boundary 는 code review concern |
|
|
|
|
## 구현 가이드
|
|
|
|
> *결정 (Decisions)* 이 "*무엇* 을 할 것인가" 라면, 본 §는 "*어디에 어떻게* 구현될 것인가" 의 사전 명세. 본 branch 는 *통합/소비자 계약* 이므로, 각 slice 의 mechanism owner 는 sibling branch 에 있고 여기서는 **새 도메인 기능을 얹을 때 module 별로 어떤 파일을 어디에 추가하는가**를 고정한다.
|
|
>
|
|
> **Anchor 출처**: 모든 경로/클래스/rule 명은 `/home/donghyeon/workspace/ca-tmpl` @ HEAD 의 실제 코드에서 확인(2026-06-15 branch-spec ground-truth read). 코드 미확인 항목은 `planned` 로 표기.
|
|
|
|
### 1. New domain feature placement & dependency direction
|
|
|
|
> **Trace**: D1(multi-module slice) + [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] D1~D5; D2(domain→application→adapter) + [[raw/branch-notes/feature-architecture-enforcement-rules]] D1~D7. 루트 패키지 `dev.caskeleton.*`.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — module/package 배치와 dependency 방향은 blueprint/enforcement sibling 이 결정·강제(`actually-implemented`).
|
|
|
|
새 도메인 기능 `<X>` 추가 시 module 별 anchor (모두 `locally-verified` — ArchUnit/Gradle task 가 강제):
|
|
|
|
| Module | 추가 위치 (package) | 명명 | 강제 rule (CleanArchitectureTest / Gradle) |
|
|
|---|---|---|---|
|
|
| `domain-core` | `dev.caskeleton.domain.<x>.{model,vo,event,service}` | `@AggregateRoot`/`@ValueObject`/`@DomainEvent` (`dev.caskeleton.domain.stereotype`, record) | `domain_is_pure`, `domain_has_no_logger`, `value_objects_have_no_public_no_arg_constructor`, `aggregate_root_setters_are_not_public`, `domain_events_are_records` |
|
|
| `application-core` | `dev.caskeleton.application.{usecase,command,query}` (+ outbound `*Port` interface) | inbound `*UseCase`, outbound `*Port` (application-port D1) | `inbound_port_implementations_end_with_use_case`, `inbound_port_implementations_declare_capability`, `application_does_not_depend_on_adapters_or_transport` |
|
|
| `adapter-web` | `dev.caskeleton.adapter.web.{controller,dto,mapper}` | `*Controller`(returns `Envelope<T>`), `*Request`/`*Response` DTO | `controllers_do_not_return_domain_or_entity_types`, `web_dtos_stay_in_web_adapter`, `application_methods_do_not_accept_web_dtos`, `request_dtos_do_not_silence_unknown_fields` |
|
|
| `adapter-persistence` | `dev.caskeleton.adapter.persistence.<x>.{entity,*JpaRepository,mapper}` + `src/main/resources/db/migration/V<n>__<x>.sql` (Flyway) | `*Entity`(extends `AuditableEntity`), `*JpaRepository` | `persistence_adapter_does_not_depend_on_web_or_outbound_adapters` |
|
|
| `adapter-outbound` | `dev.caskeleton.adapter.outbound.<x>.*` (optional) | `*Adapter` implementing application `*Port` | `outbound_adapter_does_not_depend_on_web_or_persistence_adapters`, `outbound_adapter_method_returns_only_domain_or_primitives` |
|
|
| 전 module dependency edge | — | — | Gradle task `verifyCleanArchitectureDependencies` (`src/build.gradle:54-92`, `allowedProjectDependencies` 화이트리스트) |
|
|
|
|
### 2. Read-only onboarding slice
|
|
|
|
> **Trace**: D3 + [[raw/branch-notes/feature-application-port-usecase-contract]] D9 (read-only query use case = `readOnly` tx + `READ_REPOSITORY` capability). 추상 Read/Write Difference Table 의 read-only 열을 실제 메커니즘으로 고정.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: read 가 DB 를 전혀 안 탈 때 outbound read port 자체를 생략할지 — capability `NONE` vs `READ_REPOSITORY` 선택은 기능별 trade-off(persistence 의존 0 이면 `NONE`). application-port 가 *원칙*만 권고하고 feature 별 detail 은 권고 안 함.
|
|
|
|
필수 파일 (이 중 하나라도 빠지면 review/build 실패):
|
|
|
|
| 추가물 | 위치/형태 | 비고 |
|
|
|---|---|---|
|
|
| Query 객체 | `application/query/<X>Query.java` implements `Query` (marker) | immutable record |
|
|
| inbound port | `application/usecase/<X>QueryUseCase.java` implements `QueryUseCase<Q,R>` | 이름 `...UseCase` 로 끝나야 함 (rule `inbound_port_implementations_end_with_use_case`) |
|
|
| capability | `@UseCaseCapability(transactionMode = READ_ONLY, repositoryAccess = READ_REPOSITORY \| NONE, idempotency = NOT_IDEMPOTENT)` | **필수 annotation** (rule `inbound_port_implementations_declare_capability`); 경로 `application/capability/UseCaseCapability.java` |
|
|
| (선택) read outbound port | `application/.../<X>ReadPort.java` (`*Port`) | DB/외부 read 필요 시에만 |
|
|
| response mapper + controller | `adapter/web/mapper/<X>ResponseMapper`, `adapter/web/controller/<X>Controller` (`Envelope<T>` 반환) | domain object 직접 반환 금지 |
|
|
| contract test | `app-bootstrap/src/test/.../contract/<X>...Test`; sample 참조 `sample-portfolio/.../WorkLogControllerWireTest`·`ListRecentWorkLogSummariesUseCaseTest` | 최소 assert: HTTP 200 + `Envelope<T>.data` 매핑 + unknown-field 거부(rule `request_dtos_do_not_silence_unknown_fields`) + domain object 직접 노출 없음 |
|
|
|
|
**생략 가능 (read-only)**: `Command`, idempotency store/executor, outbox, persistence write adapter, `@RequiresPermission`, `TransactionPort.inWrite`.
|
|
|
|
### 3. Write onboarding slice
|
|
|
|
> **Trace**: D4 + [[raw/branch-notes/feature-application-port-usecase-contract]] D1(`*UseCase`/`*Port`)·D3(`TransactionPort`)·D9·D14(idempotency 게이트). 예시 실증: `sample-portfolio/.../application/worklog/CreateWorkLogUseCase.java` (`@UseCaseCapability(transactionMode = WRITE, idempotency = NOT_IDEMPOTENT, repositoryAccess = WRITE_REPOSITORY)`).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: ① idempotency 모드(`IDEMPOTENT` vs `KEYED`) — **`KEYED` 는 [[raw/branch-notes/feature-rate-limit-idempotency-contract]] merge 전까지 금지**([[raw/branch-notes/feature-application-port-usecase-contract]] D14), 그 전엔 `NOT_IDEMPOTENT`/`IDEMPOTENT` 만. ② transaction 격리/전파 비기본값 — `inWrite`(기본) vs `inNew`(outbox/audit/보상 전용); 비기본 propagation 은 기능별 trade-off 이며 application-port D12(`inNew` = 새 JDBC connection, loop 호출 금지)를 따른다.
|
|
|
|
필수 파일 (write):
|
|
|
|
| 추가물 | 위치/형태 | 강제 rule |
|
|
|---|---|---|
|
|
| Command 객체 | `application/command/<X>Command.java` implements `Command` | immutable record |
|
|
| inbound port | `application/usecase/<X>UseCase.java` implements `CommandUseCase<C,R>` | `inbound_port_implementations_end_with_use_case` |
|
|
| capability | `@UseCaseCapability(transactionMode = WRITE, repositoryAccess = WRITE_REPOSITORY, idempotency = ...)` | `inbound_port_implementations_declare_capability` |
|
|
| permission | `@RequiresPermission(...)` (`application/security/RequiresPermission.java`) | `mutating_use_cases_declare_required_permission` (WRITE_REPOSITORY ⇒ 필수) |
|
|
| transaction | `TransactionPort.inWrite(...)` 콜백 (`application/transaction/TransactionPort.java`) — 직접 `@Transactional` 금지 | `application_does_not_use_spring_transactional_annotation` |
|
|
| outbound write port | `application/.../<X>WritePort.java` (`*Port`) | `read_only_use_cases_do_not_call_repository_write_methods`(capability 정합) |
|
|
| persistence adapter + migration | `adapter/persistence/<x>/{<X>Entity, <X>JpaRepository, <X>EntityMapper}` + `db/migration/V<n>__<x>.sql` | `persistence_adapter_does_not_depend_on_web_or_outbound_adapters` |
|
|
| (위임) entity PK/ID 생성 | server-assigned ULID via domain `*IdFactory` port (auto-increment/UUID v4 금지) | [[raw/branch-notes/feature-resource-identifier-contract]] D5 소관 — 본 branch 범위 밖, consume only |
|
|
| web DTO/mapper/controller + contract test | read-only 와 동일 + idempotency/header handling | `controllers_do_not_return_domain_or_entity_types` 등 |
|
|
|
|
### 4. shared-contract change gate
|
|
|
|
> **Trace**: D5 + [[raw/branch-notes/feature-architecture-enforcement-rules]] D6. shared-contract 는 도메인 기능 추가 시 **원칙적으로 변경 없음** — skeleton-wide 계약일 때만.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — 허용 package 목록은 ArchUnit rule 이 화이트리스트로 강제.
|
|
|
|
`shared_contract_contains_only_operational_contract_packages` 가 허용하는 package 만 변경 가능: `error`(`Category` enum 10값 — VALIDATION/AUTH/AUTHZ/NOT_FOUND/CONFLICT/RATE_LIMIT/TRANSIENT_DEPENDENCY/PERMANENT_DEPENDENCY/DATA_INTEGRITY/INTERNAL), `response`(`Envelope<T>`), `request`, `operation`, `headers`, `logging`, `tracing`, `metrics`, `registry`, `annotation`, `security`(`Permission`), `concurrency`. **도메인 전용 타입/비즈니스 enum/feature DTO 는 금지** → `domain-core` 또는 adapter DTO 로. 새 error code 는 `docs/registries/error-codes.yaml` 에 `owner_branch`(= 그 기능 branch) + 기존 `Category` enum 값으로 추가(신규 category 추가는 `feature-operational-error-observability-foundation` 소관 — 본 branch 범위 밖).
|
|
|
|
### 5. sample-portfolio isolation & dry-run
|
|
|
|
> **Trace**: D6 + [[raw/branch-notes/feature-architecture-enforcement-rules]] D7; D7(scorecard consume) + [[raw/branch-notes/feature-implementation-readiness-scorecard]] D5. sample scenario/minimum-model 의 owner 는 [[raw/branch-notes/feature-sample-domain-contract-fixture]] D5 — 본 branch 는 구조 참고만.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — 격리는 ArchUnit + Gradle task 가 강제.
|
|
|
|
- production module 의 `build.gradle` 이 `implementation project(':sample-portfolio')` 를 선언하면 실패 — `verifyCleanArchitectureDependencies`(allowedProjectDependencies 에서 sample-portfolio 제외) + ArchUnit `production_code_does_not_depend_on_sample_portfolio`.
|
|
- 새 도메인 기능은 sample-portfolio 의 `worklog` 구조(domain→application→web→persistence 한 슬라이스)를 **읽고 모방**하되 import 하지 않는다. sample 의 Flyway 는 `db/sample-migration/`(production 의 `db/migration/` 과 분리).
|
|
- **dry-run checklist SSOT = 본 branch 의 §New Domain Module Slice + §Read/Write Difference Table + 본 §구현 가이드.** [[raw/branch-notes/feature-implementation-readiness-scorecard]](D5) 는 이를 consume 만 하고 자체 checklist 를 두지 않는다.
|
|
|
|
## 구현 결과
|
|
|
|
| Evidence item | File / command | Result | Evidence grade |
|
|
|---|---|---|---|
|
|
| Read-only onboarding dry-run | `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java` + `dev.caskeleton.onboarding.application.query/ListFeatureAggregatesQuery`, `FeatureAggregateSummaryQueryPort`, `ListFeatureAggregatesUseCase`, web DTO/mapper/controller fixture | query/use case/mapper/controller 존재, write command/write port 부재, ArchUnit rules no violation | `locally-verified` |
|
|
| Write onboarding dry-run | `dev.caskeleton.onboarding.domain.feature.*`, `CreateFeatureAggregateCommand`, `CreateFeatureAggregateUseCase`, `FeatureAggregateWritePort`, persistence entity/mapper/repository adapter, `src/app-bootstrap/src/test/resources/db/onboarding-migration/V999__feature_aggregate.sql` | domain/id factory/command/use case/write port/persistence/migration artifact 존재, ArchUnit rules no violation | `locally-verified` |
|
|
| Transaction boundary enforcement | `CleanArchitectureTest.use_case_capability_matches_transaction_port_boundary` + `MissingTransactionBoundaryUseCase` negative fixture | `WRITE_REPOSITORY` without `TransactionPort.inWrite` is caught | `locally-verified` |
|
|
| shared-contract scope enforcement | tightened `shared_contract_contains_only_operational_contract_packages` allowlist + `violations/shared/worklog/WorkLogStatus` negative fixture | domain-specific `..shared.worklog..` package is caught | `locally-verified` |
|
|
| sample isolation | `DomainFeatureOnboardingContractTest` verifies onboarding fixtures have no `sample-portfolio` dependency; existing `SampleRemovalSmokeContractTest` keeps production Gradle sample deps test-scoped | production/sample boundary remains guarded | `locally-verified` |
|
|
| Neutral fixture naming | `Ticket*` test fixture names renamed to `FeatureAggregate*`; migration renamed to `V999__feature_aggregate.sql` | onboarding fixture no longer reads as a concrete skeleton domain | `locally-verified` |
|
|
| Package-path alignment | onboarding positive fixture moved to `src/app-bootstrap/src/test/java/dev/caskeleton/onboarding/`; package declarations remain `dev.caskeleton.onboarding.*` | source path now matches package and both test/sampleOffTest compile outputs contain onboarding classes | `locally-verified` |
|
|
|
|
### Verification commands (2026-06-25)
|
|
|
|
| Command | Result |
|
|
|---|---|
|
|
| `./gradlew verifyCleanArchitectureDependencies` | `BUILD SUCCESSFUL` |
|
|
| `./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` | `BUILD SUCCESSFUL` |
|
|
| `./gradlew :app-bootstrap:test --tests '*DomainFeatureOnboardingContractTest' --tests '*ArchitectureViolationFixtureTest'` | `BUILD SUCCESSFUL` |
|
|
| `./gradlew test` | `BUILD SUCCESSFUL` |
|
|
| `./gradlew check` | `BUILD SUCCESSFUL` (checkstyle/SpotBugs report output remains non-fatal under current Gradle settings) |
|
|
| `./gradlew :app-bootstrap:spotlessCheck :app-bootstrap:test --tests '*DomainFeatureOnboardingContractTest' --tests '*ArchitectureViolationFixtureTest'` | `BUILD SUCCESSFUL` after neutral fixture rename |
|
|
| `./gradlew :app-bootstrap:compileTestJava :app-bootstrap:compileSampleOffTestJava --rerun-tasks` | `BUILD SUCCESSFUL` after package-path alignment |
|
|
| `./gradlew :app-bootstrap:test --tests '*DomainFeatureOnboardingContractTest' --tests '*ArchitectureViolationFixtureTest' :app-bootstrap:sampleOffTest --tests '*DomainFeatureOnboardingContractTest' --tests '*ArchitectureViolationFixtureTest'` | `BUILD SUCCESSFUL` after package-path alignment |
|
|
| `./gradlew check` | `BUILD SUCCESSFUL` after package-path alignment |
|
|
|
|
## 엣지·실패·의존
|
|
|
|
> R4(깊이 게이트) 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거.
|
|
|
|
- **실패·엣지 경로** (각 경로 = 어느 rule 이 잡는가):
|
|
- read-only feature 를 write 파일 없이 추가 → `verifyCleanArchitectureDependencies` + ArchUnit 통과해야 정상(Claim 1). 반대로 controller 만 추가하고 query use case/mapper 가 없으면 review 실패(테스트 계약).
|
|
- write feature 에서 `@UseCaseCapability` 누락 → `inbound_port_implementations_declare_capability` 실패. `@RequiresPermission` 누락(WRITE_REPOSITORY) → `mutating_use_cases_declare_required_permission` 실패. 직접 `@Transactional` 사용 → `application_does_not_use_spring_transactional_annotation` 실패.
|
|
- capability 와 실제 호출 불일치(예: `READ_REPOSITORY` 인데 save/delete 호출) → `read_only_use_cases_do_not_call_repository_write_methods` 실패. `bulkWrite=true` 인데 `WRITE_REPOSITORY` 아님 → `bulk_write_capability_requires_write_repository_access` 실패.
|
|
- controller 가 domain/JPA entity 직접 반환 → `controllers_do_not_return_domain_or_entity_types` 실패. application 메서드가 web DTO 수신 → `application_methods_do_not_accept_web_dtos` 실패.
|
|
- `shared-contract` 에 domain type 유입 → `shared_contract_contains_only_operational_contract_packages` 실패. `jakarta.validation` 을 domain/application 에서 import → `validation_constraints_stay_at_web_boundary` 실패.
|
|
- idempotency `KEYED` 사용 시도 → **계약 게이트 위반**(application-port D14, `feature-rate-limit-idempotency-contract` 미merge). 빌드가 아니라 review/계약 차원에서 차단.
|
|
- empty anchor 엣지: 새 module/feature 의 빈 anchor package 가 ArchUnit "empty should" 로 오탐될 수 있음 → 유효 rule 에 `allowEmptyShould(true)` (선례: [[raw/errors/archunit-empty-should-anchor-2026-05-27]]).
|
|
- Flyway version 충돌 엣지: 동시 onboarding 중인 두 write feature 가 같은 `db/migration/V<n>__*.sql` 번호를 잡으면 startup/`flywayValidate` 실패(실코드 V1/V3/V4 이미 점유). 번호 할당은 merge 순서 기준 단조 증가로 고정하고 충돌 시 빌드 실패를 신호로 받는다.
|
|
- **다른 계약 의존** (이 계약이 바뀌면 본 branch 의 onboarding slice 영향):
|
|
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] D1~D9 — module/package boundary·dependency direction. 변경 시 §구현 가이드 §1 placement 표 갱신.
|
|
- [[raw/branch-notes/feature-architecture-enforcement-rules]] D1~D8 — ArchUnit/Gradle rule 명·범위. rule rename 시 본 노트의 rule 인용 갱신 필요.
|
|
- [[raw/branch-notes/feature-application-port-usecase-contract]] D1/D3/D9/D14 — `*UseCase`/`*Port` 명명, `TransactionPort`, read-only capability, idempotency 게이트. consume only.
|
|
- [[raw/branch-notes/feature-sample-domain-contract-fixture]] D5 — sample scenario/minimum-model owner. 본 branch 는 구조 참고만.
|
|
- [[raw/branch-notes/feature-implementation-readiness-scorecard]] D5 — 본 branch checklist 를 consume (역방향 의존). 본 §의 checklist 구조가 바뀌면 scorecard area #15 dry-run 매핑 영향.
|
|
|
|
## Audit & Findings (2026-06-15 branch-spec — ca-tmpl ground-truth 대조)
|
|
|
|
> ca-tmpl 실코드 대조에서 발견한 노트↔구현 drift. 본 branch 결정 영역 *밖* 의 것은 자동 rewrite 하지 않고 *정합 권고*만 남긴다.
|
|
|
|
- **DRIFT① — 추상 capability 모델 → `@UseCaseCapability` 구체화**: 노트의 New Domain Module Slice/Read-Write Table 은 "transaction/idempotency/capability declaration" 을 추상 서술. 실제 ca-tmpl 은 `@UseCaseCapability(transactionMode, idempotency, repositoryAccess, externalOutboundAllowed, sensitiveRead, bulkWrite, crossTenantAdmin)` + `@RequiresPermission` + `TransactionPort` 로 구체화(노트 created 2026-05-22 < capabilities.yaml 2026-06-05). **판정: 추상 표는 contract 로 유효하게 유지**, §구현 가이드가 구체 메커니즘을 anchor. capability 어휘 자체의 owner 는 `feature-application-port-usecase-contract` + `feature-repository-access-permission-contract`(capabilities.yaml) — **OUT_OF_BRANCH_SCOPE**, 본 branch 에서 재결정 안 함.
|
|
- **DRIFT② — `port/in`·`port/out` 트리 미실현**: blueprint 계획 트리는 `application/port/in`·`port/out`. 실제 application-core 는 `usecase/`,`command/`,`query/`,`capability/`,`transaction/`,`idempotency/`,`security/` (inbound port = `usecase/` 의 `*UseCase`, outbound = `*Port` co-located). owner = [[raw/branch-notes/feature-application-port-usecase-contract]] D1. **OUT_OF_BRANCH_SCOPE** — 본 §구현 가이드는 실제 경로(`usecase/`)를 anchor 로 사용.
|
|
- **DRIFT③ — 9번째 module `adapter-identifier`**: 노트의 New Domain Module Slice 는 8 module. 실제 `settings.gradle` 에 `adapter-identifier`(ID 생성, `feature-resource-identifier-contract` 소관) 추가됨. 새 도메인 기능이 보통 건드리지 않음. **OUT_OF_BRANCH_SCOPE** — 각주로만: "adapter module 은 책임별 확장 가능(예: `adapter-identifier`)".
|
|
|
|
## 테스트 계약
|
|
|
|
- 새 read-only feature가 query use case / inbound port / response mapper / contract test 없이 controller만 추가되면 실패.
|
|
- 새 write feature가 command / use case / outbound port / persistence adapter / transaction decision 중 하나 없이 추가되면 실패.
|
|
- `domain-core`가 Spring/JPA/HTTP DTO/adapter type을 import하면 실패.
|
|
- `application-core`가 adapter implementation을 직접 import하면 실패.
|
|
- `adapter-web` controller가 domain object를 response로 직접 반환하면 실패.
|
|
- `shared-contract`에 domain-specific class/package가 추가되면 실패.
|
|
- production module이 `sample-portfolio`에 의존하면 실패.
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리한다.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| read-only domain onboarding이 write-only 파일 없이도 contract/architecture test를 통과한다 | read-only file set은 ca-tmpl 자체 결정 | 가상 read-only feature 추가 → command/idempotency/outbox 없음 → Gradle/ArchUnit/contract test 통과 확인 | `locally-verified` — `DomainFeatureOnboardingContractTest.read_only_onboarding_slice_has_minimum_contract_and_no_write_artifacts`, `./gradlew test` |
|
|
| write domain onboarding에서 command/use case/port/persistence/transaction decision 중 하나가 빠지면 실패한다 | 누락 탐지는 custom ArchUnit/contract rule 필요 | violating write feature 추가 → 누락 유형별 실패 메시지 확인 (capability/permission/transaction rule) | `locally-verified` — `use_case_capability_matches_transaction_port_boundary`, 기존 capability/permission fixture tests, `./gradlew test` |
|
|
| `shared-contract`에 domain-specific class가 들어오면 실패한다 | shared module scope rule 구현 필요 | `shared-contract/.../worklog/WorkLogStatus` 추가 → ArchUnit `shared_contract_contains_only_operational_contract_packages` 실패 확인 | `locally-verified` — `shared_contract_scope_rule_catches_domain_specific_shared_package`, `./gradlew test` |
|
|
| production code가 `sample-portfolio`을 import하면 실패한다 | sample 격리는 project decision이며 실증 필요 | production module에 `implementation project(':sample-portfolio')` 추가 → `verifyCleanArchitectureDependencies` 실패 확인 | `locally-verified` — `verifyCleanArchitectureDependencies`, `production_code_does_not_depend_on_sample_portfolio`, onboarding fixture no-sample assertion |
|
|
| adapter-web controller가 domain object를 response로 직접 반환하면 실패한다 | module boundary만으로는 direct return을 잡지 못할 수 있음 | controller violating method 추가 → ArchUnit `controllers_do_not_return_domain_or_entity_types` 실패 확인 | `locally-verified` — existing `DomainReturningControllerFixture` negative test + onboarding controller no-violation test |
|
|
| scorecard area #15가 본 branch의 checklist를 consume only로 유지한다 | cross-branch governance는 자동 강제가 어려움 | [[raw/branch-notes/feature-implementation-readiness-scorecard]]`에서 자체 dry-run checklist가 없는지 grep 검증 (해당 branch D5 가 본 branch 를 consume 으로 선언함을 확인) | `locally-verified` — `rg -n 'dry-run checklist|feature-domain-feature-onboarding-contract|consume|area #15|area adoption|adoption' raw/branch-notes/feature-implementation-readiness-scorecard.md` |
|
|
|
|
## 마주친 문제
|
|
|
|
- Gradle wrapper sandbox lock: 최초 focused test 실행이 `~/.gradle/.../gradle-9.0.0-bin.zip.lck (Read-only file system)` 로 실패해 권한 상승으로 재실행했다. 별도 기록: [[raw/errors/gradle-wrapper-sandbox-lock-2026-06-25]].
|
|
- Onboarding fixture package-path mismatch: `FeatureAggregate*` fixture의 package 선언과 파일 경로가 어긋나 `compileSampleOffTestJava`에서 패키지를 찾지 못했다. fixture를 `src/app-bootstrap/src/test/java/dev/caskeleton/onboarding/` 아래로 이동해 해결했다. 별도 기록: [[raw/errors/onboarding-fixture-package-path-mismatch-2026-06-25]].
|
|
- zsh quoting 실수: `rg` 패턴에 backtick 을 double quote 안에 넣어 `command not found: adoption` 이 발생했다. single quote 로 재실행해 scorecard consume-only evidence 를 확인했다.
|
|
|
|
## 묶음 (이 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/domain-fowler-anemic-vs-rich-model]]
|
|
- [[raw/official-docs/domain-vaughn-vernon-aggregate-root]]
|
|
- [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]]
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: interviews:start -->
|
|
- [[raw/interviews/clean-architecture-domain-onboarding-guardrails]]
|
|
<!-- GENERATED: interviews:end -->
|
|
|
|
<!-- GENERATED: errors:start -->
|
|
- [[raw/errors/gradle-wrapper-sandbox-lock-2026-06-25]]
|
|
<!-- GENERATED: errors:end -->
|
|
|
|
<!-- GENERATED: blog-topics:start -->
|
|
- [[raw/blog-topics/executable-clean-architecture-onboarding-2026-06-25]]
|
|
<!-- GENERATED: blog-topics:end -->
|
|
|
|
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
|
|
|
|
### Sub-branches (세부 작업)
|
|
|
|
- (없음)
|
|
|
|
### 오류 기록 (이 branch 작업 중 발생)
|
|
|
|
- [[raw/errors/gradle-wrapper-sandbox-lock-2026-06-25]] — Gradle wrapper/test 실행이 sandbox 밖 `~/.gradle` lock 파일 쓰기에서 실패한 재현 가능한 도구 문제.
|
|
- [[raw/errors/onboarding-fixture-package-path-mismatch-2026-06-25]] — synthetic onboarding fixture package와 source path가 불일치해 sampleOffTest 컴파일이 실패한 문제.
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
- [[raw/interviews/clean-architecture-domain-onboarding-guardrails]] — Clean Architecture 템플릿에서 새 도메인 온보딩을 문서가 아니라 실행 가능한 guardrail 로 검증하는 방법.
|
|
|
|
### 강의 (이 작업을 위해 학습한 강의)
|
|
|
|
- (없음)
|
|
|
|
### job-posting tie-ins (이 작업에서 파생된 글감)
|
|
|
|
- [[raw/blog-topics/executable-clean-architecture-onboarding-2026-06-25]] — multi-module Clean Architecture onboarding checklist 를 ArchUnit/JUnit dry-run 으로 고정한 경험 글감.
|
|
- job-posting tie-ins: 없음.
|
|
|
|
## 관련 일일 노트
|
|
|
|
- [[raw/daily-notes/2026-05-27]]
|
|
|
|
## 완료 후 정리
|
|
|
|
> 머지/종료 시점에 채움. `/ingest`가 이 섹션을 기준으로 wiki/projects/에 추출.
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목:
|
|
- `locally-verified` 항목:
|
|
- `prod-verified` 항목:
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|