DongHyeonkaandClaude Opus 5 561d02ae3a feat: 프로젝트 게시와 홈 focus, 그리고 기록 사이 연결을 실제로 가능하게 한다
계약에 선언만 되어 있고 구현이 없던 네 오퍼레이션과, 의도된 스텁으로 남아 있던
catalog 두 종류가 공개 화면 다섯 곳을 조용히 비워 두고 있었다.

프로젝트 게시 — 프로젝트는 `RecordKind` 에 없어 문서 게시 파이프라인을 타지 못하는데,
공개 조회들(프로젝트 목록·상세·프로필의 "현재 프로젝트"·홈 focus)은 전부
`public_resource_projection` 의 PROJECT 행을 가시성 관문으로 쓴다. 그 행을 세우는
경로가 없었으므로 프로젝트는 만들 수는 있어도 공개될 수는 없었다. 릴리스가 자체 경로를
갖는 것과 같은 이유로, 문서 파이프라인에 끼워 넣지 않고 원본 상태·투영·canonical
route 세 곳을 한 트랜잭션에서 함께 세운다.

홈 focus — `home_focus_config` 는 마이그레이션이 빈 행 하나만 넣어 두었고 그 값을
읽고 쓸 use case 가 없었다. 지목한 대상이 실제로 있는지는 여기서 확인한다. 테이블에
FK 가 없어(설정이 대상보다 오래 살아남는 것을 허용하는 설계다) 없는 id 도 저장되고,
그러면 공개 화면은 조용히 빈 focus 를 그린다 — 저장은 성공했는데 아무것도 안 나오는,
이유를 알 수 없는 실패가 된다. 대상이 공개인지는 확인하지 않는다: 미리 지목해 두고
게시와 동시에 뜨게 하는 것이 정상적인 순서다.

catalog RELATION/EVIDENCE — 「슬라이스 2·5에서 채운다」는 주석과 함께 `List.of()` 로
남아 있었다. 그래서 어떤 기록도 연결 대상 목록을 채울 수 없었다. RELATION 은 작성
중에 고르는 것이라 작업본까지 포함하고(두 문서를 같이 쓰면서 서로 잇는 것이 정상적인
순서다), EVIDENCE 는 읽는 사람이 따라갈 수 있어야 하므로 공개된 것만 포함한다.

계약은 이 네 오퍼레이션을 ProblemDetails 모양으로 두고 있었다. 백엔드가 모든 JSON
응답을 envelope 으로 감싸므로 구현하는 순간 어긋난다 — 나머지와 같은 모양으로 옮겼다.
`/home-focus` 는 케밥 세그먼트라 D19(AIP-122)를 위반해 `/home/focus` 로 나눴다.

함께 고친 것들 (모두 이 작업 전부터 빨간 상태였다):
- `error-codes.yaml` 의 DOCUMENT_NOT_FOUND 가 표면마다 하나씩 두 행이었다. 이
  레지스트리의 식별자는 code 하나뿐이라 로딩 자체가 깨졌고, 그 여파로 거버넌스 테스트
  네 개와 outbox 계약 테스트가 함께 넘어졌다. 같은 코드는 같은 말을 해야 한다.
- 벤더된 계약 세 개의 MANIFEST.sha256 이 실제 파일과 어긋나 있었다.
- ActuatorSecurityHttpTest 는 "DB·Redis 없는 슬라이스"라고 적어 두고 Redis 자동설정을
  막지 않아, localhost:6379 연결 실패가 /actuator/health 를 503 으로 만들었다. 보안
  태세와 무관한 이유로 빨개지던 테스트다.
- 테스트 JVM 힙이 Gradle 기본 512m 이라 Spring context 캐시 + ArchUnit +
  Testcontainers 조합에서 OOM 이 났다. 증상이 테스트 실패가 아니라 "Executor 를 완료할
  수 없음"이어서 원인을 가리켰다.
