237 lines
13 KiB
Groovy
237 lines
13 KiB
Groovy
plugins {
|
|
id 'ca.spring-library'
|
|
id 'ca.spring-config'
|
|
id 'java-test-fixtures'
|
|
id 'ca.auxiliary-source-set'
|
|
id 'ca.mongo-verification'
|
|
}
|
|
|
|
// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. Applied here rather than
|
|
// from the root, the way the GraphQL leaf does: only a leaf that has shared test code needs it.
|
|
// MongoDB Document Persistence Platform leaf — see
|
|
// docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md (design package)
|
|
// and docs/mongodb/repository-adaptation.md (how the design's 19 stable + 12 advanced library
|
|
// modules map here).
|
|
//
|
|
// The design models the platform as 19 Stable and 12 Advanced Gradle modules under
|
|
// `modules/mongodb` and `modules/mongodb-advanced`. This repository's fail-closed module registry
|
|
// (src/config/architecture/modules.json) outranks that layout, so the module boundaries are
|
|
// packages under dev.caskeleton.adapter.outbound.mongo. MongoModuleBoundaryTest holds a closed
|
|
// edge matrix — every package and what it may import — compares it against the tree for exact
|
|
// equality, and rejects any observed edge that is not declared.
|
|
//
|
|
// Driver and Spring Data MongoDB versions come from the Spring Boot BOM applied to every module in
|
|
// src/build.gradle (design §4: "개별 Driver 버전 override 금지"), so nothing here pins them.
|
|
description = 'Outbound adapter: MongoDB document persistence platform (manifests, atomic writes, ' +
|
|
'consistency profiles, guardrails, change streams)'
|
|
|
|
dependencies {
|
|
// D1/D2 imperative execution path and the mapping subsystem.
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
|
|
// D1/D2 reactive execution path: reactive template, cursors and change streams (design §20).
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
|
|
// The starter package registers auto-configuration and binds typed properties.
|
|
implementation 'org.springframework.boot:spring-boot-autoconfigure'
|
|
// Driver-native observability conventions (design §27) publish through Micrometer.
|
|
implementation 'io.micrometer:micrometer-core'
|
|
implementation 'org.slf4j:slf4j-api'
|
|
|
|
|
|
// The design's module dependency table is enforced as package rules, so ArchUnit is what keeps
|
|
// "packages instead of modules" from meaning "no boundary at all".
|
|
testImplementation libs.archunit.junit5
|
|
testImplementation 'io.projectreactor:reactor-test'
|
|
// Real replica set / failover / migration lanes (design §29). Test-scoped so no production
|
|
// package can reach a container fixture.
|
|
testImplementation 'org.testcontainers:testcontainers'
|
|
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
testImplementation 'org.testcontainers:testcontainers-mongodb'
|
|
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
|
|
}
|
|
|
|
// Shared test code lives in src/testFixtures, per ADR-BUILD-001. This leaf is the first migration
|
|
// off `ca.testkit-publisher`: one leaf, two lanes, no cross-module consumer — the proof that the
|
|
// path works before the published testkits follow.
|
|
//
|
|
// `java-test-fixtures` creates the source set and puts it on `test`'s classpath itself, so neither a
|
|
// `sourceSet('testFixtures')` declaration nor a `testFixturesPublisher { consumedBy 'test' }` is needed.
|
|
// The performance lane is not `test` and still has to say it consumes the fixtures, which is what
|
|
// `compilesAgainst` is for — it looks a source set up rather than creating one, so naming a
|
|
// plugin-created set works unchanged.
|
|
//
|
|
// What is given up: the convention's opt-in publishing. This leaf's fixtures are now a consumable
|
|
// variant whether or not anybody asks for them. ADR-BUILD-001 records that as an accepted loss —
|
|
// unconsumed fixtures are unconsumed, and a leaf that genuinely must not offer them needs a module,
|
|
// not a third convention.
|
|
auxiliarySourceSets {
|
|
sourceSet('mongoPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
|
|
}
|
|
|
|
// The fixtures declare what they compile against, rather than inheriting the test configuration.
|
|
//
|
|
// The `testkit` source set used to extend `testImplementation`, so it silently saw every test
|
|
// library this leaf declared. That is convenient and it is also how a fixture acquires a dependency
|
|
// nobody chose for it. Listing them here is three more lines and makes the fixtures' own surface
|
|
// reviewable — the same model the GraphQL leaf already uses.
|
|
dependencies {
|
|
testFixturesImplementation libs.archunit.junit5
|
|
testFixturesImplementation 'io.projectreactor:reactor-test'
|
|
testFixturesImplementation 'org.assertj:assertj-core'
|
|
testFixturesImplementation 'org.mongodb:bson'
|
|
// The ArchUnit rule pack names Spring Data's Repository marker in a rule, so it needs the
|
|
// type on its compile classpath even though it never calls it.
|
|
testFixturesImplementation 'org.springframework.data:spring-data-commons'
|
|
testFixturesImplementation 'org.testcontainers:testcontainers'
|
|
testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
testFixturesImplementation 'org.testcontainers:testcontainers-mongodb'
|
|
testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy'
|
|
testFixturesImplementation libs.toxiproxy.java
|
|
}
|
|
|
|
// Docker-backed lanes are excluded from the default unit run: they fail closed without Docker, and
|
|
// a `check` that fails on a laptop without Docker teaches people to skip `check`.
|
|
//
|
|
// `mongodb-contract` is excluded here too. It was not, and `check` depends on both `test` and
|
|
// `mongoStableContractTest`, so every one of the 382 hermetic contract tests ran twice on a fresh
|
|
// build — once in each task. The two lanes are now disjoint by construction, and
|
|
// `MongoTestLaneDisjointnessTest` asserts it against the JUnit XML rather than trusting this
|
|
// comment.
|
|
tasks.named('test', Test) {
|
|
useJUnitPlatform {
|
|
excludeTags 'mongodb-contract',
|
|
'mongodb-replicaset',
|
|
'mongodb-failover',
|
|
'mongodb-migration',
|
|
'mongodb-compatibility',
|
|
'mongodb-security-integration'
|
|
}
|
|
}
|
|
|
|
// Six lanes, declared rather than assembled. `ca.strict-test-lane` owns testClassesDirs, classpath,
|
|
// tag selection, failOnNoDiscoveredTests and the up-to-date refusal — the five lines that used to be
|
|
// copied once per lane here and again in four other leaves.
|
|
strictTestLanes {
|
|
lane('mongoReplicaSetTest') {
|
|
integration()
|
|
tag = 'mongodb-replicaset'
|
|
description = 'Single-node replica set contract lane: mapping, atomic write, transaction, ' +
|
|
'change stream (design §29).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
}
|
|
lane('mongoFailoverTest') {
|
|
integration()
|
|
tag = 'mongodb-failover'
|
|
description = 'Three-node replica set failover lane: primary kill, partition, unknown ' +
|
|
'commit, resume (design §29).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
}
|
|
lane('mongoMigrationTest') {
|
|
integration()
|
|
tag = 'mongodb-migration'
|
|
description = 'Migration lane: empty / N-1 / oldest-supported snapshots, lock, checkpoint ' +
|
|
'restart (design §12).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
}
|
|
lane('mongoCompatibilityTest') {
|
|
integration()
|
|
tag = 'mongodb-compatibility'
|
|
description = 'MongoDB 7.0 compatibility and 8.0 primary certification matrix (design §30).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
}
|
|
lane('mongoSecurityIntegrationTest') {
|
|
integration()
|
|
tag = 'mongodb-security-integration'
|
|
description = 'RBAC, TLS, injection and redaction release gate against a real server ' +
|
|
'(design §26).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
}
|
|
|
|
lane('mongoStableContractTest') {
|
|
tag = 'mongodb-contract'
|
|
description = 'Hermetic stable contract suite: manifests, guardrails, retry scopes, ' +
|
|
'redaction (design §30).'
|
|
}
|
|
|
|
// Driven by its own source set rather than a tag: for this shape the source set is the
|
|
// selection, so the convention asks for no tag.
|
|
lane('mongoPerformanceTest') {
|
|
performance()
|
|
sourceSet = 'mongoPerformanceTest'
|
|
description = 'Certifies contention, aggregation spill, pagination and pool resource ' +
|
|
'bounds (design §29).'
|
|
systemProperty 'mongodb.primary.image',
|
|
providers.gradleProperty('mongoPrimaryImage').orElse('mongo:8.0.16').get()
|
|
systemProperty 'mongodb.compatibility.image',
|
|
providers.gradleProperty('mongoCompatibilityImage').orElse('mongo:7.0.28').get()
|
|
systemProperty 'mongodb.toxiproxy.image',
|
|
providers.gradleProperty('mongoToxiproxyImage').orElse('ghcr.io/shopify/toxiproxy:2.12.0').get()
|
|
// Assertions on by default: a performance gate without bounds is only a report.
|
|
systemProperty 'performance.assertions.enabled',
|
|
providers.gradleProperty('performance.assertions.enabled').orElse('true').get()
|
|
}
|
|
}
|
|
|
|
|
|
// `check` gains only the hermetic lanes. The Docker-backed ones stay opt-in for the reason above.
|
|
tasks.named('check') {
|
|
dependsOn 'mongoStableContractTest', 'verifyMongoTestLaneDisjointness',
|
|
'verifyMongoReleaseContractLanes'
|
|
}
|
|
|
|
// The two verification tasks are registered by `ca.mongo-verification`. The leaf owns only
|
|
// the semantic lane selection above and the lifecycle dependency below; JSON/JUnit parsing and
|
|
// evidence decisions live in typed Java build tooling.
|
|
|
|
|
|
// verifyMongoApiSurface — every public type this leaf exposes is a committed decision.
|
|
//
|
|
// 311 of this leaf's 313 production files declare a public top-level type. One jar means `public`
|
|
// is public to every adopter, so the intended split between contract and implementation — `api` is
|
|
// the surface, the rest is how it is built — is a convention the compiler does not know about.
|
|
//
|
|
// The full move of implementation packages under an `internal` root is a separate, mechanical
|
|
// change; this is what keeps the surface from growing while that is pending. A snapshot does not
|
|
// shrink anything. It makes each addition a decision somebody made in review rather than something
|
|
// discovered later by an adopter who imported it.
|
|
//
|
|
// A snapshot does not shrink the surface. It makes each addition visible in review, which is the
|
|
// prerequisite for shrinking it: the `api` and `spi` packages are the surface an adopter is meant
|
|
// to use, and everything else in this file is a candidate for becoming internal when the leaf is
|
|
// split into capability artifacts. Until then the number cannot grow by accident.
|
|
apiSurface {
|
|
label = 'Mongo'
|
|
baseline = rootProject.file('../docs/architecture/mongo-api-surface.txt')
|
|
description = 'MongoDB 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.',
|
|
]
|
|
}
|