Files
clean-architecture-backend-…/docs/superpowers/plans/2026-07-28-jpa-production-capability.md
T

20 KiB
Raw Blame History

JPA/PostgreSQL Production Capability Implementation Plan

상태: Phase 03 기반과 Phase 4의 idempotency/outbox polling/inbox 후보 구현 및 전체 local/real PostgreSQL 검증을 마쳤다. 검증을 통과한 항목은 implemented-candidate이며 immutable 운영 evidence가 없는 항목을 R2로 승격하지 않는다. Phase 57은 외부 topology와 policy prerequisite가 없어 not-implemented를 유지한다.

  • 작성일: 2026-07-28
  • 구현 branch: codex/jpa-production-capability
  • worktree: /home/donghyeon/workspace/clean-architecture-backend-template-jpa
  • 시작 revision: b3add0162df8d4a0a11e749e514901defe0a62a3
  • 설계 원본: /home/donghyeon/workspace/clean-architecture-backend-template/docs/superpowers/specs/2026-07-28-jpa-production-capability-design.md
  • 설계 SHA-256: c02eaef2a193a6ca66f4814087cc4d6bce723509aec251f40ea7b029046fd234

설계 문서는 main worktree의 untracked 사용자 변경이므로 stage/commit/copy하지 않는다. 구현 중에는 위 절대 경로와 hash를 승인된 정본 snapshot으로 사용한다. 정본이 바뀌면 hash drift를 먼저 보고하고 해당 task의 설계를 재검토한다.

1. 목표와 완료 경계

목표는 JPA/PostgreSQL leaf의 각 capability를 독립적으로 구현·검증하는 것이다.

truthful baseline
  -> transaction/failure/deadline
  -> entity/query discipline
  -> migration/lifecycle/security
  -> owner-safe reliability
  -> optional replica
  -> optional tenant/coordination
  -> R3 rehearsal

한 phase의 unit test 통과를 전체 JPA R2로 확대하지 않는다. card가 R2가 되려면 설계 §31.3의 prerequisite, real PostgreSQL task, zero-skip sentinel과 immutable evidence manifest를 모두 충족해야 한다.

현재 구현 작업의 완료 경계는 다음과 같다.

  1. 독립 worktree와 계획이 존재한다.
  2. Phase 0의 SQLState, Duration, OSIV/DDL, machine-readable readiness baseline이 fail-closed한다.
  3. named transaction policy, absolute deadline, PostgreSQL local timeout, phase-aware outcome, bounded serialization/deadlock retry가 구현된다.
  4. PostgreSQL 16 real test source set에서 lifecycle/security/migration/transaction/ aggregate/query가 무-skip로 실행된다.
  5. owner-safe idempotency V2, immutable outbox storage V2, polling delivery V2, same-store inbox가 독립 migration stream과 real PostgreSQL concurrency test를 가진다.
  6. 외부 CDC, replica, tenant/RLS, R3는 토폴로지/evidence 없이 선택하거나 R2로 광고하지 않는다.
  7. 전체 test/check와 Wiki capture 결과를 기록한다.

2. 공통 구현 규칙

  • src/config/architecture/modules.json의 19개 leaf와 edge를 유지한다.
  • domain-core에는 Spring/JPA/JDBC/PostgreSQL type을 추가하지 않는다.
  • application contract에는 framework-neutral Java type만 둔다.
  • transaction boundary는 application use case가 TransactionPort로 소유한다.
  • controller/repository/mapper/configuration에 business policy를 두지 않는다.
  • PostgreSQL 전용 code/import는 persistence-jpa leaf의 .postgresql package에 둔다.
  • 동작 변경은 failing test를 먼저 확인한 뒤 최소 production code를 작성한다.
  • applied Flyway V1/V3/V4/V5는 수정하지 않는다.
  • agent는 stage/commit/amend/push하지 않는다.
  • 다른 worktree의 dirty/untracked 변경을 복사하거나 되돌리지 않는다.

worktree 생성 직후 src/gradlew.bat는 CRLF blob과 checkout/attribute line-ending normalization 차이 때문에 dirty로 표시된다. 비교 결과 의미 있는 텍스트 변경은 없지만 raw worktree hash와 HEAD blob hash는 EOL 표현 때문에 다르다. targeted restore로도 사라지지 않는 known baseline drift이므로 구현 diff와 완료 판정에서 분리하고 stage하지 않는다.

