Files
clean-architecture-backend-…/infra/jpa/postgres/README.md
T
DongHyeonkaandClaude Opus 5 0e61f86eb5 feat(jpa): implement the JPA relational persistence platform
Implements the Stable and Experimental JPA persistence platform designs
against real PostgreSQL, adapted to this repository's fail-closed 19-leaf
registry.

The design models the platform as 25 Gradle projects. `src/settings.gradle`
throws unless the registry holds exactly 19 leaves, so the plan's modules
become packages inside `:adapter:outbound:persistence-jpa` (starter in
`:app-bootstrap`, testkit in its own source set). The full mapping, the
renames this repository's naming gate required, and every deliberate
substitution are recorded in `docs/jpa/repository-adaptation.md`.

Seven Docker-backed lanes replace the plan's seven JVM test suites. Each
fails closed: a lane that discovers nothing, or a container that cannot
start, is an error rather than a skip.

Three defects the contracts found against a real server:

- `CommitFailureClassifier` treated only SQLSTATE 40003, class 08, and
  transport breaks as completion-unknown. A backend terminated mid-commit
  reports 57P01, and the commit record may already be in the WAL — so a
  possibly-committed transaction could be re-run. 57P01/57P02/57P03 now
  classify as completion-unknown.
- `SchemaTenantMigrationOrchestrator` recorded `MigrateResult`'s target
  version, which is empty for a tenant already current, reporting migrated
  tenants as unmigrated during a partial rollout. It now reads the applied
  version back from the tenant's schema history.
- `JpaStreamExecutor` checked only the declared return type for reactive
  publishers, and `RegisteredPostgreSqlCopyLoader` passed the COPY timeout
  to `SET`, which is parsed before parameter binding.

`JpaModuleBoundaryTest` enforces the plan's module map as package rules;
`verifyCleanArchitectureDependencies` governs edges between leaves and
cannot see these. Its first assertion is that the import is non-empty,
because every rule under it is a `noClasses()` rule and would pass
vacuously on an empty import.

Verified: 128 container tests across all seven lanes, 1183 unit tests,
`:adapter:outbound:persistence-jpa:check`, `:app-bootstrap:check`,
`verifyCleanArchitectureDependencies`, `verifyOneTypePerFile`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:06:18 +09:00

2.3 KiB

infra/jpa/postgres

Server-side settings the JPA platform's contracts assume, and why each one matters.

The contract suites start their own containers through dev.caskeleton.adapter.outbound.persistence.testkit.postgresql.PostgreSqlContainerFactory, so nothing here is needed to run them. This directory records what a deployed PostgreSQL has to look like for the platform's guarantees to hold, because several of them are server settings rather than application code.

Settings the platform depends on

Setting Why the platform cares
statement_timeout The last bound on a runaway statement. The platform sets transaction timeouts, but a single statement inside a transaction can still outlive the request that asked for it.
idle_in_transaction_session_timeout An idle open transaction holds its locks and its snapshot indefinitely, which blocks writers and prevents vacuum. This is what turns "someone left a transaction open" into a bounded incident.
lock_timeout A cluster-wide floor under the per-request lock bounds in PostgreSqlLockOptions.
max_connections The number app.jpa-platform.datasource.maximum-pool-size must be sized against — across every instance, and allowing for REQUIRES_NEW taking a second connection while pinning the first.
default_transaction_isolation Left at read committed. The platform selects repeatable read or serializable per transaction profile; changing the default would silently change every transaction that did not ask.

Suggested baseline

statement_timeout = '30s'
idle_in_transaction_session_timeout = '60s'
lock_timeout = '10s'
default_transaction_isolation = 'read committed'

These are starting points, not recommendations: the right statement_timeout depends on the slowest legitimate query in the application, and setting it below that turns a working report into an error. Measure before pinning.

What is deliberately not configured here

  • Roles. Credential separation lives in ../roles/runtime-roles.sql.
  • Schema. Flyway owns it (design §31). Nothing in this directory creates a table.
  • Extensions. The platform's PostgreSQL support — JSONB, arrays, ranges, SKIP LOCKED, ON CONFLICT — is all core PostgreSQL. No extension is required, and none should be assumed.