- APP_SESSION_TIMEOUT 이 env-keys 레지스트리에 없었다.

새 SQL 은 실제 PostgreSQL 위에서 돌린다 — 컴파일도 단위 테스트도 컬럼 이름을 검증하지
못한다는 것이 이 파일이 존재하는 이유다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEHXspz4rv5pB5wiiSsVDu
2026-08-23 18:19:07 +09:00

Tech Log Backend

Initialized from clean-architecture-backend-template revision 0a6dd0e419620683de48f69b5c6d22d9964b6f44 as a tracked snapshot. Template updates are applied explicitly and recorded in template.lock.json.

ca-skeleton은 Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 기반의 Clean Architecture 백엔드 템플릿입니다. fork해서 도메인·패키지·엔티티·유스케이스를 추가하면 새 서비스를 시작할 수 있고, 모듈 경계와 의존 방향은 그대로 유지합니다. 기본 패키지는 dev.caskeleton이며, 구체적인 예시 도메인은 제거되어 있습니다.

이 문서는 전체 구조와 첫 실행만 다룹니다. 모듈별 상세 규칙과 설계 근거는 각 모듈의 README와 CLAUDE.md가, 빌드·환경 변수 상세는 src/README.md가 소유합니다.

아키텍처 한눈에

의존은 항상 바깥에서 안으로 흐릅니다. adapter가 core의 port에 의존하고, core는 adapter를 알지 못합니다. 이 방향이 유지되는 한 도메인 규칙과 기술 선택(웹 프레임워크, DB, 메시징)을 서로 독립적으로 바꿀 수 있습니다.

app-bootstrap -> adapter:inbound:*  -> application-core -> domain-core
app-bootstrap -> adapter:outbound:* -> application-core -> domain-core
모든 런타임 모듈 -> shared-contract

family 수준의 책임은 다음과 같습니다.

모듈 family 책임
domain-core 순수 도메인 모델·불변식·이벤트·port. 프레임워크·transport·DB·IO 타입 금지
application-core command·유스케이스·application 정책·트랜잭션 port. inbound DTO·persistence entity 금지
adapter:inbound:* HTTP·gRPC·GraphQL·WebSocket transport 경계, DTO·validation·인증·에러 매핑
adapter:outbound:persistence-* JPA/PostgreSQL·MongoDB 영속 구현과 매핑·migration
adapter:outbound:* support(공유 베이스)·messaging·cache·notification·object storage·file·HTTP client·identifier 능력을 port 뒤에서 구현. 외부 연동 어댑터(messaging·cache·notification·HTTP client)는 기본 비활성
shared-contract skeleton 전역 운영 계약. business/domain 개념 저장 금지
app-bootstrap Spring Boot entrypoint와 composition root

정확한 18개 leaf 목록과 각 leaf의 Gradle path·소스 경로·허용 production 의존 edge는 src/config/architecture/modules.json이 SSOT입니다. focused test는 해당 Gradle path에서 ./gradlew <gradle-path>:test --console=plain 형태로 파생하며, root 문서나 기억에서 개별 leaf edge를 추론하지 않습니다.

퀵스타트

전제조건은 Temurin 21(루트 .tool-versions에 고정)과 Docker Engine 또는 Docker Desktop입니다. Gradle은 저장소 wrapper를 씁니다. 첫 실행 진입점은 하나입니다.

cd src
./gradlew bootstrap

bootstrap은 compile 검사, PostgreSQL Compose 기동, 애플리케이션 이미지 build·기동(startup Flyway 포함), GET /api/healthcheck HTTP smoke를 순서대로 실행합니다. 각 단계가 별도 Gradle task라 실패 단계가 task 이름으로 드러납니다. 기동을 확인하려면 health endpoint를 호출합니다.

curl -fsS http://localhost:8080/api/healthcheck

로컬 스택을 내릴 때는 저장소 루트에서 실행합니다.

docker compose -f docker-compose.yml -f docker-compose.local.yml down

