Files
clean-architecture-backend-…/src/adapter/outbound/persistence-jpa/build.gradle
T

172 lines
10 KiB
Groovy

plugins {
id 'ca.spring-library'
id 'ca.spring-config'
id 'java-test-fixtures'
id 'ca.jpa-evidence'
id 'ca.jpa-test-lanes'
id 'ca.auxiliary-source-set'
}
// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
// JPA persistence adapter — merged RDBMS base + PostgreSQL vendor module.
// Owns JPA entities, Spring Data repositories, mappers, transaction/audit/lock/outbox port
// implementations, and the vendor-neutral SPI interfaces (OutboxClaimRepository /
// SqlStateErrorMapping). The PostgreSQL driver, flyway-database-postgresql dialect, and vendor
// Flyway migrations live only under the .postgresql subpackage (ArchUnit keeps the base neutral).
// The JPA relational persistence platform (docs/superpowers/specs/2026-08-11-jpa-persistence-
// platform-design.md) models itself as 18 Stable library modules. This repository's fail-closed
// module registry outranks that layout, so those modules are packages here and
// JpaModuleBoundaryTest enforces the design's module dependency table. The full mapping is in
// docs/jpa/repository-adaptation.md.
//
// The testkit is its own source set rather than part of `test` because more than one lane consumes
// it and because a source set whose dependencies are declared only on the test configurations gives
// the design's "no production module depends on the testkit" guarantee without a new Gradle project.
auxiliarySourceSets {
sourceSet('postgresqlIntegrationTest') {
// 'testFixtures' as well as 'main': the integration lane consumed the testkit through the
// convention's `consumedBy 'test', 'postgresqlIntegrationTest'`, and java-test-fixtures only
// wires `test` on its own. The lane has to say so (ADR-BUILD-001).
compilesAgainst 'main', 'testFixtures'
inherits 'implementation', 'compileOnly', 'runtimeOnly', 'annotationProcessor'
}
sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
}
dependencies {
implementation project(':application-core')
implementation project(':shared-contract')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// feature-distributed-lock-contract: Spring Integration JDBC LockRegistry backs the
// multi-instance distributedLockProvider. Version managed by Spring Boot BOM.
implementation 'org.springframework.integration:spring-integration-jdbc'
// Vendor (PostgreSQL): Flyway migration API + PostgreSQL driver/dialect. Used only by the
// .postgresql subpackage; the RDBMS base stays vendor-neutral (PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL).
implementation 'org.springframework.boot:spring-boot-starter-flyway'
runtimeOnly 'org.postgresql:postgresql'
runtimeOnly 'org.flywaydb:flyway-database-postgresql'
// Vendor (H2): the local-profile driver. Used only by the .h2 subpackage, which reaches it
// through JDBC/JPA rather than by importing org.h2 types — the same shape as the PostgreSQL
// driver above. Not `developmentOnly`: local is a deployable profile of this artifact, and the
// vendor selector, not the packaging, decides which driver a deployment loads.
runtimeOnly 'com.h2database:h2'
// JPA platform observability (design §37). Micrometer's observation API already arrives with
// Spring; the meter registry does not, and the platform's transaction/query/retry metrics need
// it. Version managed by the Spring Boot BOM.
implementation 'io.micrometer:micrometer-core'
// Querydsl and Envers are Advanced opt-ins (design §4.2): the platform implements their
// contracts, but the Stable runtime classpath must not carry either. compileOnly keeps them off
// every deployment while still compiling the support classes; a deployment that opts in adds the
// artifact itself, and the guards refuse the capability when the classes are absent.
compileOnly 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
compileOnly 'org.hibernate.orm:hibernate-envers'
testImplementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
testImplementation 'org.hibernate.orm:hibernate-envers'
testImplementation libs.archunit.junit5
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-postgresql'
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-toxiproxy'
postgresqlIntegrationTestRuntimeOnly 'org.postgresql:postgresql'
// Declared rather than inherited: the `testkit` source set extended testImplementation, so the
// fixtures compiled against every test library this leaf declared without stating any of them
// (ADR-BUILD-001). Listing them makes the fixtures' own surface reviewable.
testFixturesImplementation 'jakarta.persistence:jakarta.persistence-api'
testFixturesImplementation 'org.hibernate.orm:hibernate-envers'
testFixturesImplementation 'org.flywaydb:flyway-core'
testFixturesImplementation 'org.springframework:spring-jdbc'
// The rule pack names Spring Data's Repository marker in a rule; it needs the type on the
// compile classpath even though it never calls it.
testFixturesImplementation 'org.springframework.data:spring-data-commons'
testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
testFixturesImplementation libs.toxiproxy.java
testFixturesImplementation 'org.testcontainers:testcontainers'
testFixturesImplementation 'org.testcontainers:testcontainers-postgresql'
testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy'
testFixturesImplementation libs.archunit.junit5
testFixturesRuntimeOnly 'org.postgresql:postgresql'
// The performance lane starts its own servers: pool saturation is only observable against a
// real database, because the thing being measured is what happens when every connection to it
// is already held.
jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers'
jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers-postgresql'
jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
jpaPlatformPerformanceTestRuntimeOnly 'org.postgresql:postgresql'
}
// PostgreSQL readiness, tagged platform, and pool-contract lanes are owned by
// `ca.jpa-test-lanes`. Their metadata is represented as Java records, so task names, selectors,
// tags, descriptions and runtime property wiring are compiled rather than assembled from Groovy Maps.
// The security lane includes the runtime-role namespace-denial method selector in that typed model.
tasks.named('postgresqlSecurityBaselineIntegrationTest') {
// Cross-leaf task edge: see the handoff. verifyCleanArchitectureDependencies inspects
// configurations, not the task graph, so this edge is invisible to it.
dependsOn project(':adapter:inbound:web').tasks.named('jpaPersistenceRedactionContractTest')
}
// Which JPA lanes block a release is declared by .github/workflows/jpa-release.yml.
// This leaf declares the lanes and how each runs; it does not own release orchestration.
// The unit lane reads three files that are not Java sources: the release registry and its two
// renderings. Without declaring them, Gradle calls the lane up-to-date after a registry demotion or
// a workflow edit — so the drift check that exists to catch exactly that edit never runs on it.
// The unit lane reads files that are not Java sources: the release registry, its two renderings,
// and the documents that describe the pool lane. Without declaring them, Gradle calls the lane
// up-to-date after a registry demotion or a workflow edit — so the drift checks that exist to catch
// exactly those edits never run on them.
tasks.named('test') {
inputs.file(rootProject.file('config/jpa/release-registry.json'))
inputs.file(new File(rootProject.projectDir.parentFile, 'docs/jpa/support-matrix.md'))
inputs.file(new File(rootProject.projectDir.parentFile, 'docs/jpa/repository-adaptation.md'))
// The whole directory, not the two named workflows: the Experimental-major check asks whether
// *some* lane records that major as its target, so adding or deleting any workflow can change
// its answer. Naming files here would leave the lane that matters outside the up-to-date check.
inputs.dir(new File(rootProject.projectDir.parentFile, '.github/workflows'))
inputs.file(file('build.gradle'))
inputs.dir(file('src/jpaPlatformPerformanceTest/java'))
}
// verifyJpaApiSurface — every public type this leaf exposes is a committed decision.
//
// The GraphQL and Mongo leaves already carried this; the largest of the three did not, so the one
// public surface with the most adopters was the one nothing had an opinion about. The convention
// plugin is opt-in per leaf, and opting in was simply never done here.
//
// This is a record, not a budget, and the distinction matters. A snapshot shrinks nothing on its
// own: the GraphQL surface grew from 373 types to 398 while under one, each addition approved and
// none refused. What the baseline buys is that growth is visible in review at the moment it
// happens and that the number is available to argue with — not that the number cannot rise. A
// ceiling the approval flag cannot lift is a separate decision nobody has taken yet.
// `api` is the surface an adopter is meant to reach; everything else here is a candidate to become
// internal at that point.
apiSurface {
label = 'Jpa'
baseline = rootProject.file('../docs/architecture/jpa-api-surface.txt')
description = 'JPA persistence leaf public API surface — every public top-level type in src/main/java.'
rationale = [
'A public type in a single-jar leaf is reachable from every adopter\'s code, so',
'additions are reviewed rather than discovered. `api` is the intended external',
'surface; the rest is implementation that has not been moved under an internal',
'root yet.',
]
}
// The JPA readiness registry describes this platform's lanes and resolves their task paths, so it
// runs with this leaf's `check` rather than with all 62. The task itself is registered by
// the ca.jpa-qualification plugin from build-tools, which the root applies.
tasks.named('check') {
dependsOn rootProject.tasks.named('verifyJpaReadinessRegistry')
}