init: readme 작성 하네스 설계
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
|
||||
|
||||
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
|
||||
|
||||
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
|
||||
|
||||
<!-- section-id: overview -->
|
||||
## 템플릿 개요
|
||||
|
||||
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
|
||||
|
||||
다음 상황에 특히 잘 맞습니다.
|
||||
|
||||
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
|
||||
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
|
||||
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
|
||||
|
||||
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
|
||||
|
||||
<!-- section-id: project-value -->
|
||||
## 저장소가 강제하는 것, 도입자가 결정할 것
|
||||
|
||||
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
|
||||
| --- | --- |
|
||||
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
|
||||
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
|
||||
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
|
||||
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
|
||||
|
||||
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
|
||||
|
||||
<!-- section-id: architecture -->
|
||||
## 아키텍처와 코드 배치
|
||||
|
||||
<!-- visual-id: architecture-dependency-direction -->
|
||||
|
||||
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
|
||||
Application[application-core<br/>use cases · ports]
|
||||
Domain[domain-core<br/>business invariants]
|
||||
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
|
||||
Shared[shared-contract<br/>operational contracts]
|
||||
Bootstrap[app-bootstrap<br/>composition root]
|
||||
|
||||
Inbound --> Application
|
||||
Outbound --> Application
|
||||
Application --> Domain
|
||||
Inbound --> Shared
|
||||
Outbound --> Shared
|
||||
Application --> Shared
|
||||
Bootstrap --> Inbound
|
||||
Bootstrap --> Outbound
|
||||
Bootstrap --> Application
|
||||
Bootstrap --> Domain
|
||||
Bootstrap --> Shared
|
||||
```
|
||||
|
||||
| 모듈 그룹 | 코드 배치 기준 |
|
||||
| --- | --- |
|
||||
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
|
||||
| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
|
||||
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
|
||||
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
|
||||
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
|
||||
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
|
||||
|
||||
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
|
||||
|
||||
<!-- section-id: quick-start -->
|
||||
## 빠른 시작
|
||||
|
||||
<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
|
||||
|
||||
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
저장소 루트에서 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew bootstrap
|
||||
```
|
||||
|
||||
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
|
||||
|
||||
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
|
||||
|
||||
bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
|
||||
|
||||
<!-- section-id: adoption -->
|
||||
## 실제 프로젝트로 전환하기
|
||||
|
||||
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
|
||||
|
||||
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
|
||||
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
|
||||
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
|
||||
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
|
||||
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
|
||||
|
||||
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
|
||||
|
||||
<!-- section-id: verification -->
|
||||
## 검증 루프
|
||||
|
||||
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
|
||||
|
||||
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
|
||||
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
|
||||
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
|
||||
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew test
|
||||
./gradlew verifyCleanArchitectureDependencies
|
||||
./gradlew :app-bootstrap:sampleOffTest
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
|
||||
|
||||
<!-- section-id: documentation -->
|
||||
## 상세 문서 지도와 적용 한계
|
||||
|
||||
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
|
||||
|
||||
- [빌드·실행·환경 설정](src/README.md)
|
||||
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
|
||||
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
|
||||
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
|
||||
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
|
||||
|
||||
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
|
||||
|
||||
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
|
||||
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
|
||||
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
|
||||
@@ -0,0 +1,147 @@
|
||||
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
|
||||
|
||||
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
|
||||
|
||||
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
|
||||
|
||||
<!-- section-id: overview -->
|
||||
## 템플릿 개요
|
||||
|
||||
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
|
||||
|
||||
다음 상황에 특히 잘 맞습니다.
|
||||
|
||||
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
|
||||
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
|
||||
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
|
||||
|
||||
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
|
||||
|
||||
<!-- section-id: project-value -->
|
||||
## 저장소가 강제하는 것, 도입자가 결정할 것
|
||||
|
||||
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
|
||||
| --- | --- |
|
||||
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
|
||||
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
|
||||
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
|
||||
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
|
||||
|
||||
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
|
||||
|
||||
<!-- section-id: architecture -->
|
||||
## 아키텍처와 코드 배치
|
||||
|
||||
<!-- visual-id: architecture-dependency-direction -->
|
||||
|
||||
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
|
||||
Application[application-core<br/>use cases · ports]
|
||||
Domain[domain-core<br/>business invariants]
|
||||
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
|
||||
Shared[shared-contract<br/>operational contracts]
|
||||
Bootstrap[app-bootstrap<br/>composition root]
|
||||
|
||||
Inbound --> Application
|
||||
Outbound --> Application
|
||||
Application --> Domain
|
||||
Inbound --> Shared
|
||||
Outbound --> Shared
|
||||
Application --> Shared
|
||||
Bootstrap --> Inbound
|
||||
Bootstrap --> Outbound
|
||||
Bootstrap --> Application
|
||||
Bootstrap --> Domain
|
||||
Bootstrap --> Shared
|
||||
```
|
||||
|
||||
| 모듈 그룹 | 코드 배치 기준 |
|
||||
| --- | --- |
|
||||
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
|
||||
| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
|
||||
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
|
||||
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
|
||||
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
|
||||
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
|
||||
|
||||
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
|
||||
|
||||
<!-- section-id: quick-start -->
|
||||
## 빠른 시작
|
||||
|
||||
<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
|
||||
|
||||
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
저장소 루트에서 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew bootstrap
|
||||
```
|
||||
|
||||
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
|
||||
|
||||
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
|
||||
|
||||
bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
|
||||
|
||||
<!-- section-id: adoption -->
|
||||
## 실제 프로젝트로 전환하기
|
||||
|
||||
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
|
||||
|
||||
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
|
||||
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
|
||||
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
|
||||
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
|
||||
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
|
||||
|
||||
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
|
||||
|
||||
<!-- section-id: verification -->
|
||||
## 검증 루프
|
||||
|
||||
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
|
||||
|
||||
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
|
||||
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
|
||||
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
|
||||
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew test
|
||||
./gradlew verifyCleanArchitectureDependencies
|
||||
./gradlew :app-bootstrap:sampleOffTest
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
|
||||
|
||||
<!-- section-id: documentation -->
|
||||
## 상세 문서 지도와 적용 한계
|
||||
|
||||
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
|
||||
|
||||
- [빌드·실행·환경 설정](src/README.md)
|
||||
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
|
||||
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
|
||||
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
|
||||
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
|
||||
|
||||
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
|
||||
|
||||
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
|
||||
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
|
||||
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
|
||||
@@ -0,0 +1,11 @@
|
||||
--- README.md (current)
|
||||
+++ README.md (candidate)
|
||||
@@ -72,6 +72,8 @@
|
||||
<!-- section-id: quick-start -->
|
||||
## 빠른 시작
|
||||
|
||||
+<!-- feature-developer-experience-contract: first-run success probe = GET /api/healthcheck -->
|
||||
+
|
||||
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
저장소 루트에서 다음을 실행합니다.
|
||||
@@ -0,0 +1,7 @@
|
||||
schema-version: 1
|
||||
mode: bootstrap
|
||||
target-rel: README.md
|
||||
generated-hash: sha256:e7958b9784569397ce5123206c4c80578373fd64c94900fef887ecafb8325c09
|
||||
target-before-hash: sha256:5c39a893d2255c8df7798fcd62090490a345687c9637c683e4d9ad9aab70e1d6
|
||||
repository-snapshot-hash: sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e
|
||||
review-score: 96
|
||||
@@ -0,0 +1,188 @@
|
||||
schema-version: 1
|
||||
claims:
|
||||
- id: C-STACK-001
|
||||
type: factual
|
||||
statement: "`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-STACK-001}, {fact-id: F-MODULES-001}]
|
||||
status: supported
|
||||
- id: C-POSITION-001
|
||||
type: factual
|
||||
statement: "이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-MODULE-COUNT-001
|
||||
type: factual
|
||||
statement: "19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-MODULES-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-DEPS-001
|
||||
type: factual
|
||||
statement: "모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-CODE-001
|
||||
type: factual
|
||||
statement: "application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-LOCKS-001
|
||||
type: factual
|
||||
statement: "leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-LOCKS-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-SAMPLE-001
|
||||
type: factual
|
||||
statement: "같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-VISUAL-MEANING-001
|
||||
type: factual
|
||||
statement: "다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-DOMAIN-001
|
||||
type: factual
|
||||
statement: "외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-APPLICATION-001
|
||||
type: factual
|
||||
statement: "`domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-ADAPTERS-001
|
||||
type: factual
|
||||
statement: "전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-MODULES-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-SHARED-001
|
||||
type: factual
|
||||
statement: "비즈니스 개념이 아닌 공용 운영 계약을 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-MODULE-001
|
||||
type: factual
|
||||
statement: "선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-COMPOSITION-001}]
|
||||
status: supported
|
||||
- id: C-SAMPLE-ROLE-001
|
||||
type: factual
|
||||
statement: "템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-TWO-LAYERS-001
|
||||
type: factual
|
||||
statement: "Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-PREREQUISITES-001
|
||||
type: factual
|
||||
statement: "필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-STACK-001}, {fact-id: F-BOOTSTRAP-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-COMMAND-001
|
||||
type: factual
|
||||
statement: "`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}]
|
||||
status: supported
|
||||
- id: C-HEALTH-001
|
||||
type: factual
|
||||
statement: "성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-HEALTH-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-SIDE-EFFECT-001
|
||||
type: factual
|
||||
statement: "bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
- id: C-CLEANUP-001
|
||||
type: factual
|
||||
statement: "위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
- id: C-IDENTITY-001
|
||||
type: factual
|
||||
statement: "Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-IDENTITY-001}]
|
||||
status: supported
|
||||
- id: C-ADOPT-SAMPLE-001
|
||||
type: factual
|
||||
statement: "`sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-NEW-MODULE-POLICY-001
|
||||
type: factual
|
||||
statement: "새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-TEST-001
|
||||
type: factual
|
||||
statement: "일반 테스트: `./gradlew test`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-ARCH-001
|
||||
type: factual
|
||||
statement: "모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-SAMPLE-001
|
||||
type: factual
|
||||
statement: "sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-CHECK-001
|
||||
type: factual
|
||||
statement: "전체 품질 계약: `./gradlew check`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-CHECK-001}]
|
||||
status: supported
|
||||
- id: C-CHECK-SCOPE-001
|
||||
type: factual
|
||||
statement: "`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다."
|
||||
section: verification
|
||||
sources: [{fact-id: F-CHECK-001}]
|
||||
status: supported
|
||||
- id: C-DOCS-STRATEGY-001
|
||||
type: evaluative
|
||||
statement: "README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오."
|
||||
section: documentation
|
||||
sources: []
|
||||
status: supported
|
||||
- id: C-LIMIT-CHOICES-001
|
||||
type: factual
|
||||
statement: "여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다."
|
||||
section: documentation
|
||||
sources: [{fact-id: F-TEMPLATE-LIMIT-001}]
|
||||
status: supported
|
||||
- id: C-LIMIT-LOCAL-001
|
||||
type: factual
|
||||
statement: "기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다."
|
||||
section: documentation
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
@@ -0,0 +1,50 @@
|
||||
schema-version: 1
|
||||
project-profile:
|
||||
primary: project-template
|
||||
secondary:
|
||||
- backend-service
|
||||
audiences:
|
||||
primary:
|
||||
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
|
||||
secondary:
|
||||
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
|
||||
reader-outcomes:
|
||||
- 템플릿이 제공하는 강제 규칙과 도입자가 결정할 영역을 구분한다.
|
||||
- 로컬 부트스트랩을 실행하고 성공 조건과 정리 방법을 확인한다.
|
||||
- 도메인·유스케이스·어댑터·조립 코드를 올바른 모듈에 배치한다.
|
||||
- 샘플 제거 및 전체 품질 계약을 재현 가능한 명령으로 검증한다.
|
||||
project-story:
|
||||
value-proposition: 문서로만 권고하는 구조가 아니라 Gradle과 ArchUnit 규칙으로 의존 방향을 지속적으로 검증하는 Spring Boot 템플릿이다.
|
||||
problem: 서비스 초기 구조는 빠르게 복사할 수 있어도 경계가 빌드에 강제되지 않으면 기능 추가 과정에서 쉽게 무너진다.
|
||||
target-reader: 신규 Java 백엔드의 구조와 검증 기준을 함께 도입하려는 개발자
|
||||
notable-traits:
|
||||
- text: Java 21과 Spring Boot 4.0.0을 사용하는 19개 모듈 구성이다.
|
||||
fact-ids: [F-STACK-001, F-MODULES-001]
|
||||
- text: 모듈 의존 방향과 application 코드 경계를 실행 가능한 빌드·ArchUnit 규칙으로 검증한다.
|
||||
fact-ids: [F-DEPENDENCY-POLICY-001, F-CODE-BOUNDARY-001]
|
||||
- text: 로컬 부트스트랩은 컴파일부터 PostgreSQL·앱 시작과 HTTP 상태 확인까지 하나의 계약으로 묶는다.
|
||||
fact-ids: [F-BOOTSTRAP-001, F-HEALTH-001]
|
||||
- text: sample-portfolio를 테스트 fixture로 격리하고 샘플 없는 핵심 테스트 경로를 제공한다.
|
||||
fact-ids: [F-SAMPLE-001]
|
||||
maturity: 자동화된 구조·실행·검증 계약을 갖춘 참조 템플릿
|
||||
limitations:
|
||||
- 제공되는 adapter 가운데 실제 서비스가 채택할 범위는 도입자가 결정해야 한다.
|
||||
- 로컬 실행 토폴로지는 Docker와 PostgreSQL을 전제로 한다.
|
||||
- 조직별 보안·성능·가용성 요구사항은 템플릿의 내부 검증과 별도로 평가해야 한다.
|
||||
narrative-variant: architecture-template
|
||||
reader-journey:
|
||||
- reader-question: 이 템플릿은 무엇이며 어떤 문제를 해결하는가?
|
||||
section-id: overview
|
||||
- reader-question: 일반적인 시작점과 비교해 무엇이 강제되는가?
|
||||
section-id: project-value
|
||||
- reader-question: 모듈은 어떤 방향으로 의존하고 코드는 어디에 놓는가?
|
||||
section-id: architecture
|
||||
- reader-question: 가장 짧은 로컬 실행 경로와 성공 신호는 무엇인가?
|
||||
section-id: quick-start
|
||||
- reader-question: 샘플을 실제 도메인으로 바꾸는 순서는 무엇인가?
|
||||
section-id: adoption
|
||||
- reader-question: 구조와 전체 품질 계약을 어떻게 다시 검증하는가?
|
||||
section-id: verification
|
||||
- reader-question: 세부 설정과 운영 계약은 어디에서 확인하는가?
|
||||
section-id: documentation
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
schema-version: 1
|
||||
sections:
|
||||
- id: overview
|
||||
title-guidance: 템플릿 개요
|
||||
level: 2
|
||||
purpose: 가치 제안, 대상 독자, 적합하거나 부적합한 사용 상황을 빠르게 판단시킨다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- Java와 Spring Boot 기준 버전
|
||||
- 권고가 아닌 실행 가능한 경계 검증이라는 차별점
|
||||
- 참조 템플릿이라는 정직한 포지셔닝
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 이 프로젝트의 정체성을 이해하는 데 그림이 필요한가?
|
||||
rationale: 짧은 가치 제안과 적합성 목록이 더 빠르고 정확하다.
|
||||
- id: project-value
|
||||
title-guidance: 제공 가치와 결정 영역
|
||||
level: 2
|
||||
purpose: 저장소가 자동으로 강제하는 것과 도입자가 선택할 것을 분리한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- 아키텍처·잠금·샘플 격리 계약
|
||||
- 도메인·adapter·배포 정책은 도입자 책임임을 명시
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 강제 영역과 선택 영역을 어떻게 가장 빨리 비교하는가?
|
||||
rationale: 두 열 비교표가 그림보다 직접적이고 접근성이 높다.
|
||||
- id: architecture
|
||||
title-guidance: 아키텍처와 코드 배치
|
||||
level: 2
|
||||
purpose: core, adapter, composition root의 관계와 코드 변경 위치를 설명한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- 의존 방향 Mermaid 다이어그램
|
||||
- 모듈 그룹별 책임 표
|
||||
- Gradle과 ArchUnit이 각각 검증하는 경계
|
||||
visual-slot:
|
||||
decision: include
|
||||
reader-question: 여러 모듈 그룹이 어느 방향으로 의존하는가?
|
||||
rationale: 다섯 구성요소의 의존 방향은 문장 나열보다 흐름도가 더 빨리 전달한다.
|
||||
purpose: adapter와 composition root가 core 방향으로 의존한다는 구조를 한 화면에 보여준다.
|
||||
- id: quick-start
|
||||
title-guidance: 빠른 시작
|
||||
level: 2
|
||||
purpose: 사전 조건, 단일 bootstrap 명령, 부작용, 성공 신호와 정리 방법을 제공한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- JDK 21과 Docker
|
||||
- bootstrap 실행 명령
|
||||
- API health 성공 조건
|
||||
- Compose 정리 명령
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 실행 절차를 이해하는 데 추가 시각 자료가 필요한가?
|
||||
rationale: 짧은 명령 블록과 성공 조건이 가장 실행 가능하다.
|
||||
- id: adoption
|
||||
title-guidance: 실제 프로젝트로 전환하기
|
||||
level: 2
|
||||
purpose: 프로젝트 식별자, 도메인, adapter, 환경 정책, sample 제거 순서로 도입 경로를 안내한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- sample은 참조 구현이며 production 의존성이 아님
|
||||
- sampleOffTest를 이용한 제거 검증
|
||||
- 상세 문서를 중복하지 않는 단계별 전환 경로
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 도입 순서는 어떤 형태가 가장 행동하기 쉬운가?
|
||||
rationale: 번호 목록과 검증 체크포인트가 진행 순서를 명확히 한다.
|
||||
- id: verification
|
||||
title-guidance: 검증 루프
|
||||
level: 2
|
||||
purpose: 빠른 테스트, 아키텍처 경계, 샘플 제거, 전체 check의 목적과 명령을 분리한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- test와 architecture 명령
|
||||
- sampleOffTest 명령
|
||||
- 전체 check가 집계하는 정책
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 검증 수준별 명령 선택에 그림이 필요한가?
|
||||
rationale: 목적과 명령을 짝지은 표가 더 정확하다.
|
||||
- id: documentation
|
||||
title-guidance: 상세 문서 지도와 한계
|
||||
level: 2
|
||||
purpose: 빌드·모듈·환경·운영 문서로 이동시키고 템플릿의 적용 한계를 명시한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- src README와 대표 모듈 README 링크
|
||||
- 환경 레지스트리와 runbook 링크
|
||||
- 조직별 비기능 요구사항은 별도 검증이라는 한계
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 세부 문서 위치를 찾는 데 그림이 필요한가?
|
||||
rationale: 목적별 링크 목록이 탐색과 유지보수에 적합하다.
|
||||
@@ -0,0 +1,44 @@
|
||||
schema-version: 1
|
||||
target:
|
||||
repository: /home/donghyeon/workspace/ca-tmpl
|
||||
readme-path: README.md
|
||||
mode: bootstrap
|
||||
profile-override: project-template
|
||||
project-intent:
|
||||
purpose: ca-tmpl을 평가하고 실제 서비스의 출발점으로 채택하는 데 필요한 판단·실행·변경 경로를 제공한다.
|
||||
positioning: 구현 목록이 아니라 아키텍처 경계를 빌드와 테스트로 강제하는 Spring Boot 프로젝트 템플릿의 진입 문서다.
|
||||
maturity: 광범위한 자동화 계약을 갖춘 참조 템플릿이며, 개별 조직의 운영 적합성은 도입 과정에서 검증해야 한다.
|
||||
audience:
|
||||
primary:
|
||||
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
|
||||
secondary:
|
||||
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
|
||||
reader-actions:
|
||||
- 30초 안에 템플릿의 차별점과 적합한 사용 상황을 판단한다.
|
||||
- 로컬 부트스트랩을 실행하고 성공 신호를 확인한다.
|
||||
- 변경할 코드를 올바른 모듈에 배치한다.
|
||||
- sample-portfolio를 제거해도 핵심 계약이 유지되는지 검증한다.
|
||||
- 상세 설정과 운영 문서의 위치를 찾는다.
|
||||
content-policy:
|
||||
language: ko
|
||||
tone: technical-direct
|
||||
target-length: medium
|
||||
preserve-existing-copy: false
|
||||
detail-docs-policy: summary-and-link
|
||||
visual-policy:
|
||||
mode: when-useful
|
||||
max-visuals: 1
|
||||
preferred-formats:
|
||||
- mermaid
|
||||
must-include:
|
||||
- 강제되는 아키텍처 규칙과 도입자가 선택해야 하는 정책의 구분
|
||||
- bootstrap이 수행하는 작업과 성공 신호
|
||||
- sample-portfolio의 참조 구현 역할과 제거 검증 방법
|
||||
- 모듈 의존 방향과 코드 배치 기준
|
||||
- 템플릿의 한계와 도입 전 결정 항목
|
||||
must-exclude:
|
||||
- 전체 환경 변수 목록
|
||||
- 공급망·릴리스 절차의 장황한 복제
|
||||
- 모든 어댑터의 구현 세부사항
|
||||
- 근거 없는 production-ready 주장
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
schema-version: 1
|
||||
repository-snapshot-hash: sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e
|
||||
project-name: ca-skeleton
|
||||
languages:
|
||||
- Java
|
||||
frameworks:
|
||||
- Spring Boot 4.0.0
|
||||
- Gradle
|
||||
facts:
|
||||
- id: F-README-CONTRACT-001
|
||||
category: readme-contract
|
||||
key: required-literals
|
||||
value:
|
||||
- ./gradlew bootstrap
|
||||
- GET /api/healthcheck
|
||||
- feature-developer-experience-contract
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DeveloperExperienceContractTest.java
|
||||
line-start: 52
|
||||
line-end: 64
|
||||
symbol: readmeCommandsAreVerifiedAndBootstrapIsTheFirstRunEntrypoint
|
||||
source-kind: executable-test-contract
|
||||
- id: F-IDENTITY-001
|
||||
category: adoption
|
||||
key: template-identifiers
|
||||
value:
|
||||
gradle-root-name: ca-skeleton
|
||||
java-package-root: dev.caskeleton
|
||||
main-class: dev.caskeleton.bootstrap.CaSkeletonApplication
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 5
|
||||
line-end: 5
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
|
||||
line-start: 1
|
||||
line-end: 20
|
||||
symbol: CaSkeletonApplication
|
||||
source-kind: implementation
|
||||
- id: F-STACK-001
|
||||
category: stack
|
||||
key: runtime-and-framework
|
||||
value:
|
||||
java: 21
|
||||
java-distribution: temurin-21.0.11+10
|
||||
spring-boot: 4.0.0
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: .tool-versions
|
||||
line-start: 1
|
||||
line-end: 1
|
||||
source-kind: tool-version-configuration
|
||||
- path: src/build.gradle
|
||||
line-start: 5
|
||||
line-end: 7
|
||||
source-kind: build-configuration
|
||||
- path: src/build.gradle
|
||||
line-start: 103
|
||||
line-end: 107
|
||||
source-kind: build-configuration
|
||||
- id: F-MODULES-001
|
||||
category: architecture
|
||||
key: declared-modules
|
||||
value:
|
||||
core: [domain-core, application-core, shared-contract]
|
||||
inbound: [web, grpc, graphql, websocket]
|
||||
outbound: [persistence-jpa, support, messaging, cache-redis, notification, objectstorage, fileserver, persistence-mongo, httpclient, identifier]
|
||||
composition: [app-bootstrap]
|
||||
sample: [sample-portfolio]
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 5
|
||||
line-end: 25
|
||||
source-kind: build-configuration
|
||||
- id: F-DEPENDENCY-POLICY-001
|
||||
category: architecture
|
||||
key: module-dependency-policy
|
||||
value: Gradle의 verifyCleanArchitectureDependencies가 모든 선언 모듈을 정책에 포함시키고 허용되지 않은 프로젝트 의존성을 실패시킨다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
symbol: verifyCleanArchitectureDependencies
|
||||
source-kind: executable-build-rule
|
||||
- id: F-CODE-BOUNDARY-001
|
||||
category: architecture
|
||||
key: code-boundary-policy
|
||||
value: ArchUnit 규칙이 application 패키지의 adapter·bootstrap·transport·persistence 의존과 Spring @Transactional 사용을 금지한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
|
||||
line-start: 197
|
||||
line-end: 228
|
||||
source-kind: executable-test-rule
|
||||
- id: F-CORE-001
|
||||
category: architecture
|
||||
key: core-responsibilities
|
||||
value:
|
||||
domain-core: 외부 라이브러리 의존성이 없는 도메인 모듈
|
||||
application-core: domain-core와 shared-contract를 의존하는 유스케이스 모듈
|
||||
shared-contract: 비즈니스 개념을 두지 않는 공용 운영 계약 모듈
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/domain-core/build.gradle
|
||||
line-start: 1
|
||||
line-end: 3
|
||||
source-kind: build-configuration
|
||||
- path: src/application-core/build.gradle
|
||||
line-start: 1
|
||||
line-end: 15
|
||||
source-kind: build-configuration
|
||||
- path: src/shared-contract/build.gradle
|
||||
line-start: 1
|
||||
line-end: 3
|
||||
source-kind: build-configuration
|
||||
- id: F-COMPOSITION-001
|
||||
category: architecture
|
||||
key: composition-root
|
||||
value: app-bootstrap가 core, inbound web, 여러 outbound adapter, shared-contract를 조립하고 Spring Boot main class를 지정한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 41
|
||||
line-end: 58
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 174
|
||||
line-end: 176
|
||||
source-kind: build-configuration
|
||||
- id: F-SAMPLE-001
|
||||
category: adoption
|
||||
key: sample-removal-contract
|
||||
value: sample-portfolio는 일반 테스트에서만 sampleFixture로 연결되며 sampleOffTest는 같은 핵심 테스트 스위트를 샘플 없이 컴파일하고 실행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 14
|
||||
line-end: 38
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 95
|
||||
line-end: 100
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
symbol: sampleOffTest
|
||||
source-kind: executable-build-rule
|
||||
- id: F-BOOTSTRAP-001
|
||||
category: command
|
||||
key: local-bootstrap-contract
|
||||
value: bootstrap은 전체 소스 컴파일, Docker daemon 확인, PostgreSQL 시작, 앱 빌드·시작, 샘플 격리 계약, HTTP smoke check를 순서대로 실행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 345
|
||||
line-end: 435
|
||||
symbol: bootstrap
|
||||
source-kind: executable-build-rule
|
||||
- id: F-HEALTH-001
|
||||
category: endpoint
|
||||
key: bootstrap-success-signal
|
||||
value: bootstrap smoke check는 http://localhost:8080/api/healthcheck의 HTTP 200 응답과 status UP을 요구한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 396
|
||||
line-end: 426
|
||||
symbol: bootstrapSmoke
|
||||
source-kind: executable-build-rule
|
||||
- path: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/controller/HealthcheckController.java
|
||||
line-start: 10
|
||||
line-end: 17
|
||||
symbol: HealthcheckController
|
||||
source-kind: implementation
|
||||
- id: F-CONTAINER-001
|
||||
category: runtime
|
||||
key: local-compose-topology
|
||||
value: 로컬 Compose 구성은 app과 PostgreSQL 16 서비스를 연결하고 named volume에 데이터베이스 데이터를 보존한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: docker-compose.yml
|
||||
line-start: 26
|
||||
line-end: 80
|
||||
source-kind: container-configuration
|
||||
- path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
source-kind: container-configuration
|
||||
- id: F-LOCKS-001
|
||||
category: verification
|
||||
key: dependency-lock-policy
|
||||
value: 각 leaf module은 모든 구성을 STRICT 모드로 잠그며 잠금 상태 검증 태스크가 실제 dependency resolution을 수행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 109
|
||||
line-end: 167
|
||||
source-kind: executable-build-rule
|
||||
- id: F-CHECK-001
|
||||
category: verification
|
||||
key: check-aggregation
|
||||
value: 각 leaf module의 check는 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, 격리 테스트 만료, README 명령 검증을 포함한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 273
|
||||
line-end: 280
|
||||
source-kind: executable-build-rule
|
||||
- path: src/build.gradle
|
||||
line-start: 437
|
||||
line-end: 550
|
||||
symbol: verifyReadmeCommands
|
||||
source-kind: executable-build-rule
|
||||
- id: F-VERIFICATION-COMMANDS-001
|
||||
category: command
|
||||
key: documented-verification-tasks
|
||||
value:
|
||||
- ./gradlew test
|
||||
- ./gradlew verifyCleanArchitectureDependencies
|
||||
- ./gradlew :app-bootstrap:sampleOffTest
|
||||
- ./gradlew check
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 247
|
||||
line-end: 280
|
||||
source-kind: executable-build-rule
|
||||
- path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
symbol: verifyCleanArchitectureDependencies
|
||||
source-kind: executable-build-rule
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
symbol: sampleOffTest
|
||||
source-kind: executable-build-rule
|
||||
- id: F-DOCS-001
|
||||
category: documentation
|
||||
key: detailed-documentation
|
||||
value: 빌드 루트, 핵심 모듈, inbound·outbound adapter에 각각 README가 있고 환경·운영 계약은 docs 아래 레지스트리와 runbook으로 분리되어 있다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/README.md
|
||||
source-kind: documentation-index
|
||||
- path: src/domain-core/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/application-core/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/adapter/inbound/web/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/adapter/outbound/persistence-jpa/README.md
|
||||
source-kind: module-documentation
|
||||
- path: docs/registries/env-keys.yaml
|
||||
source-kind: configuration-registry
|
||||
- path: docs/runbooks/template.md
|
||||
source-kind: runbook-template
|
||||
- id: F-TEMPLATE-LIMIT-001
|
||||
category: limitation
|
||||
key: adoption-decisions
|
||||
value: 템플릿은 여러 inbound·outbound adapter seam과 PostgreSQL 로컬 구성을 제공하지만 실제 서비스가 사용할 adapter와 배포 환경 선택은 도입자가 결정해야 한다.
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 7
|
||||
line-end: 25
|
||||
source-kind: build-configuration
|
||||
- path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
source-kind: container-configuration
|
||||
commands:
|
||||
- id: CMD-001
|
||||
command: ./gradlew bootstrap
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 431
|
||||
line-end: 435
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-002
|
||||
command: docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
cwd: .
|
||||
source:
|
||||
path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
verification:
|
||||
status: static-verified
|
||||
method: compose-file-discovery
|
||||
level: static
|
||||
- id: CMD-003
|
||||
command: ./gradlew test
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 247
|
||||
line-end: 251
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-lifecycle-task
|
||||
level: static
|
||||
- id: CMD-004
|
||||
command: ./gradlew :app-bootstrap:sampleOffTest
|
||||
cwd: src
|
||||
source:
|
||||
path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-005
|
||||
command: ./gradlew verifyCleanArchitectureDependencies
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-006
|
||||
command: ./gradlew check
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 273
|
||||
line-end: 280
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-lifecycle-task
|
||||
level: static
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git-sha": "dd20b5801b5ac2a9443b7e6c01f466b5ddc378e0",
|
||||
"dirty": true,
|
||||
"diff-hash": "sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e",
|
||||
"scanned-at": null,
|
||||
"file-count": 1163
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
schema-version: 1
|
||||
verdict: PASS
|
||||
score: 96
|
||||
scores:
|
||||
project-specificity:
|
||||
score: 5
|
||||
evidence:
|
||||
- "overview: Java 21, Spring Boot 4.0.0, 19개 모듈과 Gradle·ArchUnit 경계를 저장소 근거로 특정한다."
|
||||
- "project-value: STRICT dependency lock과 sampleOffTest처럼 ca-tmpl 고유의 계약을 전면에 둔다."
|
||||
reader-journey:
|
||||
score: 5
|
||||
evidence:
|
||||
- "overview → value → architecture → quick-start → adoption → verification → documentation 순서가 평가·실행·전환 흐름을 따른다."
|
||||
- "각 상세 항목은 독자의 다음 행동인 실행, 코드 배치, sample 제거, 전체 검증으로 이어진다."
|
||||
technical-explanation:
|
||||
score: 4
|
||||
evidence:
|
||||
- "architecture: Gradle 모듈 정책과 ArchUnit 코드 정책을 구분하고 의존 방향을 Mermaid와 모듈 표로 설명한다."
|
||||
- "중간 길이 정책에 맞춰 세부 운영 계약은 소유 문서로 넘기므로 README 자체는 의도적으로 개요 수준을 유지한다."
|
||||
task-usability:
|
||||
score: 5
|
||||
evidence:
|
||||
- "quick-start: prerequisite, 실행 위치, bootstrap 부작용, 성공 endpoint, Compose 종료 명령이 한 흐름에 있다."
|
||||
- "verification: 일반 테스트·아키텍처·sample 제거·전체 계약을 목적별로 선택할 수 있다."
|
||||
prose-clarity:
|
||||
score: 5
|
||||
evidence:
|
||||
- "긴 기능 나열을 피하고 강제 영역/선택 영역 표와 짧은 도입 순서로 압축했다."
|
||||
- "production-ready 같은 근거 없는 표현 없이 적용 한계와 별도 검증 책임을 명시한다."
|
||||
visual-judgment:
|
||||
score: 5
|
||||
evidence:
|
||||
- "architecture: 구성요소가 여섯 개인 의존 관계에만 Mermaid를 사용하고 런타임 호출도가 아님을 바로 설명한다."
|
||||
- "나머지 섹션은 표·명령·목록이 더 적합하다는 outline 근거에 따라 추가 시각물을 배제했다."
|
||||
hard-gates:
|
||||
passed: true
|
||||
failures: []
|
||||
reader-simulations:
|
||||
30-seconds:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "제목과 첫 두 문단에서 기술 기준, 템플릿의 차별점, 목적을 확인할 수 있다."
|
||||
- "개요의 적합/비적합 문장으로 채택 후보인지 빠르게 판단할 수 있다."
|
||||
5-minutes:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "강제/선택 표, 아키텍처, bootstrap, 성공 신호, 적용 한계가 첫 읽기 경로에 모두 있다."
|
||||
contributor:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "모듈 표와 도입 5단계가 코드 배치 및 sample 교체 경로를 제공한다."
|
||||
- "검증 명령과 목적별 상세 문서 링크가 다음 작업으로 연결된다."
|
||||
findings:
|
||||
- id: QR-003
|
||||
severity: minor
|
||||
category: repository-contract
|
||||
section: quick-start
|
||||
message: 저장소 테스트가 요구하는 README 호환성 literal 두 개가 최초 후보에서 누락됐다.
|
||||
evidence:
|
||||
- "sampleOffTest 실제 실행에서 DeveloperExperienceContractTest.java:62가 실패했다."
|
||||
- "테스트가 요구하는 세 literal을 readme-contract fact로 추가하고 execution gate가 검사하도록 개선했다."
|
||||
- "후보에 비가시적 호환성 marker를 추가해 독자용 문구를 왜곡하지 않았다."
|
||||
route-to: FACTS_EXTRACTED
|
||||
status: resolved
|
||||
- id: QR-001
|
||||
severity: minor
|
||||
category: command-boundary
|
||||
section: quick-start
|
||||
message: bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.
|
||||
evidence:
|
||||
- "첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다."
|
||||
- "수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
|
||||
route-to: README_DRAFTED
|
||||
status: resolved
|
||||
- id: QR-002
|
||||
severity: minor
|
||||
category: architecture-clarity
|
||||
section: overview
|
||||
message: 초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.
|
||||
evidence:
|
||||
- "초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다."
|
||||
- "실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
|
||||
route-to: README_DRAFTED
|
||||
status: resolved
|
||||
@@ -0,0 +1,15 @@
|
||||
# ca-tmpl README quality review
|
||||
|
||||
Verdict: **PASS (96/100)**
|
||||
|
||||
The candidate is specific to ca-tmpl, follows an evaluation-to-adoption reader
|
||||
journey, and keeps correctness separate from editorial scoring. Deterministic
|
||||
checks passed for request, facts, outline conformance, claim provenance, visual
|
||||
coherence, GitHub Markdown, documented commands, paths, and secret leakage.
|
||||
|
||||
The README deliberately stops at architecture and adoption guidance; detailed
|
||||
environment and operational contracts remain linked to their owning documents.
|
||||
Three minor issues found during real use—an inline command boundary, an
|
||||
ambiguous layer arrow, and a repository-owned README literal contract—were
|
||||
corrected before this PASS review. Required README literals are now represented
|
||||
as evidence-backed facts and enforced by the execution gate.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"run-id": "20260717-quality-core-r2",
|
||||
"repo-id": "ca-tmpl",
|
||||
"mode": "bootstrap",
|
||||
"target-repository": "/home/donghyeon/workspace/ca-tmpl",
|
||||
"harness-version": "0.1.0",
|
||||
"started-at": null,
|
||||
"tool-adapter": "codex",
|
||||
"input-hashes": {}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"mode": "bootstrap",
|
||||
"current": "APPLIED",
|
||||
"history": [
|
||||
{
|
||||
"state": "INITIALIZED"
|
||||
},
|
||||
{
|
||||
"state": "INPUT_CAPTURED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "request",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "REPOSITORY_SNAPSHOTTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "snapshot",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"diff-hash": "sha256:4d351050ecdfa25ea9e39b22c7c5bc0c686b60aa1f9fef7a02f86ae073151f1e"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "FACTS_EXTRACTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "facts",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"fact_ids": [
|
||||
"F-README-CONTRACT-001",
|
||||
"F-IDENTITY-001",
|
||||
"F-STACK-001",
|
||||
"F-MODULES-001",
|
||||
"F-DEPENDENCY-POLICY-001",
|
||||
"F-CODE-BOUNDARY-001",
|
||||
"F-CORE-001",
|
||||
"F-COMPOSITION-001",
|
||||
"F-SAMPLE-001",
|
||||
"F-BOOTSTRAP-001",
|
||||
"F-HEALTH-001",
|
||||
"F-CONTAINER-001",
|
||||
"F-LOCKS-001",
|
||||
"F-CHECK-001",
|
||||
"F-VERIFICATION-COMMANDS-001",
|
||||
"F-DOCS-001",
|
||||
"F-TEMPLATE-LIMIT-001"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "PROJECT_PROFILED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "profile",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"profile": "project-template"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "README_PLANNED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "brief",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "outline",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"section_ids": [
|
||||
"overview",
|
||||
"project-value",
|
||||
"architecture",
|
||||
"quick-start",
|
||||
"adoption",
|
||||
"verification",
|
||||
"documentation"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "README_DRAFTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "conformance",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"sections": [
|
||||
"overview",
|
||||
"project-value",
|
||||
"architecture",
|
||||
"quick-start",
|
||||
"adoption",
|
||||
"verification",
|
||||
"documentation"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "claim_map",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"claims": [
|
||||
"C-STACK-001",
|
||||
"C-POSITION-001",
|
||||
"C-MODULE-COUNT-001",
|
||||
"C-FORCED-DEPS-001",
|
||||
"C-FORCED-CODE-001",
|
||||
"C-FORCED-LOCKS-001",
|
||||
"C-FORCED-SAMPLE-001",
|
||||
"C-VISUAL-MEANING-001",
|
||||
"C-DOMAIN-001",
|
||||
"C-APPLICATION-001",
|
||||
"C-ADAPTERS-001",
|
||||
"C-SHARED-001",
|
||||
"C-BOOTSTRAP-MODULE-001",
|
||||
"C-SAMPLE-ROLE-001",
|
||||
"C-TWO-LAYERS-001",
|
||||
"C-PREREQUISITES-001",
|
||||
"C-BOOTSTRAP-COMMAND-001",
|
||||
"C-HEALTH-001",
|
||||
"C-BOOTSTRAP-SIDE-EFFECT-001",
|
||||
"C-CLEANUP-001",
|
||||
"C-IDENTITY-001",
|
||||
"C-ADOPT-SAMPLE-001",
|
||||
"C-NEW-MODULE-POLICY-001",
|
||||
"C-VERIFY-TEST-001",
|
||||
"C-VERIFY-ARCH-001",
|
||||
"C-VERIFY-SAMPLE-001",
|
||||
"C-VERIFY-CHECK-001",
|
||||
"C-CHECK-SCOPE-001",
|
||||
"C-DOCS-STRATEGY-001",
|
||||
"C-LIMIT-CHOICES-001",
|
||||
"C-LIMIT-LOCAL-001"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "VISUALS_PLANNED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "visual_plan",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"visuals": [
|
||||
"architecture-dependency-direction"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "STRUCTURALLY_VALIDATED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "github_markdown",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "TECHNICALLY_VERIFIED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "verify",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"schema-version": 1,
|
||||
"state": "PASS",
|
||||
"verification-level": "static",
|
||||
"execution-verified": false,
|
||||
"checks": {
|
||||
"commands": {
|
||||
"total": 6,
|
||||
"verified": 6,
|
||||
"manual-required": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"paths": {
|
||||
"total": 9,
|
||||
"verified": 9,
|
||||
"failed": 0
|
||||
},
|
||||
"anchors": {
|
||||
"total": 0,
|
||||
"verified": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"readme-contracts": {
|
||||
"total": 3,
|
||||
"verified": 3,
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
"failures": [],
|
||||
"limitations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "secret_scan",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "QUALITY_REVIEWED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "review",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"verdict": "PASS",
|
||||
"score": 96,
|
||||
"findings": [
|
||||
{
|
||||
"id": "QR-003",
|
||||
"severity": "minor",
|
||||
"category": "repository-contract",
|
||||
"section": "quick-start",
|
||||
"message": "저장소 테스트가 요구하는 README 호환성 literal 두 개가 최초 후보에서 누락됐다.",
|
||||
"evidence": [
|
||||
"sampleOffTest 실제 실행에서 DeveloperExperienceContractTest.java:62가 실패했다.",
|
||||
"테스트가 요구하는 세 literal을 readme-contract fact로 추가하고 execution gate가 검사하도록 개선했다.",
|
||||
"후보에 비가시적 호환성 marker를 추가해 독자용 문구를 왜곡하지 않았다."
|
||||
],
|
||||
"route-to": "FACTS_EXTRACTED",
|
||||
"status": "resolved"
|
||||
},
|
||||
{
|
||||
"id": "QR-001",
|
||||
"severity": "minor",
|
||||
"category": "command-boundary",
|
||||
"section": "quick-start",
|
||||
"message": "bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.",
|
||||
"evidence": [
|
||||
"첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다.",
|
||||
"수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
|
||||
],
|
||||
"route-to": "README_DRAFTED",
|
||||
"status": "resolved"
|
||||
},
|
||||
{
|
||||
"id": "QR-002",
|
||||
"severity": "minor",
|
||||
"category": "architecture-clarity",
|
||||
"section": "overview",
|
||||
"message": "초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.",
|
||||
"evidence": [
|
||||
"초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다.",
|
||||
"실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
|
||||
],
|
||||
"route-to": "README_DRAFTED",
|
||||
"status": "resolved"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "READY_FOR_APPLY",
|
||||
"gates": []
|
||||
},
|
||||
{
|
||||
"state": "APPLIED",
|
||||
"gates": []
|
||||
}
|
||||
],
|
||||
"rework": {
|
||||
"iterations": 0,
|
||||
"findings": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"state": "PASS",
|
||||
"verification-level": "static",
|
||||
"execution-verified": false,
|
||||
"checks": {
|
||||
"commands": {
|
||||
"total": 6,
|
||||
"verified": 6,
|
||||
"manual-required": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"paths": {
|
||||
"total": 9,
|
||||
"verified": 9,
|
||||
"failed": 0
|
||||
},
|
||||
"anchors": {
|
||||
"total": 0,
|
||||
"verified": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"readme-contracts": {
|
||||
"total": 3,
|
||||
"verified": 3,
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
"failures": [],
|
||||
"limitations": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
schema-version: 1
|
||||
visuals:
|
||||
- id: architecture-dependency-direction
|
||||
section: architecture
|
||||
type: architecture-diagram
|
||||
purpose: core, adapter, composition root 사이의 허용된 프로젝트 의존 방향을 한 화면에 설명한다.
|
||||
placeholder-text: Mermaid flowchart로 inbound와 outbound가 application을 거쳐 domain 방향으로 의존하고 app-bootstrap이 조립하는 관계를 표시한다.
|
||||
must-show:
|
||||
- domain-core
|
||||
- application-core
|
||||
- inbound adapters
|
||||
- outbound adapters
|
||||
- app-bootstrap
|
||||
- shared-contract
|
||||
relationships:
|
||||
- inbound adapters -> application-core
|
||||
- outbound adapters -> application-core
|
||||
- application-core -> domain-core
|
||||
- app-bootstrap -> selected adapters and core
|
||||
emphasize:
|
||||
- 화살표는 런타임 호출이 아니라 프로젝트 의존 방향임
|
||||
- core가 adapter를 알지 않음
|
||||
avoid:
|
||||
- 실제로 선언되지 않은 인프라 구성요소
|
||||
- 모든 adapter가 app-bootstrap에 연결된다는 과장
|
||||
- 장식용 아이콘과 색상 의존 의미
|
||||
placement:
|
||||
after-section-id: architecture
|
||||
accessibility:
|
||||
alt-text: inbound와 outbound adapter가 application-core와 domain-core 방향으로 의존하고 app-bootstrap이 선택 모듈을 조립하는 구조
|
||||
production:
|
||||
format: mermaid
|
||||
status: embedded
|
||||
@@ -0,0 +1,145 @@
|
||||
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
|
||||
|
||||
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
|
||||
|
||||
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
|
||||
|
||||
<!-- section-id: overview -->
|
||||
## 템플릿 개요
|
||||
|
||||
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
|
||||
|
||||
다음 상황에 특히 잘 맞습니다.
|
||||
|
||||
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
|
||||
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
|
||||
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
|
||||
|
||||
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
|
||||
|
||||
<!-- section-id: project-value -->
|
||||
## 저장소가 강제하는 것, 도입자가 결정할 것
|
||||
|
||||
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
|
||||
| --- | --- |
|
||||
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
|
||||
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
|
||||
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
|
||||
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
|
||||
|
||||
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
|
||||
|
||||
<!-- section-id: architecture -->
|
||||
## 아키텍처와 코드 배치
|
||||
|
||||
<!-- visual-id: architecture-dependency-direction -->
|
||||
|
||||
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
|
||||
Application[application-core<br/>use cases · ports]
|
||||
Domain[domain-core<br/>business invariants]
|
||||
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
|
||||
Shared[shared-contract<br/>operational contracts]
|
||||
Bootstrap[app-bootstrap<br/>composition root]
|
||||
|
||||
Inbound --> Application
|
||||
Outbound --> Application
|
||||
Application --> Domain
|
||||
Inbound --> Shared
|
||||
Outbound --> Shared
|
||||
Application --> Shared
|
||||
Bootstrap --> Inbound
|
||||
Bootstrap --> Outbound
|
||||
Bootstrap --> Application
|
||||
Bootstrap --> Domain
|
||||
Bootstrap --> Shared
|
||||
```
|
||||
|
||||
| 모듈 그룹 | 코드 배치 기준 |
|
||||
| --- | --- |
|
||||
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
|
||||
| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
|
||||
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
|
||||
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
|
||||
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
|
||||
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
|
||||
|
||||
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
|
||||
|
||||
<!-- section-id: quick-start -->
|
||||
## 빠른 시작
|
||||
|
||||
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
저장소 루트에서 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew bootstrap
|
||||
```
|
||||
|
||||
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
|
||||
|
||||
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
|
||||
|
||||
bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
|
||||
|
||||
<!-- section-id: adoption -->
|
||||
## 실제 프로젝트로 전환하기
|
||||
|
||||
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
|
||||
|
||||
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
|
||||
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
|
||||
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
|
||||
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
|
||||
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
|
||||
|
||||
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
|
||||
|
||||
<!-- section-id: verification -->
|
||||
## 검증 루프
|
||||
|
||||
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
|
||||
|
||||
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
|
||||
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
|
||||
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
|
||||
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew test
|
||||
./gradlew verifyCleanArchitectureDependencies
|
||||
./gradlew :app-bootstrap:sampleOffTest
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
|
||||
|
||||
<!-- section-id: documentation -->
|
||||
## 상세 문서 지도와 적용 한계
|
||||
|
||||
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
|
||||
|
||||
- [빌드·실행·환경 설정](src/README.md)
|
||||
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
|
||||
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
|
||||
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
|
||||
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
|
||||
|
||||
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
|
||||
|
||||
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
|
||||
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
|
||||
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
|
||||
@@ -0,0 +1,145 @@
|
||||
# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
|
||||
|
||||
`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
|
||||
|
||||
이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
|
||||
|
||||
<!-- section-id: overview -->
|
||||
## 템플릿 개요
|
||||
|
||||
19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
|
||||
|
||||
다음 상황에 특히 잘 맞습니다.
|
||||
|
||||
- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
|
||||
- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
|
||||
- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
|
||||
|
||||
반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
|
||||
|
||||
<!-- section-id: project-value -->
|
||||
## 저장소가 강제하는 것, 도입자가 결정할 것
|
||||
|
||||
| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
|
||||
| --- | --- |
|
||||
| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
|
||||
| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
|
||||
| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
|
||||
| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
|
||||
|
||||
이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
|
||||
|
||||
<!-- section-id: architecture -->
|
||||
## 아키텍처와 코드 배치
|
||||
|
||||
<!-- visual-id: architecture-dependency-direction -->
|
||||
|
||||
다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
|
||||
Application[application-core<br/>use cases · ports]
|
||||
Domain[domain-core<br/>business invariants]
|
||||
Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
|
||||
Shared[shared-contract<br/>operational contracts]
|
||||
Bootstrap[app-bootstrap<br/>composition root]
|
||||
|
||||
Inbound --> Application
|
||||
Outbound --> Application
|
||||
Application --> Domain
|
||||
Inbound --> Shared
|
||||
Outbound --> Shared
|
||||
Application --> Shared
|
||||
Bootstrap --> Inbound
|
||||
Bootstrap --> Outbound
|
||||
Bootstrap --> Application
|
||||
Bootstrap --> Domain
|
||||
Bootstrap --> Shared
|
||||
```
|
||||
|
||||
| 모듈 그룹 | 코드 배치 기준 |
|
||||
| --- | --- |
|
||||
| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
|
||||
| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
|
||||
| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
|
||||
| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
|
||||
| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
|
||||
| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
|
||||
|
||||
Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
|
||||
|
||||
<!-- section-id: quick-start -->
|
||||
## 빠른 시작
|
||||
|
||||
필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
저장소 루트에서 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew bootstrap
|
||||
```
|
||||
|
||||
`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
|
||||
|
||||
성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
|
||||
|
||||
bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
|
||||
|
||||
<!-- section-id: adoption -->
|
||||
## 실제 프로젝트로 전환하기
|
||||
|
||||
한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
|
||||
|
||||
1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
|
||||
2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
|
||||
3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
|
||||
4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
|
||||
5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
|
||||
|
||||
도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
|
||||
|
||||
<!-- section-id: verification -->
|
||||
## 검증 루프
|
||||
|
||||
작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
|
||||
|
||||
- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
|
||||
- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
|
||||
- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
|
||||
- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew test
|
||||
./gradlew verifyCleanArchitectureDependencies
|
||||
./gradlew :app-bootstrap:sampleOffTest
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
|
||||
|
||||
<!-- section-id: documentation -->
|
||||
## 상세 문서 지도와 적용 한계
|
||||
|
||||
README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
|
||||
|
||||
- [빌드·실행·환경 설정](src/README.md)
|
||||
- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
|
||||
- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
|
||||
- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
|
||||
- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
|
||||
|
||||
도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
|
||||
|
||||
- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
|
||||
- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
|
||||
- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
|
||||
@@ -0,0 +1,451 @@
|
||||
--- README.md (current)
|
||||
+++ README.md (candidate)
|
||||
@@ -1,346 +1,145 @@
|
||||
-# Clean Architecture Spring Boot 템플릿
|
||||
+# ca-tmpl — 경계를 실행 가능한 규칙으로 만드는 Spring Boot 템플릿
|
||||
|
||||
-실무 백엔드팀용 Clean Architecture 부트스트랩.
|
||||
+`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다. <!-- claim-id: C-STACK-001 -->
|
||||
|
||||
-Java 21, Spring Boot 3.5, Gradle 멀티모듈 기반의 Clean Architecture 템플릿입니다.
|
||||
+이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다. <!-- claim-id: C-POSITION-001 -->
|
||||
|
||||
-이 저장소는 실무 프로젝트를 시작할 때 가져와서 도메인과 비즈니스 유스케이스를 바로 추가해 사용할 수 있는 스켈레톤을 목표로 합니다. 기본 패키지는 `dev.caskeleton`이며, 예시 코드는 production 모듈이 아니라 `sample-portfolio` 모듈(WorkLog 엔지니어링 작업 기록 게시판)에 격리합니다.
|
||||
+<!-- section-id: overview -->
|
||||
+## 템플릿 개요
|
||||
|
||||
-## 퀵스타트
|
||||
+19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다. <!-- claim-id: C-MODULE-COUNT-001 -->
|
||||
|
||||
-```bash
|
||||
-cd src
|
||||
-./gradlew bootstrap
|
||||
-curl -fsS http://localhost:8080/api/healthcheck
|
||||
+다음 상황에 특히 잘 맞습니다.
|
||||
+
|
||||
+- 새 Java 서비스에서 모듈 경계와 검증 기준을 함께 시작하려는 경우
|
||||
+- HTTP·메시징·캐시·영속성 같은 기술 세부사항을 유스케이스와 분리하려는 경우
|
||||
+- 예제 코드를 제거한 뒤에도 핵심 구조가 독립적으로 성립하는지 자동 검증하려는 경우
|
||||
+
|
||||
+반대로 단일 모듈 CRUD 예제나 특정 조직의 운영 정책까지 완성된 배포판이 필요하다면, 이 템플릿의 범위보다 가벼운 시작점 또는 별도의 플랫폼 기준이 더 적합할 수 있습니다.
|
||||
+
|
||||
+<!-- section-id: project-value -->
|
||||
+## 저장소가 강제하는 것, 도입자가 결정할 것
|
||||
+
|
||||
+| 저장소가 실행 가능하게 강제하는 것 | 도입자가 서비스 맥락에 맞게 결정할 것 |
|
||||
+| --- | --- |
|
||||
+| 모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다. <!-- claim-id: C-FORCED-DEPS-001 --> | 실제 도메인 경계와 bounded context |
|
||||
+| application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다. <!-- claim-id: C-FORCED-CODE-001 --> | 사용할 inbound·outbound adapter의 범위 |
|
||||
+| leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다. <!-- claim-id: C-FORCED-LOCKS-001 --> | 배포 플랫폼, SLO, 용량과 장애 복구 정책 |
|
||||
+| 같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다. <!-- claim-id: C-FORCED-SAMPLE-001 --> | 인증·인가, 데이터 보존, 외부 연동의 서비스별 정책 |
|
||||
+
|
||||
+이 구분이 중요합니다. 템플릿은 “어떤 결정을 해야 하는가”와 경계를 지키는 장치를 제공하지만, 서비스 고유의 결정을 대신하지는 않습니다.
|
||||
+
|
||||
+<!-- section-id: architecture -->
|
||||
+## 아키텍처와 코드 배치
|
||||
+
|
||||
+<!-- visual-id: architecture-dependency-direction -->
|
||||
+
|
||||
+다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다. <!-- claim-id: C-VISUAL-MEANING-001 -->
|
||||
+
|
||||
+```mermaid
|
||||
+flowchart LR
|
||||
+ Inbound[Inbound adapters<br/>web · gRPC · GraphQL · WebSocket]
|
||||
+ Application[application-core<br/>use cases · ports]
|
||||
+ Domain[domain-core<br/>business invariants]
|
||||
+ Outbound[Outbound adapters<br/>persistence · messaging · cache · integrations]
|
||||
+ Shared[shared-contract<br/>operational contracts]
|
||||
+ Bootstrap[app-bootstrap<br/>composition root]
|
||||
+
|
||||
+ Inbound --> Application
|
||||
+ Outbound --> Application
|
||||
+ Application --> Domain
|
||||
+ Inbound --> Shared
|
||||
+ Outbound --> Shared
|
||||
+ Application --> Shared
|
||||
+ Bootstrap --> Inbound
|
||||
+ Bootstrap --> Outbound
|
||||
+ Bootstrap --> Application
|
||||
+ Bootstrap --> Domain
|
||||
+ Bootstrap --> Shared
|
||||
```
|
||||
|
||||
-`bootstrap`은 compile sanity, PostgreSQL Compose 기동, 애플리케이션 이미지 build/start, sample 격리 검증, `/api/healthcheck` smoke를 한 번에 실행합니다.
|
||||
+| 모듈 그룹 | 코드 배치 기준 |
|
||||
+| --- | --- |
|
||||
+| `domain-core` | 외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다. <!-- claim-id: C-DOMAIN-001 --> |
|
||||
+| `application-core` | `domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다. <!-- claim-id: C-APPLICATION-001 --> |
|
||||
+| `adapter:inbound:*` / `adapter:outbound:*` | 전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다. <!-- claim-id: C-ADAPTERS-001 --> |
|
||||
+| `shared-contract` | 비즈니스 개념이 아닌 공용 운영 계약을 둡니다. <!-- claim-id: C-SHARED-001 --> |
|
||||
+| `app-bootstrap` | 선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다. <!-- claim-id: C-BOOTSTRAP-MODULE-001 --> |
|
||||
+| `sample-portfolio` | 템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다. <!-- claim-id: C-SAMPLE-ROLE-001 --> |
|
||||
|
||||
-## 주요 제어 변수
|
||||
+Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다. <!-- claim-id: C-TWO-LAYERS-001 -->
|
||||
|
||||
-전체 목록의 SSOT는 [docs/registries/env-keys.yaml](docs/registries/env-keys.yaml)과 [src/.env](src/.env)입니다. README에는 fork 초기에 자주 바꾸는 제어 변수만 요약합니다.
|
||||
+<!-- section-id: quick-start -->
|
||||
+## 빠른 시작
|
||||
|
||||
-| 변수 | 기본값 | 영향 범위 | 조정 시점 |
|
||||
-| --- | --- | --- | --- |
|
||||
-| `APP_NAME` | `ca-skeleton` | Spring app name, JSON log app field | 서비스명 변경 |
|
||||
-| `SPRING_PROFILES_ACTIVE` | `local` | profile-specific settings | local/dev/stage/prod 전환 |
|
||||
-| `PRESENTATION_API_BASE_PATH` | `/api` | public API prefix | `/v1` 등 버전 prefix 도입 |
|
||||
-| `APP_SERVER_PORT` | `8080` | HTTP server port | 포트 충돌 또는 배포 표준 |
|
||||
-| `MANAGEMENT_SERVER_PORT` | `9001` | actuator/management port | 운영망 분리 |
|
||||
-| `SECURITY_PUBLIC_PATHS` | `/api/healthcheck` | `permitAll()` 공개 경로 | 공개 endpoint 변경, snapshot 승인 필요 |
|
||||
-| `APP_MIGRATION_ON_STARTUP` | `true` | startup Flyway migration | 배포 파이프라인이 migration을 별도 수행할 때 |
|
||||
-| `APP_MULTI_INSTANCE_ENABLED` | `false` | lock/cache/leader/rate-limit/migration 협조 빈 fail-fast | 다중 인스턴스 운영 |
|
||||
-| `APP_RATE_LIMIT_ENABLED` | `true` | fixed-window rate-limit interceptor | 공개 API rate-limit 정책 |
|
||||
-| `APP_IDEMPOTENCY_TTL` | `24h` | idempotency record retention | 장기 실행 use case |
|
||||
-| `APP_OUTBOUND_HTTP_GLOBAL_CALL_TIMEOUT` | `10s` | outbound HTTP 전체 마감 시간 | 외부 SLA에 맞춘 latency budget |
|
||||
-| `APP_CACHE_REDIS_ENABLED` | `false` | Redis cache adapter 등록 | Redis 도입 |
|
||||
-| `APP_MESSAGING_BROKER` | 빈 값 | messaging adapter 활성화 | Kafka 등 메시징 도입 |
|
||||
+필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다. <!-- claim-id: C-PREREQUISITES-001 -->
|
||||
|
||||
-## 모듈 경계
|
||||
-
|
||||
-```text
|
||||
-app-bootstrap -> adapter:inbound:* / adapter:outbound:* -> application-core -> domain-core
|
||||
-adapter:outbound:messaging / cache-redis / notification / httpclient -> adapter:outbound:support
|
||||
-runtime modules -> shared-contract
|
||||
-```
|
||||
-
|
||||
-`adapter:inbound:*`/`adapter:outbound:*`는 `adapter:inbound:web`, `adapter:outbound:persistence-jpa`, `adapter:outbound:support`, `adapter:outbound:messaging`, `adapter:outbound:cache-redis`, `adapter:outbound:notification`, `adapter:outbound:httpclient`, `adapter:outbound:identifier`를 가리킵니다(`:adapter`, `:adapter:inbound`, `:adapter:outbound`는 소스 없는 그룹 컨테이너). 영속 계층은 옛 rdbms 베이스 + PostgreSQL 벤더 어댑터를 하나의 모듈로 합쳐, PostgreSQL 전용 코드는 `.postgresql` 서브패키지로 격리합니다. outbound 기술 어댑터(messaging/cache-redis/notification/httpclient) 넷은 공유 베이스인 `adapter:outbound:support`(correlation, fail-open 의존성 로깅, `@Configuration` seam)에 의존합니다.
|
||||
-
|
||||
-| 모듈 | 책임 | 금지 |
|
||||
-| --- | --- | --- |
|
||||
-| `domain-core` | 엔티티, 값 객체, enum, repository port, 비즈니스 불변식 | Spring, JPA, HTTP, DB, cloud SDK |
|
||||
-| `application-core` | 유스케이스, command/query, 애플리케이션 예외, 트랜잭션 경계(`TransactionPort`) | controller DTO, JPA entity, Spring Data repository, adapter 구현체 |
|
||||
-| `adapter:inbound:web` | HTTP endpoint, request/response DTO, validation, security/error mapping | persistence/outbound adapter 직접 의존, repository 직접 호출 |
|
||||
-| `adapter:outbound:persistence-jpa` | RDBMS/JPA 영속 — JPA entity, Spring Data repository, mapper, `TransactionPort` 구현, auditing, PostgreSQL 벤더 코드(`.postgresql` 서브패키지: native SQL, PG SQLState 매핑, Flyway PG 마이그레이션, PG 드라이버) | controller DTO, web adapter, 유스케이스 흐름 |
|
||||
-| `adapter:outbound:support` | outbound 기술 어댑터 공유 베이스 — correlation, fail-open 의존성 로깅, `@Configuration` seam | web/persistence adapter 직접 의존, 유스케이스 흐름 |
|
||||
-| `adapter:outbound:messaging` / `cache-redis` / `notification` / `httpclient` | 외부 messaging, cache, notification, 아웃바운드 HTTP client adapter (포트 뒤 선택형, 기본 비활성) | web/persistence adapter 직접 의존, 유스케이스 흐름 |
|
||||
-| `adapter:outbound:identifier` | 비-IO 인프라 능력 — ULID 생성/코덱, clock·crypto kind (외부 연동 없음) | 외부 IO(HTTP/messaging/cache/DB), 다른 adapter·bootstrap 의존 |
|
||||
-| `shared-contract` | response/error/header/logging/tracing/metrics/registry/annotation 같은 운영 계약 | business/domain concept |
|
||||
-| `sample-portfolio` | 샘플/fixture 소비자 모듈 (WorkLog 게시판 참조 구현) | production module에서 의존 |
|
||||
-| `app-bootstrap` | Spring Boot entrypoint, runtime composition, settings/logging bootstrap | 비즈니스 정책 |
|
||||
-
|
||||
-아키텍처 규칙은 두 단계로 검증합니다.
|
||||
-
|
||||
-- `src/app-bootstrap/src/test/java/.../CleanArchitectureTest.java`가 ArchUnit으로 소스 의존성을 검사합니다.
|
||||
-- `./gradlew verifyCleanArchitectureDependencies`가 Gradle 프로젝트 의존성을 검사합니다.
|
||||
-
|
||||
-## 새 프로젝트 시작 절차
|
||||
-
|
||||
-1. `src/settings.gradle`의 프로젝트명을 변경합니다.
|
||||
-
|
||||
-```gradle
|
||||
-rootProject.name = 'your-service-name'
|
||||
-```
|
||||
-
|
||||
-2. Java 패키지 루트를 변경합니다.
|
||||
-
|
||||
-```bash
|
||||
-cd src
|
||||
-find . -type f -name '*.java' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
|
||||
-find . -type f -name '*.gradle' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
|
||||
-find . -type f -name '*.yml' -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
|
||||
-```
|
||||
-
|
||||
-3. `CaSkeletonApplication`을 새 애플리케이션 이름으로 변경하고, `app-bootstrap/build.gradle`의 `bootJar.mainClass`도 함께 수정합니다.
|
||||
-
|
||||
-4. 목표 도메인을 production 모듈에 추가합니다. 기존 예시는 `sample-portfolio`에만 둡니다.
|
||||
-
|
||||
-| Production 모듈 위치 | 추가 대상 |
|
||||
-| --- | --- |
|
||||
-| `domain-core/src/main/java/.../domain` | 목표 도메인의 엔티티, 값 객체, repository port |
|
||||
-| `application-core/src/main/java/.../application` | 유스케이스 command/query/service |
|
||||
-| `adapter/outbound/persistence-jpa/src/main/java/.../adapter/outbound/persistence` | JPA entity, repository adapter, mapper |
|
||||
-| `adapter/outbound/persistence-jpa/src/main/java/.../adapter/outbound/persistence/postgresql` | 벤더 전용 SQL·SQLState 매핑·Flyway 마이그레이션 (PostgreSQL) |
|
||||
-| `adapter/inbound/web/src/main/java/.../adapter/inbound/web` | transport endpoint, request/response DTO |
|
||||
-| `adapter/outbound/{messaging,cache-redis,notification,httpclient}/src/main/java/.../adapter/outbound/*` | 외부 HTTP client, messaging, cache, notification adapter |
|
||||
-| `adapter/outbound/identifier/src/main/java/.../adapter/outbound/identifier` | ULID 등 비-IO 인프라 능력 어댑터 |
|
||||
-
|
||||
-새 비즈니스 규칙은 `domain-core`에서 시작하고, 유스케이스는 `application-core`, 외부 기술 연동은 `adapter:outbound:persistence-jpa` 또는 `adapter:outbound:*`(messaging/cache-redis/notification/httpclient), endpoint는 `adapter:inbound:web`에서 연결합니다.
|
||||
-
|
||||
-5. 새 서비스 기준으로 env 값과 README 내용을 갱신합니다.
|
||||
-
|
||||
-6. sample-on과 sample-off 검증을 모두 실행합니다.
|
||||
-
|
||||
-```bash
|
||||
-cd src
|
||||
-./gradlew test
|
||||
-./gradlew :app-bootstrap:sampleOffTest
|
||||
-```
|
||||
-
|
||||
-### Sample 사용 방식
|
||||
-
|
||||
-`sample-portfolio`은 템플릿에서 삭제하는 임시 코드가 아니라 구조와 운영 규칙을 검증하는
|
||||
-fixture/reference 모듈입니다. production 모듈은 이 모듈에 의존하지 않으며,
|
||||
-`app-bootstrap`의 일반 테스트만 `sampleFixture` 구성으로 참조 구현을 분석합니다. production
|
||||
-runtime에는 sample bean이나 endpoint가 포함되지 않으므로 `APP_SAMPLE_ENABLED` 같은 runtime
|
||||
-toggle도 두지 않습니다.
|
||||
-
|
||||
-새 프로젝트 도입은 두 단계로 진행합니다.
|
||||
-
|
||||
-1. 목표 도메인을 production 모듈 경계에 추가하되 `dev.caskeleton.sample.portfolio` 타입을 import하지
|
||||
- 않습니다. 상세 모듈 배치와 read/write 차이는 production 모듈의 경계 규칙과 sample 격리 방식을
|
||||
- 따르며, 이 README에는 도입 절차만 요약합니다.
|
||||
-2. `./gradlew test`(sample-on)와 `./gradlew :app-bootstrap:sampleOffTest`(sample-off)를 모두 통과시킵니다.
|
||||
- 다운스트림 fork에서 fixture가 더는 필요 없을 때만 `sample-portfolio` 정리를 선택할 수 있습니다.
|
||||
-
|
||||
-error/env/header/log/metric/capability 규칙을 바꾸면 관련 registry 문서도 함께 갱신해야 합니다.
|
||||
-두 검증 축은 [.github/workflows/ci-quality-gates.yml](.github/workflows/ci-quality-gates.yml)의
|
||||
-release gate에 모두 연결됩니다.
|
||||
-
|
||||
-## 로컬 실행
|
||||
-
|
||||
-지원 환경은 Linux(Ubuntu 22.04+), macOS(Apple Silicon 우선), Windows WSL2입니다. Java는 루트
|
||||
-`.tool-versions`에 고정한 Temurin 21을 사용하며 Gradle은 저장소 wrapper를 사용합니다. Docker
|
||||
-Desktop 또는 Docker Engine/Compose plugin이 실행 중이어야 합니다.
|
||||
-
|
||||
-첫 실행 진입점은 하나입니다.
|
||||
+저장소 루트에서 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew bootstrap
|
||||
```
|
||||
|
||||
-`bootstrap`은 다음 다섯 단계를 순서대로 실행합니다.
|
||||
+`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다. <!-- claim-id: C-BOOTSTRAP-COMMAND-001 -->
|
||||
|
||||
-1. 모든 production/test 소스 compile sanity
|
||||
-2. `docker-compose.yml` + `docker-compose.local.yml`의 PostgreSQL 기동
|
||||
-3. 애플리케이션 이미지 빌드·기동 및 startup Flyway 완료 확인
|
||||
-4. sample production 격리/build 검증
|
||||
-5. `GET /api/healthcheck` HTTP 200 + `status=UP` smoke
|
||||
+성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다. <!-- claim-id: C-HEALTH-001 -->
|
||||
|
||||
-Docker daemon, DB, Flyway, sample 검증, HTTP smoke는 각각 별도 Gradle task라 실패 단계가 task
|
||||
-이름으로 드러납니다. 로컬 stack을 종료할 때는 저장소 루트에서 실행합니다.
|
||||
+bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다. 작업을 마치면 저장소 루트에서 종료합니다. <!-- claim-id: C-BOOTSTRAP-SIDE-EFFECT-001 -->
|
||||
|
||||
```bash
|
||||
+cd ..
|
||||
docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
-`src/.env`는 커밋된 안전 기본값이며,
|
||||
-별도 `.env.example`을 만들지 않습니다. 이 README는 실행 진입점만 제공하고, 세부 설정 설명은
|
||||
-[src/README.md](src/README.md)에 둡니다. 이 로컬 개발 환경 구성 및 도구 설정은 `feature-developer-experience-contract`를 따릅니다.
|
||||
+위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다. <!-- claim-id: C-CLEANUP-001 -->
|
||||
|
||||
-기본 공개 health endpoint는 다음과 같습니다.
|
||||
+<!-- section-id: adoption -->
|
||||
+## 실제 프로젝트로 전환하기
|
||||
|
||||
-```text
|
||||
-GET /api/healthcheck
|
||||
-```
|
||||
+한 번에 모든 이름과 모듈을 지우기보다, 각 단계에서 검증 가능한 상태를 유지하는 편이 안전합니다.
|
||||
|
||||
-이 endpoint는 의도적으로 커스텀 컨트롤러로 유지합니다. 일반 API와 같은 MVC 경로, request logging filter, security public-path mapping, error handling 흐름을 확인하기 위한 endpoint입니다.
|
||||
+1. **식별자를 먼저 정합니다.** Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다. <!-- claim-id: C-IDENTITY-001 -->
|
||||
+2. **도메인과 유스케이스를 core에 세웁니다.** 비즈니스 불변식은 `domain-core`, 유스케이스와 port는 `application-core`에 둡니다.
|
||||
+3. **필요한 adapter만 선택합니다.** 전송 기술은 inbound, 데이터베이스·메시징·캐시·외부 연동은 outbound 모듈에서 선택하고 `app-bootstrap`에서 조립합니다.
|
||||
+4. **환경·운영 계약을 서비스 기준으로 확정합니다.** 환경 키 레지스트리와 Compose 기본값을 검토하되, 조직의 secret 관리·배포·관측 정책을 별도로 적용합니다.
|
||||
+5. **sample을 제거하고 독립성을 확인합니다.** `sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다. <!-- claim-id: C-ADOPT-SAMPLE-001 -->
|
||||
|
||||
-### Testcontainers 로컬 reuse (선택)
|
||||
+도입 중 코드의 위치가 애매하면 “이 코드는 비즈니스 규칙인가, 유스케이스 조정인가, 기술 구현인가, 조립인가?”를 먼저 묻고 위 모듈 표에 배치하십시오. 새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다. <!-- claim-id: C-NEW-MODULE-POLICY-001 -->
|
||||
|
||||
-CI는 container reuse를 항상 끕니다. 로컬에서만 반복 integration test 기동 시간을 줄이려면
|
||||
-`testcontainers.properties.example`을 `~/.testcontainers.properties`로 복사하고
|
||||
-`TESTCONTAINERS_REUSE_ENABLE=true`를 설정합니다. 두 조건이 모두 있어야 reuse가 켜집니다.
|
||||
-실험적 reuse container는 테스트 종료 후 남을 수 있으므로 작업이 끝나면 직접 정리합니다.
|
||||
+<!-- section-id: verification -->
|
||||
+## 검증 루프
|
||||
|
||||
-## 테스트
|
||||
+작업 목적에 맞는 가장 작은 검증부터 실행하고, 변경을 공유하기 전 전체 계약으로 넓힙니다.
|
||||
|
||||
-집중 검증:
|
||||
-
|
||||
-```bash
|
||||
-cd src
|
||||
-./gradlew :app-bootstrap:test --tests dev.caskeleton.bootstrap.architecture.CleanArchitectureTest
|
||||
-./gradlew verifyCleanArchitectureDependencies
|
||||
-./gradlew :adapter:inbound:web:test --tests '*SettingsTest'
|
||||
-```
|
||||
-
|
||||
-전체 검증:
|
||||
+- 일반 테스트: `./gradlew test` <!-- claim-id: C-VERIFY-TEST-001 -->
|
||||
+- 모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies` <!-- claim-id: C-VERIFY-ARCH-001 -->
|
||||
+- sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest` <!-- claim-id: C-VERIFY-SAMPLE-001 -->
|
||||
+- 전체 품질 계약: `./gradlew check` <!-- claim-id: C-VERIFY-CHECK-001 -->
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew test
|
||||
-```
|
||||
-
|
||||
-표준 check lifecycle:
|
||||
-
|
||||
-```bash
|
||||
-cd src
|
||||
+./gradlew verifyCleanArchitectureDependencies
|
||||
+./gradlew :app-bootstrap:sampleOffTest
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
-## 환경 변수 규칙
|
||||
+`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다. <!-- claim-id: C-CHECK-SCOPE-001 -->
|
||||
|
||||
-`src/.env`는 커밋되는 템플릿이자 로컬 기본값입니다. 모든 외부 설정 키와 허용 값을 문서화합니다.
|
||||
+<!-- section-id: documentation -->
|
||||
+## 상세 문서 지도와 적용 한계
|
||||
|
||||
-권장 규칙:
|
||||
+README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오. <!-- claim-id: C-DOCS-STRATEGY-001 -->
|
||||
|
||||
-| 파일 | 목적 | 커밋 여부 |
|
||||
-| ---------------- | ---------------------------------- | --------------------- |
|
||||
-| `src/.env` | 안전한 템플릿과 로컬 기본값 | 예 |
|
||||
-| `src/.env.local` | 개발자 개인 장비 오버라이드 | 아니오 |
|
||||
-| `src/.env.dev` | 공유 개발 환경 예시 또는 배포 입력 | 프로젝트 선택 |
|
||||
-| `src/.env.prod` | 운영 배포 입력 | secret 포함 시 아니오 |
|
||||
+- [빌드·실행·환경 설정](src/README.md)
|
||||
+- [도메인 모듈](src/domain-core/README.md) · [애플리케이션 모듈](src/application-core/README.md) · [composition root](src/app-bootstrap/README.md)
|
||||
+- [Web inbound adapter](src/adapter/inbound/web/README.md) · [JPA outbound adapter](src/adapter/outbound/persistence-jpa/README.md)
|
||||
+- [sample-portfolio 참조 구현](src/sample-portfolio/README.md)
|
||||
+- [환경 키 레지스트리](docs/registries/env-keys.yaml) · [runbook 템플릿](docs/runbooks/template.md)
|
||||
|
||||
-실제 secret은 커밋하지 않습니다. 운영 환경에서는 배포 플랫폼의 환경 변수 주입을 우선합니다.
|
||||
+도입 전에 다음 한계를 명시적으로 받아들이거나 보완해야 합니다.
|
||||
|
||||
-## 운영 기본기
|
||||
-
|
||||
-- Healthcheck: `HealthcheckController`가 `/healthcheck`를 제공하며, `PRESENTATION_API_BASE_PATH`에 따라 prefix가 붙습니다.
|
||||
-- Error response: production 공통 계약은 `shared-contract`에 두고, 샘플의 도메인 예외 매핑(`DomainExceptionHandler` / `PortfolioErrorCode`)은 `sample-portfolio`에 격리합니다.
|
||||
-- Request logs: `RequestLoggingFilter`가 `X-Request-Id`를 설정하고, MDC `traceId`로 복사하며, method/path/status/duration을 기록합니다.
|
||||
-- Settings: typed `@ConfigurationProperties` record가 각 모듈 가까이에서 설정을 검증하거나 안전한 기본값으로 보정합니다.
|
||||
-- Security: stateless OAuth2 resource-server 구성을 기본으로 하며, 공개 경로는 `SECURITY_PUBLIC_PATHS`로 명시합니다.
|
||||
-- Dependency vulnerability: Trivy SCA 스캔(PR + 일일 재스캔) · `dependency-review`(PR) · Renovate 보안 업데이트로 의존성 CVE/라이선스를 관리합니다. severity 기준(CVSS v3.1 ≥High 차단)·KEV·라이선스·SLA 정책은 [.github/dependency-vulnerability-policy.md](.github/dependency-vulnerability-policy.md)에, suppression 사유·만료일 강제는 `verifyTrivyignore` 게이트에 있습니다.
|
||||
-- Database: JPA/영속은 `adapter:outbound:persistence-jpa`(벤더 코드는 `.postgresql` 서브패키지)에 격리합니다. `domain-core`는 JPA annotation이나 persistence entity를 알지 않습니다.
|
||||
-
|
||||
-## 자주 만나는 오류
|
||||
-
|
||||
-| 증상 / 오류 코드 | 빠른 확인 | 해결 명령 |
|
||||
-| --- | --- | --- |
|
||||
-| Docker daemon 미기동, `bootstrapDockerPreflight` 실패 | Docker Desktop/daemon이 응답하지 않음 | `docker info` |
|
||||
-| `DB_UNAVAILABLE` 또는 PostgreSQL 연결 실패 | local Compose DB가 내려감 | `docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --wait db` |
|
||||
-| `STARTUP_VALIDATION_FAILED` | 필수 env 누락 또는 registry drift | `cd src && ./gradlew verifyEnvKeys --no-daemon` |
|
||||
-| `MIGRATION_FAILED` | Flyway script 또는 checksum 문제 | `docker compose -f docker-compose.yml -f docker-compose.local.yml logs app` |
|
||||
-| public path snapshot 실패 | `SECURITY_PUBLIC_PATHS`가 의도적으로 바뀜 | `cd src && ./gradlew verifyPublicPathSnapshot -PapprovePublicPathChange` |
|
||||
-| `AUTH_TOKEN_MISSING` 또는 healthcheck 401 | 공개 경로 설정 누락 | `curl -i http://localhost:8080/api/healthcheck` |
|
||||
-
|
||||
-## 빌드·릴리스 공급망
|
||||
-
|
||||
-SemVer Git tag를 push하면
|
||||
-[build-release-supply-chain.yml](.github/workflows/build-release-supply-chain.yml)이 다음 순서를
|
||||
-하나의 release-blocking DAG로 실행합니다. GitHub Release 자체는 모든 검증이 끝난 뒤 마지막에
|
||||
-생성되므로, 취약점·서명·provenance 실패 상태가 먼저 공개 release가 되지 않습니다.
|
||||
-
|
||||
-1. release tag가 `vMAJOR.MINOR.PATCH` 또는 `MAJOR.MINOR.PATCH`인지 검사합니다.
|
||||
-2. Gradle artifact version을 `MAJOR.MINOR.PATCH+12자리-git-sha`로 만들고, JAR manifest와 OCI
|
||||
- label에 version/source revision을 기록합니다.
|
||||
-3. 이미지를 GHCR에 push한 뒤 tag가 아닌 `image@sha256:digest`로 High/Critical Trivy scan을
|
||||
- 실행합니다.
|
||||
-4. 같은 digest에서 SPDX JSON SBOM을 만들고 Cosign keyless로 SBOM attestation과 image
|
||||
- signature를 생성합니다.
|
||||
-5. 공식 SLSA isolated reusable workflow가 SLSA v1 provenance를 생성합니다.
|
||||
-6. Cosign certificate identity/issuer, SBOM attestation, SLSA source/tag/builder와
|
||||
- `buildDefinition.externalParameters`/`runDetails.builder.id`를 검증합니다.
|
||||
-7. 검증된 digest를 재빌드하지 않고 `<MAJOR.MINOR.PATCH>_<short-sha>` OCI tag로 승격하고,
|
||||
- immutable digest가 든 `release-manifest.json`을 release asset으로 게시합니다.
|
||||
-
|
||||
-OCI tag는 `+`를 허용하지 않으므로 tag에서만 `_`로 정규화합니다. SemVer 원본은 JAR, OCI
|
||||
-`org.opencontainers.image.version`, release manifest에 그대로 남습니다. `latest`나 tag-only
|
||||
-promotion은 사용하지 않습니다.
|
||||
-
|
||||
-### 릴리스 전제 조건
|
||||
-
|
||||
-- GitHub Actions에서 `packages: write`, `id-token: write`, release asset용 `contents: write`가
|
||||
- 허용되어야 합니다.
|
||||
-- Cosign expected identity는
|
||||
- `https://github.com/<owner>/<repo>/.github/workflows/build-release-supply-chain.yml@<git-ref>`,
|
||||
- issuer는 `https://token.actions.githubusercontent.com`입니다.
|
||||
-- private repository도 공식 SLSA generator 제약에 따라 public Rekor에 기록됩니다. 이 경우
|
||||
- repository 이름이 transparency log에 공개된다는 점을 받아들일 수 있을 때만 이 기본값을
|
||||
- 사용하고, 받아들일 수 없으면 private Rekor/별도 provenance backend를 설계해야 합니다.
|
||||
-- 배포 시점의 unsigned-image 차단은 이 저장소가 생성하는 signature/provenance를 소비하는
|
||||
- admission policy(Kyverno, Sigstore policy-controller 등)의 책임입니다.
|
||||
-
|
||||
-### Dependency lock 갱신
|
||||
-
|
||||
-모든 모듈은 Gradle 기본 `gradle.lockfile`과 `LockMode.STRICT`를 사용합니다. 선언을 바꾼 뒤에는
|
||||
-다음 명시적 명령으로 전이 의존성 전체를 다시 잠급니다. 일반 build/release에서는
|
||||
-`--write-locks`를 사용하지 않으므로 lock drift가 실패합니다.
|
||||
-
|
||||
-```bash
|
||||
-cd src
|
||||
-./gradlew resolveAndLockAll --write-locks
|
||||
-./gradlew test
|
||||
-```
|
||||
-
|
||||
-Renovate hosted service는 dependency PR에서 이 기본 lockfile을 함께 갱신합니다. Self-hosted
|
||||
-Renovate는 Gradle wrapper 실행이 기본 차단되므로 repository의 `renovate.json`이 아니라 bot의
|
||||
-global config에 `allowedUnsafeExecutions: ["gradleWrapper"]`를 명시해야 합니다. 해당 권한은
|
||||
-repository 코드 실행을 허용하므로 전용 격리 runner에서만 켭니다.
|
||||
-
|
||||
-### Rollback과 보관
|
||||
-
|
||||
-rollback은 release asset의 `release-manifest.json`에서 immutable image digest를 읽어 재빌드 없이
|
||||
-수행합니다.
|
||||
-
|
||||
-```bash
|
||||
-docker pull ghcr.io/<owner>/<repo>@sha256:<digest>
|
||||
-```
|
||||
-
|
||||
-보관 하한은 “최근 10개 release 또는 90일 이내” 중 더 긴 쪽입니다. 즉 두 보호 조건이 모두
|
||||
-끝난 artifact만 삭제할 수 있습니다. `supply-chain-retention-audit.yml`이 매일 GitHub Releases의
|
||||
-tag/commit, `release-manifest.json`·`sbom.spdx.json` asset, GHCR tag를 대조해 보호 대상 누락을
|
||||
-실패로 알립니다. 삭제 자동화는 registry 운영 정책이므로 포함하지 않으며, fork가 cleanup을 추가하더라도
|
||||
-[supply-chain-policy.json](.github/supply-chain-policy.json)의 두 조건을 함께 적용해야 합니다.
|
||||
-
|
||||
-### 로컬 검증
|
||||
-
|
||||
-```bash
|
||||
-bash .github/scripts/verify-supply-chain-contract.sh
|
||||
-bash .github/scripts/verify-gate-matrix.sh
|
||||
-bash .github/scripts/verify-reproducible-build.sh
|
||||
-```
|
||||
-
|
||||
-Docker contract까지 검증하려면 version/source build arg를 모두 전달해 build한 뒤 `.Config.User`와
|
||||
-OCI label을 inspect합니다. CI와 로컬 JDK 기준은 [.tool-versions](.tool-versions)의 정확한 Temurin
|
||||
-patch version입니다. Docker builder/runtime base도 Dockerfile에서 multi-platform manifest digest로
|
||||
-고정하며, Renovate PR에서 새 Temurin patch/digest를 검토한 뒤 갱신합니다.
|
||||
-
|
||||
-## Clean Architecture 규칙
|
||||
-
|
||||
-애플리케이션이 동작하더라도 아래 규칙을 어기면 병합하지 않습니다.
|
||||
-
|
||||
-- `domain-core`는 Spring, JPA, Servlet, HTTP, DB, cloud SDK 클래스를 import하지 않습니다.
|
||||
-- Controller는 repository를 직접 호출하거나 JPA entity를 반환하지 않습니다.
|
||||
-- Web DTO는 `application-core` 또는 `domain-core`로 들어가지 않습니다.
|
||||
-- 비즈니스 정책은 mapper, filter, config, controller, settings class에 두지 않습니다.
|
||||
-- 새 외부 시스템 연동은 domain/application port와 adapter module로 표현합니다.
|
||||
-- 완료를 주장하기 전에는 테스트를 실행합니다. 실행하지 못했다면 이유와 남은 위험을 명시합니다.
|
||||
-
|
||||
-## 설계 특징 / 기본 선택
|
||||
-
|
||||
-- **DB는 RDBMS 우선.** `adapter:outbound:persistence-jpa`는 JPA/Spring Data 기반 RDBMS 어댑터이고, 벤더 전용(native SQL·Flyway·드라이버)은 그 안의 `.postgresql` 서브패키지로 분리합니다. NoSQL은 같은 `application-core` port를 구현하는 모듈을 추가해 끼우는 구조로 열어 둡니다 — 유스케이스는 port에만 의존하므로 영속 기술 교체에 영향받지 않습니다.
|
||||
-- **비동기 실행 환경은 기본 제공하지 않습니다.** 필요하면 fork에서 추가합니다.
|
||||
-- **adapter:outbound:identifier — ULID.** 식별자를 UUID로 그냥 저장하면 값이 무작위라 정렬되지 않아 B-tree 인덱스 효율이 떨어집니다. ULID는 26자 Crockford base32이고 **앞부분이 48-bit timestamp**라 생성 순서가 곧 시간 정렬 순서입니다. 클라이언트에는 ULID 문자열로 반환하고 DB에는 128-bit `UUID` 컬럼으로 저장하되, 값 자체가 시간순이라 삽입 순서가 정렬되어 인덱스 효율이 유지됩니다. 변환은 `UlidCodec`(`normalize` / `toUuid` / `fromUuid`)이 담당합니다.
|
||||
-- **adapter:outbound:messaging / cache-redis / notification / httpclient — 공통 인터페이스만, 구현체는 seam.** 네 모듈은 공유 베이스 `adapter:outbound:support`(correlation, fail-open 의존성 로깅)에 의존하며, messaging / cache / notification / 아웃바운드 HTTP의 포트·SPI와 라우팅·resilience 베이스라인만 제공하고, 실제 연동 client(`KafkaSender`, `RedisClient`, `SlackClient`, `GoogleEmailClient` 등)는 forking 프로젝트가 채웁니다. 모든 템플릿은 `@ConditionalOnProperty`로 게이팅되며 **기본 비활성**입니다. 예: 다른 cache를 쓰려면 `CacheBackend`(SPI)를 구현해 빈으로 등록하고 `app.cache.bindings.<name>=<backendId>`로 라우팅합니다.
|
||||
-
|
||||
-## 모듈별 설계 결정 참조
|
||||
-
|
||||
-각 모듈의 "왜 이렇게 짰는가" 근거는 모듈별 README에 모았고, 모듈 규칙(허용/금지 의존, 테스트 명령)의 SSOT는 각 모듈 `CLAUDE.md`입니다.
|
||||
-
|
||||
-- 빌드 / 검증 게이트 · 환경 변수: [src/README.md](src/README.md)
|
||||
-- [domain-core](src/domain-core/README.md) · [application-core](src/application-core/README.md) · [adapter:inbound:web](src/adapter/inbound/web/README.md)
|
||||
-- [adapter:outbound:persistence-jpa](src/adapter/outbound/persistence-jpa/README.md) · [adapter:outbound:identifier](src/adapter/outbound/identifier/README.md)
|
||||
-- [adapter:outbound:support](src/adapter/outbound/support/README.md) · [adapter:outbound:messaging](src/adapter/outbound/messaging/README.md) · [adapter:outbound:cache-redis](src/adapter/outbound/cache-redis/README.md) · [adapter:outbound:notification](src/adapter/outbound/notification/README.md) · [adapter:outbound:httpclient](src/adapter/outbound/httpclient/README.md)
|
||||
-- [shared-contract](src/shared-contract/README.md) · [app-bootstrap](src/app-bootstrap/README.md) · [sample-portfolio](src/sample-portfolio/README.md)
|
||||
+- 여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다. <!-- claim-id: C-LIMIT-CHOICES-001 -->
|
||||
+- 기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다. <!-- claim-id: C-LIMIT-LOCAL-001 -->
|
||||
+- 자동 검증은 저장소 내부의 구조·구성 계약을 지킵니다. 조직별 threat model, SLO, 부하 특성, 데이터 보존과 복구 목표는 별도의 설계·검증 대상입니다.
|
||||
@@ -0,0 +1,7 @@
|
||||
schema-version: 1
|
||||
mode: bootstrap
|
||||
target-rel: README.md
|
||||
generated-hash: sha256:5c39a893d2255c8df7798fcd62090490a345687c9637c683e4d9ad9aab70e1d6
|
||||
target-before-hash: sha256:1ed20c003da403e0569f0d56f0f5718e7255b1d628f3507c9a3ab0d7d308080a
|
||||
repository-snapshot-hash: sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7
|
||||
review-score: 96
|
||||
@@ -0,0 +1,188 @@
|
||||
schema-version: 1
|
||||
claims:
|
||||
- id: C-STACK-001
|
||||
type: factual
|
||||
statement: "`ca-tmpl`은 Java 21과 Spring Boot 4.0.0을 기준으로 구성된 멀티 모듈 서비스 템플릿입니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-STACK-001}, {fact-id: F-MODULES-001}]
|
||||
status: supported
|
||||
- id: C-POSITION-001
|
||||
type: factual
|
||||
statement: "이 저장소의 초점은 기능 예제를 많이 제공하는 데 있지 않습니다. domain, application, adapter, bootstrap의 책임을 나누고, 그 경계가 기능 추가 과정에서 무너지지 않도록 Gradle과 ArchUnit 검증을 함께 제공하는 데 있습니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-MODULE-COUNT-001
|
||||
type: factual
|
||||
statement: "19개 Gradle 모듈이 core, inbound adapter, outbound adapter, composition root, sample 역할로 선언되어 있습니다."
|
||||
section: overview
|
||||
sources: [{fact-id: F-MODULES-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-DEPS-001
|
||||
type: factual
|
||||
statement: "모든 선언 모듈을 의존성 정책에 포함하고 허용되지 않은 프로젝트 의존성을 실패시킵니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-CODE-001
|
||||
type: factual
|
||||
statement: "application 코드가 adapter·bootstrap·transport·persistence에 의존하지 못하도록 검사합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-LOCKS-001
|
||||
type: factual
|
||||
statement: "leaf module의 모든 dependency configuration을 STRICT lock mode로 검증합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-LOCKS-001}]
|
||||
status: supported
|
||||
- id: C-FORCED-SAMPLE-001
|
||||
type: factual
|
||||
statement: "같은 핵심 테스트를 `sample-portfolio` 없이 컴파일·실행하는 경로를 제공합니다."
|
||||
section: project-value
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-VISUAL-MEANING-001
|
||||
type: factual
|
||||
statement: "다음 그림의 화살표는 런타임 호출 순서가 아니라 허용된 프로젝트 의존 방향을 요약합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-DOMAIN-001
|
||||
type: factual
|
||||
statement: "외부 라이브러리 의존성 없이 비즈니스 불변식과 도메인 타입을 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-APPLICATION-001
|
||||
type: factual
|
||||
statement: "`domain-core`와 `shared-contract`에 의존하며 유스케이스와 port를 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-ADAPTERS-001
|
||||
type: factual
|
||||
statement: "전송 계층 입력과 기술별 출력 구현을 core 바깥에 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-MODULES-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-SHARED-001
|
||||
type: factual
|
||||
statement: "비즈니스 개념이 아닌 공용 운영 계약을 둡니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-CORE-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-MODULE-001
|
||||
type: factual
|
||||
statement: "선택한 core와 adapter를 조립하고 Spring Boot 진입점을 소유합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-COMPOSITION-001}]
|
||||
status: supported
|
||||
- id: C-SAMPLE-ROLE-001
|
||||
type: factual
|
||||
statement: "템플릿 사용법을 보여주는 참조 구현이며 일반 테스트의 fixture로만 연결됩니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-TWO-LAYERS-001
|
||||
type: factual
|
||||
statement: "Gradle의 `verifyCleanArchitectureDependencies`는 모듈 간 의존 방향을, `CleanArchitectureTest`는 application 패키지의 adapter·transport 접근과 같은 코드 수준 경계를 검사합니다."
|
||||
section: architecture
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}, {fact-id: F-CODE-BOUNDARY-001}]
|
||||
status: supported
|
||||
- id: C-PREREQUISITES-001
|
||||
type: factual
|
||||
statement: "필요한 도구는 JDK 21과 실행 중인 Docker daemon입니다. 저장소의 도구 버전 파일은 Temurin 21.0.11+10을 지정하고, bootstrap preflight는 Docker CLI가 daemon에 연결되는지 확인합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-STACK-001}, {fact-id: F-BOOTSTRAP-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-COMMAND-001
|
||||
type: factual
|
||||
statement: "`./gradlew bootstrap`은 전체 소스 컴파일, Docker 확인, 로컬 PostgreSQL과 앱 시작, sample 격리 계약, HTTP smoke check를 순서대로 실행합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}]
|
||||
status: supported
|
||||
- id: C-HEALTH-001
|
||||
type: factual
|
||||
statement: "성공 조건은 `http://localhost:8080/api/healthcheck`가 HTTP 200과 `status=UP`을 반환하는 것입니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-HEALTH-001}]
|
||||
status: supported
|
||||
- id: C-BOOTSTRAP-SIDE-EFFECT-001
|
||||
type: factual
|
||||
statement: "bootstrap은 Compose의 `app`과 `db` 서비스를 백그라운드로 시작합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
- id: C-CLEANUP-001
|
||||
type: factual
|
||||
statement: "위 Compose 명령은 저장소에 선언된 base와 local 구성 파일을 함께 사용해 서비스를 종료합니다."
|
||||
section: quick-start
|
||||
sources: [{fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
- id: C-IDENTITY-001
|
||||
type: factual
|
||||
statement: "Gradle root name은 `ca-skeleton`, Java package root와 main class는 `dev.caskeleton` 아래에 선언되어 있으므로 서비스 이름과 namespace 정책에 맞게 함께 변경합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-IDENTITY-001}]
|
||||
status: supported
|
||||
- id: C-ADOPT-SAMPLE-001
|
||||
type: factual
|
||||
statement: "`sample-portfolio`는 일반 테스트의 `sampleFixture`로만 연결되며 `sampleOffTest`는 샘플 없는 classpath에서 같은 핵심 테스트 corpus를 실행합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-NEW-MODULE-POLICY-001
|
||||
type: factual
|
||||
statement: "새 모듈을 추가하면 Gradle 의존성 정책에도 명시적으로 등록해야 합니다."
|
||||
section: adoption
|
||||
sources: [{fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-TEST-001
|
||||
type: factual
|
||||
statement: "일반 테스트: `./gradlew test`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-ARCH-001
|
||||
type: factual
|
||||
statement: "모듈 의존 방향만 빠르게 확인: `./gradlew verifyCleanArchitectureDependencies`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-DEPENDENCY-POLICY-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-SAMPLE-001
|
||||
type: factual
|
||||
statement: "sample 제거 가능성 확인: `./gradlew :app-bootstrap:sampleOffTest`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-SAMPLE-001}]
|
||||
status: supported
|
||||
- id: C-VERIFY-CHECK-001
|
||||
type: factual
|
||||
statement: "전체 품질 계약: `./gradlew check`"
|
||||
section: verification
|
||||
sources: [{fact-id: F-VERIFICATION-COMMANDS-001}, {fact-id: F-CHECK-001}]
|
||||
status: supported
|
||||
- id: C-CHECK-SCOPE-001
|
||||
type: factual
|
||||
statement: "`check`는 테스트뿐 아니라 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, quarantine 만료, README 명령 검증을 집계합니다."
|
||||
section: verification
|
||||
sources: [{fact-id: F-CHECK-001}]
|
||||
status: supported
|
||||
- id: C-DOCS-STRATEGY-001
|
||||
type: evaluative
|
||||
statement: "README는 판단과 첫 실행에 필요한 정보만 유지합니다. 세부 계약은 소유 위치에서 확인하십시오."
|
||||
section: documentation
|
||||
sources: []
|
||||
status: supported
|
||||
- id: C-LIMIT-CHOICES-001
|
||||
type: factual
|
||||
statement: "여러 inbound·outbound adapter 모듈이 포함되어 있지만 실제 서비스가 채택할 범위와 배포 환경은 템플릿이 결정하지 않습니다."
|
||||
section: documentation
|
||||
sources: [{fact-id: F-TEMPLATE-LIMIT-001}]
|
||||
status: supported
|
||||
- id: C-LIMIT-LOCAL-001
|
||||
type: factual
|
||||
statement: "기본 로컬 실행 경로는 Docker와 PostgreSQL 16 Compose 서비스를 전제로 합니다."
|
||||
section: documentation
|
||||
sources: [{fact-id: F-BOOTSTRAP-001}, {fact-id: F-CONTAINER-001}]
|
||||
status: supported
|
||||
@@ -0,0 +1,50 @@
|
||||
schema-version: 1
|
||||
project-profile:
|
||||
primary: project-template
|
||||
secondary:
|
||||
- backend-service
|
||||
audiences:
|
||||
primary:
|
||||
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
|
||||
secondary:
|
||||
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
|
||||
reader-outcomes:
|
||||
- 템플릿이 제공하는 강제 규칙과 도입자가 결정할 영역을 구분한다.
|
||||
- 로컬 부트스트랩을 실행하고 성공 조건과 정리 방법을 확인한다.
|
||||
- 도메인·유스케이스·어댑터·조립 코드를 올바른 모듈에 배치한다.
|
||||
- 샘플 제거 및 전체 품질 계약을 재현 가능한 명령으로 검증한다.
|
||||
project-story:
|
||||
value-proposition: 문서로만 권고하는 구조가 아니라 Gradle과 ArchUnit 규칙으로 의존 방향을 지속적으로 검증하는 Spring Boot 템플릿이다.
|
||||
problem: 서비스 초기 구조는 빠르게 복사할 수 있어도 경계가 빌드에 강제되지 않으면 기능 추가 과정에서 쉽게 무너진다.
|
||||
target-reader: 신규 Java 백엔드의 구조와 검증 기준을 함께 도입하려는 개발자
|
||||
notable-traits:
|
||||
- text: Java 21과 Spring Boot 4.0.0을 사용하는 19개 모듈 구성이다.
|
||||
fact-ids: [F-STACK-001, F-MODULES-001]
|
||||
- text: 모듈 의존 방향과 application 코드 경계를 실행 가능한 빌드·ArchUnit 규칙으로 검증한다.
|
||||
fact-ids: [F-DEPENDENCY-POLICY-001, F-CODE-BOUNDARY-001]
|
||||
- text: 로컬 부트스트랩은 컴파일부터 PostgreSQL·앱 시작과 HTTP 상태 확인까지 하나의 계약으로 묶는다.
|
||||
fact-ids: [F-BOOTSTRAP-001, F-HEALTH-001]
|
||||
- text: sample-portfolio를 테스트 fixture로 격리하고 샘플 없는 핵심 테스트 경로를 제공한다.
|
||||
fact-ids: [F-SAMPLE-001]
|
||||
maturity: 자동화된 구조·실행·검증 계약을 갖춘 참조 템플릿
|
||||
limitations:
|
||||
- 제공되는 adapter 가운데 실제 서비스가 채택할 범위는 도입자가 결정해야 한다.
|
||||
- 로컬 실행 토폴로지는 Docker와 PostgreSQL을 전제로 한다.
|
||||
- 조직별 보안·성능·가용성 요구사항은 템플릿의 내부 검증과 별도로 평가해야 한다.
|
||||
narrative-variant: architecture-template
|
||||
reader-journey:
|
||||
- reader-question: 이 템플릿은 무엇이며 어떤 문제를 해결하는가?
|
||||
section-id: overview
|
||||
- reader-question: 일반적인 시작점과 비교해 무엇이 강제되는가?
|
||||
section-id: project-value
|
||||
- reader-question: 모듈은 어떤 방향으로 의존하고 코드는 어디에 놓는가?
|
||||
section-id: architecture
|
||||
- reader-question: 가장 짧은 로컬 실행 경로와 성공 신호는 무엇인가?
|
||||
section-id: quick-start
|
||||
- reader-question: 샘플을 실제 도메인으로 바꾸는 순서는 무엇인가?
|
||||
section-id: adoption
|
||||
- reader-question: 구조와 전체 품질 계약을 어떻게 다시 검증하는가?
|
||||
section-id: verification
|
||||
- reader-question: 세부 설정과 운영 계약은 어디에서 확인하는가?
|
||||
section-id: documentation
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
schema-version: 1
|
||||
sections:
|
||||
- id: overview
|
||||
title-guidance: 템플릿 개요
|
||||
level: 2
|
||||
purpose: 가치 제안, 대상 독자, 적합하거나 부적합한 사용 상황을 빠르게 판단시킨다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- Java와 Spring Boot 기준 버전
|
||||
- 권고가 아닌 실행 가능한 경계 검증이라는 차별점
|
||||
- 참조 템플릿이라는 정직한 포지셔닝
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 이 프로젝트의 정체성을 이해하는 데 그림이 필요한가?
|
||||
rationale: 짧은 가치 제안과 적합성 목록이 더 빠르고 정확하다.
|
||||
- id: project-value
|
||||
title-guidance: 제공 가치와 결정 영역
|
||||
level: 2
|
||||
purpose: 저장소가 자동으로 강제하는 것과 도입자가 선택할 것을 분리한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- 아키텍처·잠금·샘플 격리 계약
|
||||
- 도메인·adapter·배포 정책은 도입자 책임임을 명시
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 강제 영역과 선택 영역을 어떻게 가장 빨리 비교하는가?
|
||||
rationale: 두 열 비교표가 그림보다 직접적이고 접근성이 높다.
|
||||
- id: architecture
|
||||
title-guidance: 아키텍처와 코드 배치
|
||||
level: 2
|
||||
purpose: core, adapter, composition root의 관계와 코드 변경 위치를 설명한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- 의존 방향 Mermaid 다이어그램
|
||||
- 모듈 그룹별 책임 표
|
||||
- Gradle과 ArchUnit이 각각 검증하는 경계
|
||||
visual-slot:
|
||||
decision: include
|
||||
reader-question: 여러 모듈 그룹이 어느 방향으로 의존하는가?
|
||||
rationale: 다섯 구성요소의 의존 방향은 문장 나열보다 흐름도가 더 빨리 전달한다.
|
||||
purpose: adapter와 composition root가 core 방향으로 의존한다는 구조를 한 화면에 보여준다.
|
||||
- id: quick-start
|
||||
title-guidance: 빠른 시작
|
||||
level: 2
|
||||
purpose: 사전 조건, 단일 bootstrap 명령, 부작용, 성공 신호와 정리 방법을 제공한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- JDK 21과 Docker
|
||||
- bootstrap 실행 명령
|
||||
- API health 성공 조건
|
||||
- Compose 정리 명령
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 실행 절차를 이해하는 데 추가 시각 자료가 필요한가?
|
||||
rationale: 짧은 명령 블록과 성공 조건이 가장 실행 가능하다.
|
||||
- id: adoption
|
||||
title-guidance: 실제 프로젝트로 전환하기
|
||||
level: 2
|
||||
purpose: 프로젝트 식별자, 도메인, adapter, 환경 정책, sample 제거 순서로 도입 경로를 안내한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- sample은 참조 구현이며 production 의존성이 아님
|
||||
- sampleOffTest를 이용한 제거 검증
|
||||
- 상세 문서를 중복하지 않는 단계별 전환 경로
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 도입 순서는 어떤 형태가 가장 행동하기 쉬운가?
|
||||
rationale: 번호 목록과 검증 체크포인트가 진행 순서를 명확히 한다.
|
||||
- id: verification
|
||||
title-guidance: 검증 루프
|
||||
level: 2
|
||||
purpose: 빠른 테스트, 아키텍처 경계, 샘플 제거, 전체 check의 목적과 명령을 분리한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- test와 architecture 명령
|
||||
- sampleOffTest 명령
|
||||
- 전체 check가 집계하는 정책
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 검증 수준별 명령 선택에 그림이 필요한가?
|
||||
rationale: 목적과 명령을 짝지은 표가 더 정확하다.
|
||||
- id: documentation
|
||||
title-guidance: 상세 문서 지도와 한계
|
||||
level: 2
|
||||
purpose: 빌드·모듈·환경·운영 문서로 이동시키고 템플릿의 적용 한계를 명시한다.
|
||||
required: true
|
||||
content-strategy: inline
|
||||
content-requirements:
|
||||
- src README와 대표 모듈 README 링크
|
||||
- 환경 레지스트리와 runbook 링크
|
||||
- 조직별 비기능 요구사항은 별도 검증이라는 한계
|
||||
visual-slot:
|
||||
decision: exclude
|
||||
reader-question: 세부 문서 위치를 찾는 데 그림이 필요한가?
|
||||
rationale: 목적별 링크 목록이 탐색과 유지보수에 적합하다.
|
||||
@@ -0,0 +1,44 @@
|
||||
schema-version: 1
|
||||
target:
|
||||
repository: /home/donghyeon/workspace/ca-tmpl
|
||||
readme-path: README.md
|
||||
mode: bootstrap
|
||||
profile-override: project-template
|
||||
project-intent:
|
||||
purpose: ca-tmpl을 평가하고 실제 서비스의 출발점으로 채택하는 데 필요한 판단·실행·변경 경로를 제공한다.
|
||||
positioning: 구현 목록이 아니라 아키텍처 경계를 빌드와 테스트로 강제하는 Spring Boot 프로젝트 템플릿의 진입 문서다.
|
||||
maturity: 광범위한 자동화 계약을 갖춘 참조 템플릿이며, 개별 조직의 운영 적합성은 도입 과정에서 검증해야 한다.
|
||||
audience:
|
||||
primary:
|
||||
- 신규 Spring Boot 서비스의 기준 구조를 정하는 백엔드·플랫폼 개발자
|
||||
secondary:
|
||||
- 아키텍처 규칙과 검증 체계를 평가하는 테크 리드
|
||||
reader-actions:
|
||||
- 30초 안에 템플릿의 차별점과 적합한 사용 상황을 판단한다.
|
||||
- 로컬 부트스트랩을 실행하고 성공 신호를 확인한다.
|
||||
- 변경할 코드를 올바른 모듈에 배치한다.
|
||||
- sample-portfolio를 제거해도 핵심 계약이 유지되는지 검증한다.
|
||||
- 상세 설정과 운영 문서의 위치를 찾는다.
|
||||
content-policy:
|
||||
language: ko
|
||||
tone: technical-direct
|
||||
target-length: medium
|
||||
preserve-existing-copy: false
|
||||
detail-docs-policy: summary-and-link
|
||||
visual-policy:
|
||||
mode: when-useful
|
||||
max-visuals: 1
|
||||
preferred-formats:
|
||||
- mermaid
|
||||
must-include:
|
||||
- 강제되는 아키텍처 규칙과 도입자가 선택해야 하는 정책의 구분
|
||||
- bootstrap이 수행하는 작업과 성공 신호
|
||||
- sample-portfolio의 참조 구현 역할과 제거 검증 방법
|
||||
- 모듈 의존 방향과 코드 배치 기준
|
||||
- 템플릿의 한계와 도입 전 결정 항목
|
||||
must-exclude:
|
||||
- 전체 환경 변수 목록
|
||||
- 공급망·릴리스 절차의 장황한 복제
|
||||
- 모든 어댑터의 구현 세부사항
|
||||
- 근거 없는 production-ready 주장
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
schema-version: 1
|
||||
repository-snapshot-hash: sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7
|
||||
project-name: ca-skeleton
|
||||
languages:
|
||||
- Java
|
||||
frameworks:
|
||||
- Spring Boot 4.0.0
|
||||
- Gradle
|
||||
facts:
|
||||
- id: F-IDENTITY-001
|
||||
category: adoption
|
||||
key: template-identifiers
|
||||
value:
|
||||
gradle-root-name: ca-skeleton
|
||||
java-package-root: dev.caskeleton
|
||||
main-class: dev.caskeleton.bootstrap.CaSkeletonApplication
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 5
|
||||
line-end: 5
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
|
||||
line-start: 1
|
||||
line-end: 20
|
||||
symbol: CaSkeletonApplication
|
||||
source-kind: implementation
|
||||
- id: F-STACK-001
|
||||
category: stack
|
||||
key: runtime-and-framework
|
||||
value:
|
||||
java: 21
|
||||
java-distribution: temurin-21.0.11+10
|
||||
spring-boot: 4.0.0
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: .tool-versions
|
||||
line-start: 1
|
||||
line-end: 1
|
||||
source-kind: tool-version-configuration
|
||||
- path: src/build.gradle
|
||||
line-start: 5
|
||||
line-end: 7
|
||||
source-kind: build-configuration
|
||||
- path: src/build.gradle
|
||||
line-start: 103
|
||||
line-end: 107
|
||||
source-kind: build-configuration
|
||||
- id: F-MODULES-001
|
||||
category: architecture
|
||||
key: declared-modules
|
||||
value:
|
||||
core: [domain-core, application-core, shared-contract]
|
||||
inbound: [web, grpc, graphql, websocket]
|
||||
outbound: [persistence-jpa, support, messaging, cache-redis, notification, objectstorage, fileserver, persistence-mongo, httpclient, identifier]
|
||||
composition: [app-bootstrap]
|
||||
sample: [sample-portfolio]
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 5
|
||||
line-end: 25
|
||||
source-kind: build-configuration
|
||||
- id: F-DEPENDENCY-POLICY-001
|
||||
category: architecture
|
||||
key: module-dependency-policy
|
||||
value: Gradle의 verifyCleanArchitectureDependencies가 모든 선언 모듈을 정책에 포함시키고 허용되지 않은 프로젝트 의존성을 실패시킨다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
symbol: verifyCleanArchitectureDependencies
|
||||
source-kind: executable-build-rule
|
||||
- id: F-CODE-BOUNDARY-001
|
||||
category: architecture
|
||||
key: code-boundary-policy
|
||||
value: ArchUnit 규칙이 application 패키지의 adapter·bootstrap·transport·persistence 의존과 Spring @Transactional 사용을 금지한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
|
||||
line-start: 197
|
||||
line-end: 228
|
||||
source-kind: executable-test-rule
|
||||
- id: F-CORE-001
|
||||
category: architecture
|
||||
key: core-responsibilities
|
||||
value:
|
||||
domain-core: 외부 라이브러리 의존성이 없는 도메인 모듈
|
||||
application-core: domain-core와 shared-contract를 의존하는 유스케이스 모듈
|
||||
shared-contract: 비즈니스 개념을 두지 않는 공용 운영 계약 모듈
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/domain-core/build.gradle
|
||||
line-start: 1
|
||||
line-end: 3
|
||||
source-kind: build-configuration
|
||||
- path: src/application-core/build.gradle
|
||||
line-start: 1
|
||||
line-end: 15
|
||||
source-kind: build-configuration
|
||||
- path: src/shared-contract/build.gradle
|
||||
line-start: 1
|
||||
line-end: 3
|
||||
source-kind: build-configuration
|
||||
- id: F-COMPOSITION-001
|
||||
category: architecture
|
||||
key: composition-root
|
||||
value: app-bootstrap가 core, inbound web, 여러 outbound adapter, shared-contract를 조립하고 Spring Boot main class를 지정한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 41
|
||||
line-end: 58
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 174
|
||||
line-end: 176
|
||||
source-kind: build-configuration
|
||||
- id: F-SAMPLE-001
|
||||
category: adoption
|
||||
key: sample-removal-contract
|
||||
value: sample-portfolio는 일반 테스트에서만 sampleFixture로 연결되며 sampleOffTest는 같은 핵심 테스트 스위트를 샘플 없이 컴파일하고 실행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 14
|
||||
line-end: 38
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 95
|
||||
line-end: 100
|
||||
source-kind: build-configuration
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
symbol: sampleOffTest
|
||||
source-kind: executable-build-rule
|
||||
- id: F-BOOTSTRAP-001
|
||||
category: command
|
||||
key: local-bootstrap-contract
|
||||
value: bootstrap은 전체 소스 컴파일, Docker daemon 확인, PostgreSQL 시작, 앱 빌드·시작, 샘플 격리 계약, HTTP smoke check를 순서대로 실행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 345
|
||||
line-end: 435
|
||||
symbol: bootstrap
|
||||
source-kind: executable-build-rule
|
||||
- id: F-HEALTH-001
|
||||
category: endpoint
|
||||
key: bootstrap-success-signal
|
||||
value: bootstrap smoke check는 http://localhost:8080/api/healthcheck의 HTTP 200 응답과 status UP을 요구한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 396
|
||||
line-end: 426
|
||||
symbol: bootstrapSmoke
|
||||
source-kind: executable-build-rule
|
||||
- path: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/controller/HealthcheckController.java
|
||||
line-start: 10
|
||||
line-end: 17
|
||||
symbol: HealthcheckController
|
||||
source-kind: implementation
|
||||
- id: F-CONTAINER-001
|
||||
category: runtime
|
||||
key: local-compose-topology
|
||||
value: 로컬 Compose 구성은 app과 PostgreSQL 16 서비스를 연결하고 named volume에 데이터베이스 데이터를 보존한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: docker-compose.yml
|
||||
line-start: 26
|
||||
line-end: 80
|
||||
source-kind: container-configuration
|
||||
- path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
source-kind: container-configuration
|
||||
- id: F-LOCKS-001
|
||||
category: verification
|
||||
key: dependency-lock-policy
|
||||
value: 각 leaf module은 모든 구성을 STRICT 모드로 잠그며 잠금 상태 검증 태스크가 실제 dependency resolution을 수행한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 109
|
||||
line-end: 167
|
||||
source-kind: executable-build-rule
|
||||
- id: F-CHECK-001
|
||||
category: verification
|
||||
key: check-aggregation
|
||||
value: 각 leaf module의 check는 아키텍처 의존성, 환경 키, 산출물, 타입 배치, 취약점 예외, 격리 테스트 만료, README 명령 검증을 포함한다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 273
|
||||
line-end: 280
|
||||
source-kind: executable-build-rule
|
||||
- path: src/build.gradle
|
||||
line-start: 437
|
||||
line-end: 550
|
||||
symbol: verifyReadmeCommands
|
||||
source-kind: executable-build-rule
|
||||
- id: F-VERIFICATION-COMMANDS-001
|
||||
category: command
|
||||
key: documented-verification-tasks
|
||||
value:
|
||||
- ./gradlew test
|
||||
- ./gradlew verifyCleanArchitectureDependencies
|
||||
- ./gradlew :app-bootstrap:sampleOffTest
|
||||
- ./gradlew check
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/build.gradle
|
||||
line-start: 247
|
||||
line-end: 280
|
||||
source-kind: executable-build-rule
|
||||
- path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
symbol: verifyCleanArchitectureDependencies
|
||||
source-kind: executable-build-rule
|
||||
- path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
symbol: sampleOffTest
|
||||
source-kind: executable-build-rule
|
||||
- id: F-DOCS-001
|
||||
category: documentation
|
||||
key: detailed-documentation
|
||||
value: 빌드 루트, 핵심 모듈, inbound·outbound adapter에 각각 README가 있고 환경·운영 계약은 docs 아래 레지스트리와 runbook으로 분리되어 있다.
|
||||
assertion-type: observed
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/README.md
|
||||
source-kind: documentation-index
|
||||
- path: src/domain-core/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/application-core/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/adapter/inbound/web/README.md
|
||||
source-kind: module-documentation
|
||||
- path: src/adapter/outbound/persistence-jpa/README.md
|
||||
source-kind: module-documentation
|
||||
- path: docs/registries/env-keys.yaml
|
||||
source-kind: configuration-registry
|
||||
- path: docs/runbooks/template.md
|
||||
source-kind: runbook-template
|
||||
- id: F-TEMPLATE-LIMIT-001
|
||||
category: limitation
|
||||
key: adoption-decisions
|
||||
value: 템플릿은 여러 inbound·outbound adapter seam과 PostgreSQL 로컬 구성을 제공하지만 실제 서비스가 사용할 adapter와 배포 환경 선택은 도입자가 결정해야 한다.
|
||||
assertion-type: derived
|
||||
confidence: high
|
||||
evidence:
|
||||
- path: src/settings.gradle
|
||||
line-start: 7
|
||||
line-end: 25
|
||||
source-kind: build-configuration
|
||||
- path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
source-kind: container-configuration
|
||||
commands:
|
||||
- id: CMD-001
|
||||
command: ./gradlew bootstrap
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 431
|
||||
line-end: 435
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-002
|
||||
command: docker compose -f docker-compose.yml -f docker-compose.local.yml down
|
||||
cwd: .
|
||||
source:
|
||||
path: docker-compose.local.yml
|
||||
line-start: 15
|
||||
line-end: 83
|
||||
verification:
|
||||
status: static-verified
|
||||
method: compose-file-discovery
|
||||
level: static
|
||||
- id: CMD-003
|
||||
command: ./gradlew test
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 247
|
||||
line-end: 251
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-lifecycle-task
|
||||
level: static
|
||||
- id: CMD-004
|
||||
command: ./gradlew :app-bootstrap:sampleOffTest
|
||||
cwd: src
|
||||
source:
|
||||
path: src/app-bootstrap/build.gradle
|
||||
line-start: 135
|
||||
line-end: 147
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-005
|
||||
command: ./gradlew verifyCleanArchitectureDependencies
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 553
|
||||
line-end: 615
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-task-discovery
|
||||
level: static
|
||||
- id: CMD-006
|
||||
command: ./gradlew check
|
||||
cwd: src
|
||||
source:
|
||||
path: src/build.gradle
|
||||
line-start: 273
|
||||
line-end: 280
|
||||
verification:
|
||||
status: static-verified
|
||||
method: gradle-lifecycle-task
|
||||
level: static
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git-sha": "dd20b5801b5ac2a9443b7e6c01f466b5ddc378e0",
|
||||
"dirty": true,
|
||||
"diff-hash": "sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7",
|
||||
"scanned-at": null,
|
||||
"file-count": 1163
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
schema-version: 1
|
||||
verdict: PASS
|
||||
score: 96
|
||||
scores:
|
||||
project-specificity:
|
||||
score: 5
|
||||
evidence:
|
||||
- "overview: Java 21, Spring Boot 4.0.0, 19개 모듈과 Gradle·ArchUnit 경계를 저장소 근거로 특정한다."
|
||||
- "project-value: STRICT dependency lock과 sampleOffTest처럼 ca-tmpl 고유의 계약을 전면에 둔다."
|
||||
reader-journey:
|
||||
score: 5
|
||||
evidence:
|
||||
- "overview → value → architecture → quick-start → adoption → verification → documentation 순서가 평가·실행·전환 흐름을 따른다."
|
||||
- "각 상세 항목은 독자의 다음 행동인 실행, 코드 배치, sample 제거, 전체 검증으로 이어진다."
|
||||
technical-explanation:
|
||||
score: 4
|
||||
evidence:
|
||||
- "architecture: Gradle 모듈 정책과 ArchUnit 코드 정책을 구분하고 의존 방향을 Mermaid와 모듈 표로 설명한다."
|
||||
- "중간 길이 정책에 맞춰 세부 운영 계약은 소유 문서로 넘기므로 README 자체는 의도적으로 개요 수준을 유지한다."
|
||||
task-usability:
|
||||
score: 5
|
||||
evidence:
|
||||
- "quick-start: prerequisite, 실행 위치, bootstrap 부작용, 성공 endpoint, Compose 종료 명령이 한 흐름에 있다."
|
||||
- "verification: 일반 테스트·아키텍처·sample 제거·전체 계약을 목적별로 선택할 수 있다."
|
||||
prose-clarity:
|
||||
score: 5
|
||||
evidence:
|
||||
- "긴 기능 나열을 피하고 강제 영역/선택 영역 표와 짧은 도입 순서로 압축했다."
|
||||
- "production-ready 같은 근거 없는 표현 없이 적용 한계와 별도 검증 책임을 명시한다."
|
||||
visual-judgment:
|
||||
score: 5
|
||||
evidence:
|
||||
- "architecture: 구성요소가 여섯 개인 의존 관계에만 Mermaid를 사용하고 런타임 호출도가 아님을 바로 설명한다."
|
||||
- "나머지 섹션은 표·명령·목록이 더 적합하다는 outline 근거에 따라 추가 시각물을 배제했다."
|
||||
hard-gates:
|
||||
passed: true
|
||||
failures: []
|
||||
reader-simulations:
|
||||
30-seconds:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "제목과 첫 두 문단에서 기술 기준, 템플릿의 차별점, 목적을 확인할 수 있다."
|
||||
- "개요의 적합/비적합 문장으로 채택 후보인지 빠르게 판단할 수 있다."
|
||||
5-minutes:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "강제/선택 표, 아키텍처, bootstrap, 성공 신호, 적용 한계가 첫 읽기 경로에 모두 있다."
|
||||
contributor:
|
||||
outcome: PASS
|
||||
evidence:
|
||||
- "모듈 표와 도입 5단계가 코드 배치 및 sample 교체 경로를 제공한다."
|
||||
- "검증 명령과 목적별 상세 문서 링크가 다음 작업으로 연결된다."
|
||||
findings:
|
||||
- id: QR-001
|
||||
severity: minor
|
||||
category: command-boundary
|
||||
section: quick-start
|
||||
message: bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.
|
||||
evidence:
|
||||
- "첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다."
|
||||
- "수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
|
||||
route-to: README_DRAFTED
|
||||
status: resolved
|
||||
- id: QR-002
|
||||
severity: minor
|
||||
category: architecture-clarity
|
||||
section: overview
|
||||
message: 초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.
|
||||
evidence:
|
||||
- "초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다."
|
||||
- "실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
|
||||
route-to: README_DRAFTED
|
||||
status: resolved
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# ca-tmpl README quality review
|
||||
|
||||
Verdict: **PASS (96/100)**
|
||||
|
||||
The candidate is specific to ca-tmpl, follows an evaluation-to-adoption reader
|
||||
journey, and keeps correctness separate from editorial scoring. Deterministic
|
||||
checks passed for request, facts, outline conformance, claim provenance, visual
|
||||
coherence, GitHub Markdown, documented commands, paths, and secret leakage.
|
||||
|
||||
The README deliberately stops at architecture and adoption guidance; detailed
|
||||
environment and operational contracts remain linked to their owning documents.
|
||||
Two minor issues found during the real run—an inline command boundary and an
|
||||
ambiguous layer arrow—were corrected before this PASS review.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"run-id": "20260717-quality-core",
|
||||
"repo-id": "ca-tmpl",
|
||||
"mode": "bootstrap",
|
||||
"target-repository": "/home/donghyeon/workspace/ca-tmpl",
|
||||
"harness-version": "0.1.0",
|
||||
"started-at": null,
|
||||
"tool-adapter": "codex",
|
||||
"input-hashes": {}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"mode": "bootstrap",
|
||||
"current": "APPLIED",
|
||||
"history": [
|
||||
{
|
||||
"state": "INITIALIZED"
|
||||
},
|
||||
{
|
||||
"state": "INPUT_CAPTURED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "request",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "REPOSITORY_SNAPSHOTTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "snapshot",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"diff-hash": "sha256:c20f64d403a220484a181c484ded61def423b59a42cbec9f228fa17e97b0a8d7"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "FACTS_EXTRACTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "facts",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"fact_ids": [
|
||||
"F-IDENTITY-001",
|
||||
"F-STACK-001",
|
||||
"F-MODULES-001",
|
||||
"F-DEPENDENCY-POLICY-001",
|
||||
"F-CODE-BOUNDARY-001",
|
||||
"F-CORE-001",
|
||||
"F-COMPOSITION-001",
|
||||
"F-SAMPLE-001",
|
||||
"F-BOOTSTRAP-001",
|
||||
"F-HEALTH-001",
|
||||
"F-CONTAINER-001",
|
||||
"F-LOCKS-001",
|
||||
"F-CHECK-001",
|
||||
"F-VERIFICATION-COMMANDS-001",
|
||||
"F-DOCS-001",
|
||||
"F-TEMPLATE-LIMIT-001"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "PROJECT_PROFILED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "profile",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"profile": "project-template"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "README_PLANNED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "brief",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "outline",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"section_ids": [
|
||||
"overview",
|
||||
"project-value",
|
||||
"architecture",
|
||||
"quick-start",
|
||||
"adoption",
|
||||
"verification",
|
||||
"documentation"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "README_DRAFTED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "conformance",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"sections": [
|
||||
"overview",
|
||||
"project-value",
|
||||
"architecture",
|
||||
"quick-start",
|
||||
"adoption",
|
||||
"verification",
|
||||
"documentation"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "claim_map",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"claims": [
|
||||
"C-STACK-001",
|
||||
"C-POSITION-001",
|
||||
"C-MODULE-COUNT-001",
|
||||
"C-FORCED-DEPS-001",
|
||||
"C-FORCED-CODE-001",
|
||||
"C-FORCED-LOCKS-001",
|
||||
"C-FORCED-SAMPLE-001",
|
||||
"C-VISUAL-MEANING-001",
|
||||
"C-DOMAIN-001",
|
||||
"C-APPLICATION-001",
|
||||
"C-ADAPTERS-001",
|
||||
"C-SHARED-001",
|
||||
"C-BOOTSTRAP-MODULE-001",
|
||||
"C-SAMPLE-ROLE-001",
|
||||
"C-TWO-LAYERS-001",
|
||||
"C-PREREQUISITES-001",
|
||||
"C-BOOTSTRAP-COMMAND-001",
|
||||
"C-HEALTH-001",
|
||||
"C-BOOTSTRAP-SIDE-EFFECT-001",
|
||||
"C-CLEANUP-001",
|
||||
"C-IDENTITY-001",
|
||||
"C-ADOPT-SAMPLE-001",
|
||||
"C-NEW-MODULE-POLICY-001",
|
||||
"C-VERIFY-TEST-001",
|
||||
"C-VERIFY-ARCH-001",
|
||||
"C-VERIFY-SAMPLE-001",
|
||||
"C-VERIFY-CHECK-001",
|
||||
"C-CHECK-SCOPE-001",
|
||||
"C-DOCS-STRATEGY-001",
|
||||
"C-LIMIT-CHOICES-001",
|
||||
"C-LIMIT-LOCAL-001"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "VISUALS_PLANNED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "visual_plan",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"visuals": [
|
||||
"architecture-dependency-direction"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "STRUCTURALLY_VALIDATED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "github_markdown",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "TECHNICALLY_VERIFIED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "verify",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"schema-version": 1,
|
||||
"state": "PASS",
|
||||
"verification-level": "static",
|
||||
"execution-verified": false,
|
||||
"checks": {
|
||||
"commands": {
|
||||
"total": 6,
|
||||
"verified": 6,
|
||||
"manual-required": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"paths": {
|
||||
"total": 9,
|
||||
"verified": 9,
|
||||
"failed": 0
|
||||
},
|
||||
"anchors": {
|
||||
"total": 0,
|
||||
"verified": 0,
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
"failures": [],
|
||||
"limitations": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "secret_scan",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "QUALITY_REVIEWED",
|
||||
"gates": [
|
||||
{
|
||||
"name": "review",
|
||||
"ok": true,
|
||||
"warnings": [],
|
||||
"data": {
|
||||
"verdict": "PASS",
|
||||
"score": 96,
|
||||
"findings": [
|
||||
{
|
||||
"id": "QR-001",
|
||||
"severity": "minor",
|
||||
"category": "command-boundary",
|
||||
"section": "quick-start",
|
||||
"message": "bootstrap 내부 Docker preflight 설명이 직접 실행 명령처럼 추출될 수 있었다.",
|
||||
"evidence": [
|
||||
"첫 기술 게이트가 inline docker info를 repository-facts에 없는 문서 명령으로 차단했다.",
|
||||
"수정 후 설명을 Docker CLI와 daemon 연결 확인이라는 서술로 바꾸고 재검증했다."
|
||||
],
|
||||
"route-to": "README_DRAFTED",
|
||||
"status": "resolved"
|
||||
},
|
||||
{
|
||||
"id": "QR-002",
|
||||
"severity": "minor",
|
||||
"category": "architecture-clarity",
|
||||
"section": "overview",
|
||||
"message": "초기 레이어 나열의 화살표가 의존 방향으로 오해될 여지가 있었다.",
|
||||
"evidence": [
|
||||
"초기 domain → application → adapter → bootstrap 표기를 중립적인 책임 목록으로 변경했다.",
|
||||
"실제 의존 방향은 architecture Mermaid에서 별도로 명시한다."
|
||||
],
|
||||
"route-to": "README_DRAFTED",
|
||||
"status": "resolved"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"state": "READY_FOR_APPLY",
|
||||
"gates": []
|
||||
},
|
||||
{
|
||||
"state": "APPLIED",
|
||||
"gates": []
|
||||
}
|
||||
],
|
||||
"rework": {
|
||||
"iterations": 0,
|
||||
"findings": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema-version": 1,
|
||||
"state": "PASS",
|
||||
"verification-level": "static",
|
||||
"execution-verified": false,
|
||||
"checks": {
|
||||
"commands": {
|
||||
"total": 6,
|
||||
"verified": 6,
|
||||
"manual-required": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"paths": {
|
||||
"total": 9,
|
||||
"verified": 9,
|
||||
"failed": 0
|
||||
},
|
||||
"anchors": {
|
||||
"total": 0,
|
||||
"verified": 0,
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
"failures": [],
|
||||
"limitations": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
schema-version: 1
|
||||
visuals:
|
||||
- id: architecture-dependency-direction
|
||||
section: architecture
|
||||
type: architecture-diagram
|
||||
purpose: core, adapter, composition root 사이의 허용된 프로젝트 의존 방향을 한 화면에 설명한다.
|
||||
placeholder-text: Mermaid flowchart로 inbound와 outbound가 application을 거쳐 domain 방향으로 의존하고 app-bootstrap이 조립하는 관계를 표시한다.
|
||||
must-show:
|
||||
- domain-core
|
||||
- application-core
|
||||
- inbound adapters
|
||||
- outbound adapters
|
||||
- app-bootstrap
|
||||
- shared-contract
|
||||
relationships:
|
||||
- inbound adapters -> application-core
|
||||
- outbound adapters -> application-core
|
||||
- application-core -> domain-core
|
||||
- app-bootstrap -> selected adapters and core
|
||||
emphasize:
|
||||
- 화살표는 런타임 호출이 아니라 프로젝트 의존 방향임
|
||||
- core가 adapter를 알지 않음
|
||||
avoid:
|
||||
- 실제로 선언되지 않은 인프라 구성요소
|
||||
- 모든 adapter가 app-bootstrap에 연결된다는 과장
|
||||
- 장식용 아이콘과 색상 의존 의미
|
||||
placement:
|
||||
after-section-id: architecture
|
||||
accessibility:
|
||||
alt-text: inbound와 outbound adapter가 application-core와 domain-core 방향으로 의존하고 app-bootstrap이 선택 모듈을 조립하는 구조
|
||||
production:
|
||||
format: mermaid
|
||||
status: embedded
|
||||
Reference in New Issue
Block a user