src/.env는 커밋된 안전 기본값이라 별도 .env.example을 만들지 않습니다. 전체 환경 변수 목록과 조정 시점은 src/README.mddocs/registries/env-keys.yaml에 있습니다.

프로파일별 데이터스토어

bootstrap은 컨테이너 경로(PostgreSQL)를 검증하는 첫 실행 진입점입니다. 일상 개발은 Docker 없이 돌리는 local 프로파일이며, 이때 데이터스토어는 H2 in-memory입니다.

cd src
./gradlew :app-bootstrap:bootRun
프로파일 데이터스토어 스키마 소유자
local (bootRun 기본) H2 in-memory Hibernate create-drop
dev PostgreSQL Flyway
prod PostgreSQL Flyway

local은 wiring과 애플리케이션 동작을 검증하고, migration과 vendor 동작은 검증하지 않습니다. 프로파일별 설정은 src/app-bootstrap/src/main/resources/application-{local,dev,prod}.yml이, 상세 설명은 src/README.md가 소유합니다.

새 프로젝트로 시작하기

이 저장소를 새 서비스의 출발점으로 쓸 때 핵심 단계는 다음과 같습니다. 전체 체크리스트는 AGENTS.md의 "템플릿 재사용 체크리스트"에 있습니다.

  1. src/settings.gradlerootProject.name을 새 서비스 이름으로 바꿉니다.

  2. 패키지 루트 dev.caskeleton을 조직·서비스 패키지로 바꿉니다. 소스뿐 아니라 빌드·설정 파일의 참조도 함께 바꿔야 mainClass·group이 어긋나 bootstrap이 깨지지 않습니다.

    cd src
    find . -type f \( -name '*.java' -o -name '*.gradle' -o -name '*.yml' \) -print0 | xargs -0 sed -i 's/dev.caskeleton/com.yourorg.yourservice/g'
    

    애플리케이션 이름 등 나머지 rename 단계는 위 체크리스트를 따릅니다.

  3. CaSkeletonApplication을 새 애플리케이션 이름으로 바꾸고, 목표 도메인의 엔티티·repository port·유스케이스·adapter를 production 모듈에 추가합니다.

  4. 모듈 이름과 경계는 그대로 유지합니다.

검증은 전체 테스트와 sample-off 재유입 방지 계약을 모두 통과시킵니다.

cd src
./gradlew test
./gradlew :app-bootstrap:sampleOffTest

sampleOffTest는 삭제된 샘플 타입이 production bootstrap classpath에 다시 들어오지 않는지 검증합니다.

아키텍처 규칙과 검증

애플리케이션이 동작하더라도 아래를 어기면 병합하지 않습니다. 8개 HARD-STOP 조건의 정본 로컬 정책 권위는 AGENTS.md이며, CLAUDE.md는 동기화된 요약입니다.

  • domain-core는 Spring·JPA·Servlet·HTTP·DB·cloud SDK 타입을 import하지 않습니다.
  • controller는 repository를 직접 호출하거나 persistence entity를 반환하지 않습니다.
  • inbound DTO는 application-coredomain-core로 들어가지 않습니다.
  • 비즈니스 정책은 mapper·filter·config·settings·controller에 두지 않습니다.
  • 새 외부 시스템 연동은 domain/application port와 adapter 모듈로 표현합니다.

이 규칙은 두 축으로 자동 강제합니다. ArchUnit CleanArchitectureTest가 컴파일된 소스 의존성을, verifyCleanArchitectureDependencies 게이트가 JSON registry의 허용 Gradle project edge를 검사합니다.

cd src
./gradlew verifyCleanArchitectureDependencies

두 검증 축은 ci-quality-gates.yml의 release gate에 연결되어, 규칙 위반이 병합·릴리스를 막습니다.

더 알아보기

S
Description
No description provided
Readme
7 MiB
Languages
Java 99.3%
Shell 0.4%
PLpgSQL 0.2%