3. Phase 0 — Truthful baseline과 contract freeze

Task 0.1 SQLState mapping duplicate fail-fast

상태: 2026-07-28 구현 및 focused/architecture 검증 완료.

소유 leaf: adapter-outbound-persistence-jpa

파일:

  • 수정: src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/failure/PersistenceExceptionTranslatorTest.java
  • 수정: src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/failure/PersistenceExceptionTranslator.java
  • 필요 시 수정: src/adapter/outbound/persistence-jpa/README.md

TDD:

  1. 서로 다른 두 SqlStateErrorMapping이 같은 exact SQLState에 같은 OperationalError를 등록해도 constructor가 실패하는 test를 작성한다.
  2. 같은 SQLState에 서로 다른 OperationalError를 등록하면 실패하는 test를 작성한다.
  3. error message가 raw SQL, credential, endpoint 없이 duplicate SQLState와 mapping contributor type을 식별하는지 검증한다.
  4. focused test를 실행해 RED를 확인한다.
  5. putAll을 explicit merge로 바꾸고 first/duplicate provenance를 보존한다.
  6. null mapping/map/key/value와 08* pseudo-entry를 fail-fast할지 현재 SPI 계약에 맞춰 validation test를 추가한다. 이 세부 계약은 범위를 키우지 않고 constructor invariant로 한정한다.
  7. focused test를 GREEN으로 만든다.

검증:

cd src
./gradlew :adapter:outbound:persistence-jpa:test \
  --tests 'dev.caskeleton.adapter.outbound.persistence.failure.PersistenceExceptionTranslatorTest' \
  --console=plain
./gradlew :adapter:outbound:persistence-jpa:test --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain

Task 0.2 Duration/OSIV/DDL production safety

상태: 2026-07-28 strict Duration와 prod DDL guard 구현 완료. OSIV guard는 기존 구현을 재사용하고 함께 회귀 검증했다.

소유 leaf:

  • app-bootstrap: runtime settings/startup validator
  • adapter-outbound-persistence-jpa: typed provider settings가 필요할 때만

선행 조사 파일:

  • src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java
  • src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidatorTest.java
  • src/app-bootstrap/src/main/resources/application.yml
  • src/app-bootstrap/CLAUDE.md

TDD:

  1. 5s, PT5S, millisecond number의 canonical/legacy 허용 matrix를 test로 고정한다.
  2. invalid/unknown Duration을 skip하지 않고 startup failure로 만드는 RED를 확인한다.
  3. spring.jpa.open-in-view=true를 거절한다.
  4. production profile의 ddl-auto=update|create|create-drop을 거절한다.
  5. local/sample compatibility를 별도 test로 유지한다.

검증:

cd src
./gradlew :app-bootstrap:test \
  --tests 'dev.caskeleton.bootstrap.runtime.HikariPoolConstraintValidatorTest' \
  --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
./gradlew verifyEnvKeys --console=plain

Task 0.3 Machine-readable readiness baseline

상태: 2026-07-28 구현 및 mutation/registry 검증 완료.

파일:

  • 추가: src/config/jpa/readiness-cards.yaml
  • 수정: src/build.gradle
  • 추가: persistence-jpa readiness registry parser/validation tests

구현:

  1. 설계 §31.3의 15 card와 7 owned migration stream을 exact key로 옮긴다.
  2. unknown/missing card, duplicate task, cycle, missing prerequisite, duplicate location/history를 fail-closed한다.
  3. 현재 구현되지 않은 task/card는 not-implemented로 유지한다.
  4. 존재하지 않는 target task를 통과 증거로 만들지 않는다.
  5. registry structural verification task를 check의 architecture policy chain에 연결하되 real PostgreSQL readiness를 거짓으로 통과시키지 않는다.

4. Phase 1 — Transaction/failure/deadline foundation

상태: 2026-07-28 application contract, Spring executor, local timeout, phase-aware outcome, bounded retry/backoff 후보 구현 완료. commit fault injection과 immutable R2 manifest는 남아 있다.

Task 1.1 Additive application transaction contract

소유 leaf: application-core

