22 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, git_branch, parent_branch, related_projects, tags, created, target_merge, status_label, evidence_grade, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | git_branch | parent_branch | related_projects | tags | created | target_merge | status_label | evidence_grade | contract_packet_sha256 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / experiment-nplus1-feed-api-replay (11-stage real DB and HTTP replay) | branch-note | raw | BR-NPLUS1-PRESENTATION-PREP-002 | project-work-item | nplus1-presentation-prep | WI-NPLUS1-PRESENTATION-PREP-002 |
|
|
1 | experiment-nplus1-feed-api-replay | lab/nplus1-api-replay |
|
|
2026-07-15 | review | locally-verified | 88f5e6fd5ec219c6017f8213d076cde1803e63e62f978fe926f86ddcbc616f41 |
branch: experiment-nplus1-feed-api-replay
Layer:
raw/branch-notes/— 기존 raw/branch-notes/experiment-nplus1-highlight-feed의 L1~L16/Crown/L12 결과를, 실제 PostgreSQL과 HTTP로 한 단계씩 재현할 수 있게 만든 11-checkpoint replay 브랜치다. 실제 Git branch는lab/nplus1-api-replay다. wiki slug는 파일명 규칙에 맞춘 별도 식별자다.
부모 (필수)
- raw/project-notes/nplus1-presentation-prep
- 관련 선행 작업: raw/branch-notes/experiment-nplus1-highlight-feed — 각 랩의 원래 문제·측정·해법 사슬을 소유한다. 이 노트는 그 결과를 checkout 가능한 API/DB 학습 경로로 만드는 작업만 소유한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: 11개 replay tag와 guide mapping을 고정하고, clean clone/worktree에서 Compose·HTTP·PostgreSQL smoke 및 full-check 상태를 재검증하며 lab profile의 deployment 비활성 증거를 기록한다.
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-NPLUS1-PRESENTATION-PREP-LEARNING-001@1 |
Measure→Break→Diagnose→Fix→Re-measure→Generalize를 랩 완료 루프로 사용한다. | 11개 checkout point마다 동일한 reset→HTTP→DB 관찰 절차를 제공한다. | raw/project-notes/nplus1-presentation-prep |
DEC-NPLUS1-PRESENTATION-PREP-SUBSTRATE-001@1 |
ca-tmpl production substrate를 사용하고 학습 API·측정 경로는 profile과 sibling 경로로 격리한다. | /api/lab/**와 marker-owned fixture를 lab profile에 한정한다. |
raw/project-notes/nplus1-presentation-prep |
DEC-NPLUS1-PRESENTATION-PREP-EVIDENCE-001@1 |
local·Testcontainers 결과는 locally-verified로만 기록하고 prod evidence로 승격하지 않는다. | Compose/Testcontainers 결과를 locally-verified로만 기록한다. |
raw/project-notes/nplus1-presentation-prep |
DEC-NPLUS1-PRESENTATION-PREP-SCOPE-001@1 |
same-store CQRS-lite까지를 현재 범위로 두고 full CQRS는 ca-tmpl contract escalation 이후에만 허용한다. | Crown 1-query와 L12 2-query endpoint를 병존시킨다. | raw/project-notes/nplus1-presentation-prep |
브랜치 지역 결정
기존 branch-local 결정은 아래 ## Decision Evidence Map의 D1~D5가 소유한다.
선언한 예외
- 없음.
목표
마지막 Crown/L12 상태만 남은 작업 트리에서는 L1의 컬렉션 N+1부터 Crown의 통합 쿼리까지를 HTTP와 실제 DB로 순서대로 관찰하기 어렵다. 이를 11개의 독립 checkout point로 고정한다.
-
각 tag에서 Docker PostgreSQL을 띄우고 lab fixture를 reset한 뒤 API 응답과 DB row를 직접 확인한다.
-
정상
/api/feed동작은 바꾸지 않고,labprofile에서만 학습용/api/lab/**경로를 제공한다. -
Crown의 1-query read와 L12의 same-store CQRS-lite 2-query read를 같은 것으로 포장하지 않고, 별도 endpoint와 문서로 비교 가능하게 둔다.
-
이슈: 사용자 요청 — N+1 랩을 실 API/DB로 단계별 학습
-
PR: 없음 (로컬 replay branch)
범위
포함 범위
- L1, L2, L3, L4, L5, L6, L14, L15, L16, Crown, L12 순서의 정확히 11개 commit/tag.
- lab profile, marker-safe fixture, HTTP 관찰 endpoint, PostgreSQL 확인 절차와 단계별 가이드.
- Crown 및 L12의 실제 PostgreSQL/Testcontainers 검증과 이력 tag의 L1 smoke 검증.
제외 범위
/api/feed의 production 계약 또는 기본 보안 정책 변경.- L12를 별도 read store·outbox 동기화가 있는 Full CQRS로 확장.
- 프로덕션 배포, 부하/latency SLA, 성능 수치의 운영 일반화.
근거
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/cqrs-pattern-azure-architecture-center | D3: 같은 저장소에서 read/write 논리를 분리하는 CQRS-lite와 별도 저장소 CQRS를 구분한다. |
| raw/official-docs/spring-data-jpa-projections-spring-official | D3: application 반환용 DTO/projection을 통해 aggregate hydration과 read shape를 분리하는 선택지를 뒷받침한다. |
| raw/official-docs/test-taxonomy-testcontainers-official | D4: in-memory 대체물이 아닌 Docker의 실제 PostgreSQL로 integration evidence를 얻는 선택을 뒷받침한다. |
TODO
- 11개 replay commit/tag를 사용자 지정 학습 순서로 고정 — 등급:
actually-implemented labprofile에서 실제 DB reset 및 HTTP feed 관찰 경로 제공 — 등급:actually-implemented- Crown/L12 최신 상태의 Docker HTTP + PostgreSQL smoke 수행 — 등급:
locally-verified - L1 historical tag의 독립 Docker HTTP smoke 수행 — 등급:
locally-verified - 누적 focused Gradle suite 및 dependency/public-path/env verifier 수행 — 등급:
locally-verified - 전체
./gradlew check를 branch 변경과 무관한 base architecture failure 없이 통과 — 등급:needs-confirmation(아래 §검증 기록 참조)
진행 중 메모
- application repository의 replay branch는
54cf7e7/nplus1-replay-l12까지 tag가 완료된 상태다. - full
check의 유일한 base failure는 별도 수정 범위로 남겼다. 이 노트의 evidence grade는 local Docker/Gradle 검증까지만 나타낸다.
결정 사항
- 2026-07-15 D1: 학습 순서를 원래 구현 시간순이 아니라
L1 → L2 → L3 → L4 → L5 → L6 → L14 → L15 → L16 → Crown → L12의 11개 tag로 고정한다. / 이유: 사용자가 각 commit으로 이동해 API/DB를 직접 관찰해야 한다. / 검토한 대안: 마지막 코드 하나와 문서만 제공. / 근거: 사용자 요구; 구체 tag 순서는 외부 자료가 정하지 않으므로UNSUPPORTED_IMPL_DECISION. - 2026-07-15 D2: 공개 학습 reset은
labprofile의/api/lab/**에만 두고, anonymous access는lab:reset하나에만 허용한다. / 이유: 실제 HTTP 재현은 가능해야 하지만 정상 profile의 feed/권한 정책을 약화하면 안 된다. / 검토한 대안:/api/feed에 reset/debug 파라미터 추가 또는 lab profile 전체 anonymous 허용. / 근거: D4 및 사용자 범위; profile/permission의 구체 모양은UNSUPPORTED_IMPL_DECISION. - 2026-07-15 D3: Crown은 1 native query API, L12는 same-store CQRS-lite 2-query read port API로 병존시킨다. / 이유: 쿼리 수 최소화와 application read-model 분리는 서로 다른 선택지다. / 검토한 대안: L12가 Crown endpoint를 조용히 대체. / 근거:
AZURE-CQRS-C2,AZURE-CQRS-C3,SPRING-PROJ-C4. - 2026-07-15 D4: Testcontainers 검증에 더해 fresh Docker Compose PostgreSQL에서 HTTP response와 SQL row count를 확인한다. / 이유: test-only assertion으로는 사용자가 직접 API/DB를 따라 보는 목표를 충족하지 못한다. / 검토한 대안: integration test 결과만 보관. / 근거:
TC-OFFICIAL-C1,TC-OFFICIAL-C3,TC-OFFICIAL-C5. - 2026-07-15 D5: Hibernate
addScalar를 Java SQL compile-time checker로 설명하지 않는다. / 이유: 이는 native-query result extraction의 runtime type mapping이며 SQL 문법/컬럼 존재성은 실행 시점에 검증된다. / 검토한 대안:addScalar가 SQL 안전성을 보장한다고 문서화. / 근거: 구현 관찰;UNSUPPORTED_IMPL_DECISION.
결정-근거 매핑
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 11개의 checkout 가능한 learning checkpoint | 사용자가 단계별 API/DB 관찰을 원할 때; 단일 현재 상태만 필요하면 하나의 branch 상태로 충분 | User request; UNSUPPORTED_IMPL_DECISION |
user-scoped requirement | history를 rewrite하면 tag/문서 매핑도 함께 갱신해야 함 |
| D2 | lab-only reset/API/anonymous boundary | 로컬 학습 profile일 때만; 정상 runtime에서는 lab bean/route 자체를 등록하지 않음 | TC-OFFICIAL-C1, TC-OFFICIAL-C3; UNSUPPORTED_IMPL_DECISION |
official-vendor-doc + unit/HTTP local verification | lab profile을 production에 실수로 활성화하지 않는 운영 절차는 별도 확인 필요 |
| D3 | Crown 1-query와 L12 2-query CQRS-lite 병존 | endpoint-specific optimization을 비교할 때; 별도 store가 필요하면 Full CQRS contract를 별도 설계 | raw/official-docs/cqrs-pattern-azure-architecture-center.md#AZURE-CQRS-C2, #AZURE-CQRS-C3, raw/official-docs/spring-data-jpa-projections-spring-official.md#SPRING-PROJ-C4 |
official-vendor-doc | L12은 keyset/visibility를 Crown처럼 모두 포함하지 않음 |
| D4 | real PostgreSQL HTTP+DB smoke | SQL dialect, container wiring, public response shape를 함께 확인할 때 | raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C1, #TC-OFFICIAL-C3, #TC-OFFICIAL-C5 |
official-vendor-doc + local runtime evidence | production traffic/permissions을 검증한 것은 아님 |
| D5 | addScalar compile-time 보장 부정 |
native query mapping 설명 시 항상 적용 | Local code/runtime behavior; UNSUPPORTED_IMPL_DECISION |
locally verified implementation fact | native SQL의 syntax/plan error는 CI compile이 아니라 query execution에서 발견됨 |
구현 가이드
1. 고정 replay checkpoint
Trace: D1. 단계 순서와 tag 이름은 사용자 학습 요구에서 정한
UNSUPPORTED_IMPL_DECISION이다. 각 checkpoint의 원래 N+1 원인/해결 설명은 raw/branch-notes/experiment-nplus1-highlight-feed를 참조한다.
| 순서 | Stage | Commit | Tag | checkout 후 주 관찰점 |
|---|---|---|---|---|
| 1 | L1 | e68dd67 |
nplus1-replay-l1 |
lazy highlights 컬렉션 N+1 |
| 2 | L2 | 138eb67 |
nplus1-replay-l2 |
EAGER ToOne fetch 수 |
| 3 | L3 | dc7495c |
nplus1-replay-l3 |
two-bag fetch의 예상 실패 |
| 4 | L4 | f257196 |
nplus1-replay-l4 |
collection fetch join + paging의 in-memory paging |
| 5 | L5 | be2a123 |
nplus1-replay-l5 |
batch fetch paging |
| 6 | L6 | cad3c15 |
nplus1-replay-l6 |
DTO scalar projection, entity load 0 |
| 7 | L14 | 26b196c |
nplus1-replay-l14 |
window query로 parent별 Top-N |
| 8 | L15 | 8ff0771 |
nplus1-replay-l15 |
keyset cursor |
| 9 | L16 | 3310897 |
nplus1-replay-l16 |
viewer visibility + keyset |
| 10 | Crown Task 4 | 3f0b82e |
nplus1-replay-crown |
Top-N + keyset + visibility one query |
| 11 | L12 | 54cf7e7 |
nplus1-replay-l12 |
CQRS-lite projection port, parent/child two queries |
각 stage의 세부 실습은 application repository의 docs/superpowers/plans/*nplus1*lab-guide.md, docs/notes/L*.md, docs/notes/crown.md를 사용한다. L12를 마지막 tag로 둔 것은 원래 번호가 아니라 이 replay의 학습 순서다.
2. lab profile의 API/권한 경계
Trace: D2, D4 /
TC-OFFICIAL-C1,TC-OFFICIAL-C3.
- UNSUPPORTED_IMPL_DECISION:
labprofile과lab:resetpermission 이름, marker 소유 방식, anonymous allowlist의 구체 구현은 외부 자료가 정하지 않는다. 정상 profile과 분리된 학습 reset 및 최소 권한 허용이라는 사용자 범위를 우선했다.
| 항목 | replay 계약 |
|---|---|
| profile | lab controller/usecase/fixture는 local lab profile에만 등록된다. |
| normal runtime | 기존 advisor를 유지하고 lab usecase/route를 등록하지 않는다. /api/feed의 기존 계약을 바꾸지 않는다. |
| reset authorization | POST /api/lab/feed:reset?count=N은 lab:reset을 선언한다. lab profile의 security advisor는 AnonymousAuthenticationToken을 인식해 anonymous에는 lab:reset 하나만 허용하고, 다른 permission은 거부한다. unit+HTTP로 확인했다. |
| fixture ownership | created_by = nplus1-lab marker 데이터만 삭제/재생성한다. |
| input guard | page=10001은 HTTP 400, VALIDATION_FAILED다. |
3. Crown과 L12의 의도적 차이
Trace: D3 /
AZURE-CQRS-C2,AZURE-CQRS-C3,SPRING-PROJ-C4.
| 경로 | 목적 | SQL 관찰값 | 포함 범위 |
|---|---|---|---|
GET /api/lab/feed at Crown/L12 |
Crown Task 4 최적화 | CROWN_TASK4_ONE_QUERY, prepared statement 1, entity load 0 |
visible parent keyset + Top-3 child를 하나의 native query로 읽음 |
GET /api/lab/feed/read-model at L12 |
CQRS-lite read model | parent projection 1 + child Top-3 query 1, integration test에서 entity/collection hydration 0 | same store의 application query port; Crown을 대체하지 않음 |
L12의 native child mapping에 사용한 addScalar는 runtime 결과 타입 매핑이다. SQL 문자열의 문법, table/column 이름, plan을 Java compiler가 검증하게 만드는 기능은 아니다. 따라서 native SQL은 Testcontainers/실제 PostgreSQL 실행으로 검증한다.
검증 기록
Docker HTTP + PostgreSQL smoke — final L12 tag
fresh nplus1-final Compose stack에서 nplus1-replay-l12(54cf7e7)을 실행했다.
| 수행 | 결과 | 등급 |
|---|---|---|
POST /api/lab/feed:reset?count=100 |
success=true, feed item 100개, highlight 1,961개 |
locally-verified |
GET /api/lab/feed?size=20&viewer=lab-user-008 |
item 20개, strategy=CROWN_TASK4_ONE_QUERY, prepared=1, entityLoads=0, parent당 Top-3 최대 3개 |
locally-verified |
GET /api/lab/feed/read-model?page=0&size=20 |
success=true, item 20개, parent당 Top-3 최대 3개 |
locally-verified |
GET with page=10001 |
HTTP 400, VALIDATION_FAILED |
locally-verified |
PostgreSQL psql |
created_by = nplus1-lab row count 100 |
locally-verified |
| anonymous authorization | AnonymousAuthenticationToken의 lab:reset만 허용하고 다른 permission은 거부하는 unit+HTTP 검증 |
locally-verified |
Historical L1 smoke
fresh stack에서 nplus1-replay-l1(e68dd67)을 별도로 실행했다.
| 수행 | 결과 | 등급 |
|---|---|---|
reset count=10 |
success=true |
locally-verified |
| feed request | item 10개, strategy=L1_LAZY_HIGHLIGHTS, prepared=24, collectionFetch=10 |
locally-verified |
Focused regression suite
다음 filtered cumulative suite는 BUILD SUCCESSFUL, test result의 failures=0, errors=0이었다.
cd /home/donghyeon/workspace/ca-tmpl-nplus1-api/src
./gradlew spotlessApply :application-core:test --tests dev.caskeleton.application.feed.GetFeedReadModelUseCaseTest --tests dev.caskeleton.application.feed.lab.LabFeedUseCaseTest --tests dev.caskeleton.application.feed.lab.LabFeedCursorTest :adapter:inbound:web:test --tests dev.caskeleton.adapter.inbound.web.controller.lab.LabFeedControllerTest :app-bootstrap:test --tests dev.caskeleton.bootstrap.contract.DeveloperExperienceContractTest --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedReadModelUseCaseIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedCrownIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedVisibilityIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedKeysetIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedTopNIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedProjectionIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedBatchFetchIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedFetchJoinPagingIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedToOneEagerIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedMultipleBagIT --tests dev.caskeleton.bootstrap.lab.LabFeedPersistenceIT
명령에 포함된 L1L6, L14L16, Crown, L12의 stage integration test class는 모두 통과했다.
다음 verifier도 통과했다.
./gradlew verifyCleanArchitectureDependencies verifyPublicPathSnapshot verifyEnvKeys
Full check의 기준선 실패
./gradlew check에는 정확히 하나의 잔여 architecture failure가 있었다. 이는 이 replay branch가 수정하지 않은 base commit 6f0b0d6의 IdempotencyRecordEntity.requestHash에 있는 columnDefinition = "char(64)"가 vendor-neutral entity rule을 위반한 것이다. 따라서 이 노트는 full check를 green이라고 주장하지 않으며, 재현 브랜치의 실패로 귀속하지 않는다.
엣지·실패·의존
- 실패·엣지 경로: lab profile 밖에서 lab reset을 사용하려 하면 endpoint/usecase가 등록되지 않아야 한다. lab profile에서도 anonymous access는
lab:reset하나에만 한정되고, 다른 permission은 거부된다. - 실패·엣지 경로: native query의
addScalar타입이 결과와 맞지 않거나 SQL이 잘못되면 compile이 아니라 integration/runtime 실행에서 실패한다. - 실패·엣지 경로: L12 read model이 Crown과 동등한 visibility/keyset solution이라고 가정하면 안 된다. L12은 same-store read port의 2-query projection이고 Crown 최적화 endpoint는 유지된다.
- 다른 계약 의존: raw/branch-notes/experiment-nplus1-highlight-feed의 각 랩 의미와 raw/branch-notes/feature-application-query-bypass-contract의 CQRS-lite/read-port 경계를 소비한다. Full CQRS physical read store는 후자 D2의 escalation 범위다.
- 검증 환경 의존:
DeveloperExperienceContractTest가 rootAGENTS.md존재를 요구해 replay worktree에 일시적인 ignored bridge를 두고 test 후 제거했다. 이는 application commit에 포함되지 않는다.
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| 11개 tag가 다른 machine에서도 compose/API guide대로 재현된다 | 로컬 Docker, Gradle cache, port 상태에 의존 | 깨끗한 clone/worktree에서 tag별 compose smoke 수행 | needs-confirmation |
| lab profile이 배포 환경에서 활성화되지 않는다 | local profile boundary는 production deployment policy를 증명하지 않음 | deployment manifest/env registry audit | needs-confirmation |
IdempotencyRecordEntity failure를 수정한 뒤 full check가 green이 된다 |
현재 branch가 해당 base failure를 고치지 않음 | 별도 base-fix branch에서 full check 실행 | planned |
마주친 문제
DeveloperExperienceContractTest가 checkout worktree root의AGENTS.md를 요구했다.- 원인: replay worktree의 contract discovery 조건.
- 시도: test 실행 중 ignored bridge를 일시적으로 제공.
- 해결: test 통과 후 bridge를 삭제했고 application history에는 포함하지 않았다.
- 별도 오류 노트: 아래 Cluster의 raw error 노트.
- full
check가IdempotencyRecordEntity.requestHashvendor-specificcolumnDefinition에서 멈췄다.- 원인: base
6f0b0d6에 이미 존재한 rule violation. - 해결: replay scope 밖으로 남기고 base failure로 명시했다.
- 원인: base
묶음
- raw/interviews/crown-one-query-vs-cqrs-lite-read-model
- raw/interviews/native-query-addscalar-runtime-validation
- raw/errors/developer-experience-contract-agents-bridge-2026-07-15
- raw/errors/idempotency-column-definition-base-check-failure-2026-07-15
아래 raw leaf는 생성되었고, 이 branch를 ## Parent upward link로 가진다. 두 번째 블로그 주제는 별도 raw 문서로 추출하지 않았다.
Sub-branches (세부 작업)
- 없음 — 11개 checkpoint는 하나의 replay branch history로 관리한다.
오류 기록 (이 branch 작업 중 발생)
- raw/errors/developer-experience-contract-agents-bridge-2026-07-15 — replay worktree root discovery와 임시 ignored bridge.
- raw/errors/idempotency-column-definition-base-check-failure-2026-07-15 — base
6f0b0d6의 vendor-neutral entity rule failure.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/crown-one-query-vs-cqrs-lite-read-model — one-query optimization과 same-store CQRS-lite를 구분하는 기준.
- raw/interviews/native-query-addscalar-runtime-validation — native mapping type과 SQL compile-time validation의 차이.
job-posting tie-ins (이 작업에서 파생된 글감)
- raw/blog-topics/nplus1-lab-checkoutable-api-replay-2026-07-15 — 테스트 코드를 실제 API/DB 학습 환경으로 변환한 방법.
raw/blog-topics/crown-query-and-cqrs-lite-boundary-2026-07-15.md— 이번 capture에서는 별도 raw 문서로 추출하지 않았다.- derived blog: 생성 전. canonical 검증 후
wiki/blog/후보를 결정한다.
관련 일일 노트
- 해당 없음 — 이 캡처 시점에는 별도 daily-note를 만들지 않았다.
완료 후 정리
- PR 링크: 없음.
- 리뷰 메모: 11개 replay tag와 L1/final Docker smoke, focused suite, architecture/public-path/env verifier를 local에서 확인했다.
- 머지 결과 / 배포 환경: local Docker Compose + local PostgreSQL만 검증. production 배포 검증 없음.
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: lab-profile replay API, 11 stage tag catalog, Crown/L12 분리 경로.locally-verified항목: L1 historical smoke, final Docker HTTP/PostgreSQL smoke, filtered Gradle suite/verifier.prod-verified항목: 없음.
- 추출하지 않을 항목 (planned / documented-only / abandoned): base
IdempotencyRecordEntityrule failure 해결, production profile/deployment verification.