예상 파일:

  • 추가: transaction/TransactionPolicy.java
  • 추가: transaction/CallBudget.java
  • 추가: transaction/OperationId.java
  • 추가: transaction/TransactionOutcome.java
  • 추가: transaction/PolicyTransactionPort.java
  • 수정: transaction/TransactionPort.java
  • tests: 같은 package의 pure unit tests

계약:

  • 기존 inWrite, inRead, inNew source compatibility 유지
  • named write policy는 stable operation ID 요구
  • legacy facade는 non-replayable/uncorrelated policy로 격리
  • absolute deadline과 finite timeout intersection
  • core에는 Spring TransactionDefinition/DurationStyle을 노출하지 않음

Task 1.2 Spring policy executor와 propagation ownership

소유 leaf: adapter-outbound-persistence-jpa

예상 파일:

  • 수정: transaction/SpringTransactionPort.java
  • 추가: transaction/SpringPolicyTransactionPort.java
  • 추가: transaction phase/outcome collaborator
  • tests: unit + real PostgreSQL task

검증:

  • REQUIRED physical owner와 participant 구분
  • REQUIRES_NEW depth/capacity admission
  • read/write route mismatch fail-fast
  • commit callback ordering
  • locale 없는 toLowerCase() 제거

Task 1.3 Deadline와 PostgreSQL local timeout

  • Hikari acquisition은 fixed pool timeout으로 유지
  • action 시작 전 remaining budget pre-gate
  • first statement 전 SET LOCAL statement_timeout, lock_timeout
  • transaction/statement/lock rounding boundary test
  • pool wait 뒤 total budget overshoot negative test

Task 1.4 Phase-aware failure/retry

  • operation/query executor를 모든 production persistence path에 연결
  • constraint name allowlist
  • begin/action/flush/commit/after-completion phase 분류
  • COMMIT_INDETERMINATE는 blind retry 금지
  • pre-commit + replay-safe + budget 조건에서만 whole-transaction retry

5. Phase 2 — Entity/query discipline

상태: production template에 임의 business aggregate를 추가하지 않고 sample의 기존 entity/ mapper/query discipline을 실제 PostgreSQL aggregate CAS와 query-plan fixture로 검증했다.

Task 2.1 Aggregate persistence baseline

  • domain aggregate와 persistence entity 분리
  • mapper round-trip과 invariant failure test
  • optimistic version/expected-version conflict
  • audit creation carry-forward와 bulk DML guard
  • bounded persistence-context batch

Task 2.2 Purpose-built query model

  • application projection *QueryPort
  • allowlisted query ID
  • max page/IN bound와 signed/versioned keyset cursor
  • N+1 statement budget
  • native/JDBC query는 .postgresql package
  • representative EXPLAIN invariant task

6. Phase 3 — Migration/lifecycle/security

상태: legacy V1/V3/V4/V5/V6 adoption, independent core stream, PostgreSQL 16 lifecycle/security/ migration/transaction/aggregate/query candidate task와 content-addressed manifest producer 구현 완료. TLS verify-full/role/redaction, pool lifecycle, fresh/interrupted/rolling migration, transaction concurrency/fault dimension을 실제 PostgreSQL과 transport test로 채웠다. clean CI provenance와 외부 restore rehearsal이 없으면 R2/R3 aggregation은 계속 fail-closed한다.

Task 3.1 Legacy adoption과 independent streams

  • legacy V1/V3/V4/V5 checksum/object fingerprint
  • capability_schema_registry
  • explicit target stream version-0 adoption command
  • core/optional history table ownership
  • fresh/LEGACY_ADOPTED/interrupted paths
  • old/target dual authority rejection

Task 3.2 Real PostgreSQL qualification source set

canonical tasks:

postgresqlLifecycleIntegrationTest
postgresqlSecurityBaselineIntegrationTest
postgresqlMigrationIntegrationTest
postgresqlTransactionIntegrationTest
postgresqlAggregateIntegrationTest
postgresqlQueryIntegrationTest
verifyJpaPrimaryFoundationEvidence

Docker/Testcontainers가 없으면 R2 lane은 skip이 아니라 fail이다. local optional task와 evidence producer를 분리한다.

구현된 evidence task:

verifyJpaEvidenceHarnessContract
generateJpaEvidenceManifests
verifyJpaCandidateEvidence
verifyJpaPrimaryFoundationEvidence

candidate task는 11개 active card의 exact JUnit selector, zero-skip count, source/이미지/의존성 version과 prerequisite manifest ID를 SHA-256 filename manifest로 남긴다. primary task는 -PjpaEvidenceProfile=r2, clean revision, CI job/artifact metadata, 모든 base dimension과 prerequisite R2를 추가로 요구한다.

Task 3.3 Lifecycle/security

  • migration/runtime role 분리
  • trusted schema/search_path, PUBLIC CREATE/TEMP revoke
  • TLS verify-full profile
  • startup/readiness/shutdown/quiesce
  • bounded/redacted metric/trace/log
  • restore/forward-recovery runbook

7. Phase 4 — Owner-safe same-store reliability

상태: idempotency V2, outbox storage V2, polling delivery V2, inbox V1은 각각 implemented-candidate. 네 stream 모두 fresh-disabled/first-enable/disable/re-enable/ interrupted-recovery의 non-destructive lifecycle을 실제 PostgreSQL에서 검증한다. CDC는 external messaging prerequisite가 없어 not-implemented다.

독립 implementation slice:

  1. jpa-idempotency-owner-safe-v2
  2. jpa-outbox-storage-v2
  3. jpa-outbox-polling-delivery-v2 또는 jpa-outbox-cdc-retention-v1
  4. jpa-inbox-same-store-v1

각 slice는 자기 migration stream/task/manifest를 가진다.

outbox storage 구현은:

  • V3 outbox_event를 수정하지 않음
  • outbox_publication_control_v2
  • outbox_publication_cutover_v2
  • outbox_event_identity_v2
  • outbox_event_log_v2
  • polling 선택 시에만 outbox_delivery_v2
  • fresh/legacy genesis sentinel
  • legacy mutation trigger/ACL fence
  • paused old writer와 cutover barrier test

를 포함한다.

8. Phase 57

상태: 선택된 replica topology, tenant mode/RLS policy, target-like backup/failover environment가 없으므로 registry에서 not-implemented를 유지한다. 로컬 단일 PostgreSQL 테스트를 해당 운영 보장의 대체 evidence로 사용하지 않는다.

Phase 5 — Primary/replica

  • 별도 pool/route context
  • explicit ReadConsistency
  • endpoint-bound lag evidence
  • strong/RYW primary default
  • failover authority reconciliation

Phase 6 — Tenant/RLS와 JDBC coordination

  • tenant-prefixed unique/FK/query
  • missing context fail-closed
  • optional FORCE RLS
  • runtime role bypass negative test
  • JDBC coordination은 EFFICIENCY_ONLY

Phase 7 — R3

  • target-like load/capacity
  • failover, rolling migration, certificate rotation
  • backup/PITR restore
  • outbox/idempotency/inbox reconciliation
  • measured RPO/RTO와 operator game day

9. 공통 verification ladder

변경 leaf focused test부터 실행한다.

cd src
./gradlew :application-core:test --console=plain
./gradlew :adapter:outbound:persistence-jpa:test --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew verifyPublicPathSnapshot --console=plain
./gradlew verifyEnvKeys --console=plain

전체 test/check와 real PostgreSQL task는 해당 phase가 경계를 실제로 변경하거나 required task를 추가한 시점에 실행한다. 실행하지 못한 명령은 이유와 남은 위험을 branch-note와 최종 응답에 기록한다.

10. Wiki capture

각 의미 있는 slice가 끝날 때 실제 vault의 branch-note:

raw/branch-notes/codex-jpa-production-capability.md

에 다음을 누적한다.

  • design hash와 plan path
  • 변경 파일/decision ID
  • RED/GREEN/architecture command와 결과
  • 실패/차단/known baseline drift
  • evidence grade와 아직 R2가 아닌 이유
  • 실제 파생 raw interview/blog/error 판단

canonical 문서는 별도 요청 전 생성하지 않는다.

11. 최종 실행 결과

2026-07-28:

  • ./gradlew :sample-portfolio:test --console=plain → 성공, 176 tests.
  • ./gradlew test --console=plain → 성공, 1m 59s.
  • PostgreSQL readiness task 10개 (lifecycle, security, migration, transaction, aggregate, query, idempotency, outbox-storage, outbox-polling, inbox) → 성공, 49s. XML 합계 23 tests, skipped=0, failures=0, errors=0.
  • ./gradlew check --console=plain → 성공, 2m 9s, 209 actionable tasks. 같은 실행에서 root architecture policy, Checkstyle, Spotless, SpotBugs와 custom PostgreSQL source set 검증을 통과했다.
  • ./gradlew verifyCleanArchitectureDependencies verifyPublicPathSnapshot verifyEnvKeys verifyJpaReadinessRegistry --console=plain → 성공. 19개 leaf edge, 1개 public path, 113 env keys, exact 15 cards/7 streams 검증.
  • git diff --check → 진단 없음.
  • ./gradlew :adapter:outbound:persistence-jpa:verifyJpaPrimaryFoundationEvidence --console=plain → 기존 unconditional sentinel을 제거했다. content-addressed candidate manifest를 검증한 뒤 candidate profile과 observability, TLS/role/redaction, fresh/interrupted/rolling migration, transaction concurrency 누락을 card별 blocker로 보고 R2를 차단한다.
  • ./gradlew :adapter:outbound:persistence-jpa:verifyJpaCandidateEvidence --console=plain → 성공, active card 11개 manifest 생성. PostgreSQL 23 tests와 primary base aggregation 7 tests 모두 zero-skip이고 content hash/prerequisite link를 검증했다.
  • bash .github/scripts/verify-gate-matrix.sh → 성공, 21 gates verified. PR candidate evidence job과 conditional R2 workflow가 registry에 반영됐다.
  • CI metadata를 주입한 verifyJpaPrimaryFoundationEvidence -PjpaEvidenceProfile=r2 → PostgreSQL 23 tests와 r2-profile manifest 11개 생성 뒤 의도된 실패, 1m 21s. worktree-is-dirty, observability, TLS/roles/redaction, migration fresh/interrupted/rolling, transaction concurrency를 실제 blocker로 보고했다.
  • ./gradlew test --console=plain → 성공, 15s, 78 tasks up-to-date. 직전 evidence lane에서 persistence/app test는 강제 재실행했다.
  • ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --console=plain → 성공, 9s, 230 actionable tasks(37 executed, 193 up-to-date).

전체 test에서 발견한 sample Flyway 회귀는 independent V1 stream을 broad classpath:db/migration으로 합친 문제와 production/sample V6 충돌이었다. sample slice를 legacy PostgreSQL location으로 한정하고 disposable poster migration을 V7로 이동했다. 세부 재현·해결 기록은 Wiki raw/errors/flyway-independent-stream-broad-root-collision-2026-07-28.md에 남겼다.

2026-07-29 completion pass:

  • primary foundation의 pool lifecycle/observability, TLS verify-full/role/redaction, fresh/interrupted/rolling migration, transaction concurrency/fault evidence를 추가했다.
  • idempotency/outbox storage/outbox polling/inbox 네 독립 stream에 fresh-disabled/first-enable/disable/re-enable/interrupted-recovery 실제 PostgreSQL lifecycle test를 추가했다.
  • ./gradlew :adapter:outbound:persistence-jpa:verifyJpaCandidateEvidence --console=plainBUILD SUCCESSFUL in 1m 55s. 11개 manifest 모두 missing=none, zero-skip. PostgreSQL producer 38 tests와 web redaction support 2 tests가 실행됐으며 primary aggregation은 20 tests다.
  • ./gradlew test --console=plainBUILD SUCCESSFUL in 55s, 78 actionable tasks.
  • ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --console=plain → 포맷과 test fixture SQL construction을 수정한 뒤 BUILD SUCCESSFUL in 12s, 231 actionable tasks. 19 leaf architecture, Checkstyle, Spotless, SpotBugs, dependency lock, env/readiness/public-path gate를 통과했다.
  • CI 메타데이터 형식만 주입한 verifyJpaPrimaryFoundationEvidence -PjpaEvidenceProfile=r2의도된 BUILD FAILED in 2m 6s. missing evidence는 없고 root blocker는 worktree-is-dirty; 다른 blocker는 prerequisite R2 전파뿐이다.
  • bash .github/scripts/verify-gate-matrix.shOK, 21 gates/21 verified.
  • git diff --check → 진단 없음.

현재 환경에서 선택된 Phase 0~4 후보의 로컬 구현·검증은 완료됐다. R2 승격은 사람의 commit/push, clean revision에서의 retained CI artifact가 필요하고, R3는 target-like backup/failover/load/operator rehearsal 환경이 필요하다.