145 lines
11 KiB
Markdown
145 lines
11 KiB
Markdown
# MongoDB Document Persistence Platform — Repository Adaptation Contract
|
|
|
|
**Design source:** `docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md`
|
|
**Stable plan:** `docs/superpowers/plans/2026-08-11-mongodb-document-persistence-platform-implementation-plan.md`
|
|
**Advanced plan:** `docs/superpowers/plans/2026-08-11-mongodb-advanced-capabilities-expansion-plan.md`
|
|
|
|
The design package declares its own module root (`modules/mongodb`) and root package
|
|
(`io.backend.skeleton.mongodb`) as *implementation assumptions*, not as contract. This file is the
|
|
single record of how that assumed layout was mapped onto this repository. Only paths, build DSL,
|
|
and composition-root ownership changed. Public contracts, policy order, and error semantics are
|
|
implemented exactly as specified.
|
|
|
|
## 1. Why the module layout differs
|
|
|
|
The design assumes 19 Stable Gradle projects under `modules/mongodb/` and 12 Advanced projects
|
|
under `modules/mongodb-advanced/`. This repository is a Clean Architecture template whose
|
|
**fail-closed registry** (`src/config/architecture/modules.json`, enforced by `src/settings.gradle`
|
|
and `verifyCleanArchitectureDependencies`) declares **exactly 19 leaf identities**. Creating 31 more
|
|
Gradle projects would violate HARD-STOP #5 in `AGENTS.md`.
|
|
|
|
Therefore the design's 31 modules become **package boundaries inside the registered leaf**
|
|
`:adapter:outbound:persistence-mongo`, following the precedent already set by
|
|
[docs/httpclient/repository-adaptation.md](../httpclient/repository-adaptation.md). The design's
|
|
module dependency table (§6.3) is enforced by `MongoModuleBoundaryTest` as a **closed edge matrix**:
|
|
every top-level package is declared with the packages it may import, the matrix is compared against
|
|
the tree for exact equality, and every observed edge must appear in it. A forbidden edge fails the
|
|
build the same way a missing Gradle dependency would, and so does a new package nobody registered.
|
|
|
|
This used to be a stronger claim than the test. The rules forbade a handful of reverse dependencies
|
|
and said nothing about the rest, so four edges outside the design's DAG existed and passed:
|
|
`reactive → imperative`, `reactive → query`, `transaction → reactive` and `geo → imperative`. They
|
|
are declared in the matrix now rather than removed — each is a real coupling the code relies on, and
|
|
the point of recording them is that the next one is a decision instead of an accident.
|
|
|
|
## 2. Package mapping
|
|
|
|
Root package: `io.backend.skeleton.mongodb` → `dev.caskeleton.adapter.outbound.mongo`.
|
|
|
|
### 2.1 Stable modules
|
|
|
|
| Design module | Repository package |
|
|
|---|---|
|
|
| `mongodb-core-api` | `…outbound.mongo.api` (+ `.capability`, `.consistency`, `.error`, `.mapping`, `.observation`, `.profile`, `.schema`) |
|
|
| `mongodb-spring-data` | `…outbound.mongo.mapping` (+ `.type`), `…outbound.mongo.failure` |
|
|
| `mongodb-imperative` | `…outbound.mongo.imperative` (+ `.atomic`, `.bulk`, `.revision`) |
|
|
| `mongodb-reactive` | `…outbound.mongo.reactive` (+ `.cursor`) |
|
|
| `mongodb-query` | `…outbound.mongo.query` (+ `.budget`, `.pagination`) |
|
|
| `mongodb-aggregation` | `…outbound.mongo.aggregation` |
|
|
| `mongodb-transaction` | `…outbound.mongo.transaction` (+ `.retry`, `.session`) |
|
|
| `mongodb-index-schema` | `…outbound.mongo.schema` (+ `.index`, `.manifest`, `.model`, `.ttl`, `.validation`) |
|
|
| `mongodb-change-stream` | `…outbound.mongo.changestream` (+ `.projector`, `.recovery`) |
|
|
| `mongodb-geospatial` | `…outbound.mongo.geo` |
|
|
| `mongodb-migration-core` | `…outbound.mongo.migration` |
|
|
| `mongodb-migration-flamingock` | `…outbound.mongo.migration.flamingock` |
|
|
| `mongodb-observability` | `…outbound.mongo.observation` |
|
|
| `mongodb-security` | `…outbound.mongo.security` (+ `.admin`), `…outbound.mongo.nativecap` |
|
|
| `mongodb-spring-boot-starter` | `…outbound.mongo.autoconfigure` |
|
|
| `mongodb-testkit-core` | `…outbound.mongo.testkit.mapping`, `.compat`, `.performance` (`testkit` source set) |
|
|
| `mongodb-testkit-replicaset` | `…outbound.mongo.testkit.rs` (`testkit` source set) |
|
|
| `mongodb-testkit-failover` | `…outbound.mongo.testkit.failover` (`testkit` source set) |
|
|
| `mongodb-testkit-migration` | `…outbound.mongo.testkit.migration` (`testkit` source set) |
|
|
|
|
`…outbound.mongo.architecture` has no design counterpart: it holds the `@MongoOperation` marker and
|
|
the reusable ArchUnit rule set a fork applies to its own document/repository code.
|
|
|
|
### 2.2 Advanced modules
|
|
|
|
| Design module | Repository package |
|
|
|---|---|
|
|
| `mongodb-sharding` | `…outbound.mongo.advanced.sharding` (+ `.admin` for the D4 shard plane) |
|
|
| `mongodb-timeseries` | `…outbound.mongo.advanced.timeseries` |
|
|
| `mongodb-csfle` | `…outbound.mongo.advanced.encryption.csfle` |
|
|
| `mongodb-queryable-encryption` | `…outbound.mongo.advanced.encryption.qe` |
|
|
| `mongodb-search` | `…outbound.mongo.advanced.search` |
|
|
| `mongodb-vector-search` | `…outbound.mongo.advanced.vector` |
|
|
| `mongodb-tenancy-shared` | `…outbound.mongo.advanced.tenancy.shared` |
|
|
| `mongodb-tenancy-database` | `…outbound.mongo.advanced.tenancy.database` |
|
|
| `mongodb-change-stream-messaging-bridge` | `…outbound.mongo.advanced.bridge` |
|
|
| `mongodb-gridfs-compat` | `…outbound.mongo.advanced.gridfs` |
|
|
| `mongodb-testkit-sharded` | `…outbound.mongo.testkit.sharded` (`testkit` source set) |
|
|
| `mongodb-testkit-atlas` | `…outbound.mongo.testkit.atlas` (`testkit` source set) |
|
|
|
|
The design's rule that a Stable module never depends on an Advanced one survives as an ArchUnit rule
|
|
(`stableNeverDependsOnAdvanced`) plus the opt-in flag: every Advanced entry point requires
|
|
`MongoAdvancedCapabilityFlags` to have the matching capability enabled and refuses construction
|
|
otherwise. Being on the classpath is not being enabled.
|
|
|
|
## 3. Other deliberate substitutions
|
|
|
|
| Design assumption | Repository reality | Adaptation |
|
|
|---|---|---|
|
|
| Gradle Kotlin DSL under `modules/mongodb*` | Groovy DSL, root `build.gradle` conventions, `LockMode.STRICT` locking | Dependencies declared in `src/adapter/outbound/persistence-mongo/build.gradle`; `gradle.lockfile` regenerated. |
|
|
| `mongodb-spring-boot-starter` is a separate module the app depends on | `modules.json` gives `adapter-outbound-persistence-mongo` `runtime_memberships: []` and does **not** list it among `app-bootstrap`'s allowed dependencies | The `autoconfigure` package stays inside the leaf and registers through the leaf's own `META-INF/spring/…AutoConfiguration.imports`. This differs from the httpclient precedent, where the starter moved to `:app-bootstrap`; here the registry forbids that edge. |
|
|
| Spring Boot 4.1 / Spring Data MongoDB 5.1 baseline | Repository baseline is Spring Boot 4.0.8 / Spring Data MongoDB 5.0.x | The platform targets the Spring Data MongoDB **API surface** common to both; no 5.1-only type is referenced. The support matrix records the actual pinned versions. |
|
|
| `MongoRetryScope` lives in `mongodb-transaction` | The `mongodb-spring-data` failure translator must classify retry scope, and it cannot depend on `mongodb-transaction` | `MongoRetryScope` lives in `…api.error` (core-api), which both packages already depend on. Same values, same meaning, one legal position in the DAG. |
|
|
| `mongodb-migration-flamingock` depends on Flamingock | Adding an unvetted external dependency is out of scope for this task, and the design itself requires the public contract not to depend on Flamingock types | The adapter is provider-neutral: it consumes a platform-owned `FlamingockChangeUnitView`. Wiring an actual Flamingock distribution is a one-file change behind that view. |
|
|
| Testkit as its own Gradle module | The design forbids production modules depending on the testkit | A dedicated `testkit` source set whose output is on the test compile/runtime classpaths only. ArchUnit rule `productionNeverDependsOnTestkit` enforces the direction. |
|
|
| Per-task `git commit` | `AGENTS.md`: commit policy is `human-only` | Implementation is delivered unstaged; commits are the human's action. This is the only plan step intentionally not executed, and it is recorded here. |
|
|
| `docs/mongodb/**`, `scripts/verify-mongodb-*.sh` | Repository already owns `docs/` and `scripts/` | Created at the same repository-relative paths. The two gate scripts were later removed (2026-08-15); see §5. |
|
|
|
|
## 4. What is unchanged from the design
|
|
|
|
- D1 / D2 / D3 / D4 exposure planes and the ordered D3 admission sequence (§5).
|
|
- Stable API V1 with `apiStrict=true` on the D1/D2 client generation; D3/D4 on separate generations.
|
|
- `MongoExecutionOutcome`, including both ambiguous outcomes (`WRITE_RESULT_UNKNOWN`,
|
|
`TRANSACTION_COMMIT_UNKNOWN`), and `MongoFailureContext`'s permitted-field list.
|
|
- The complete stable exception hierarchy and the label-before-code classification order.
|
|
- The BSON representation manifest (UUID `STANDARD`, `Decimal128`, UTC instants, alias type metadata)
|
|
and the document-size budget.
|
|
- Update-operator-first writes, and optimistic revision as the precondition for whole-document
|
|
replacement.
|
|
- Transaction body retry and commit retry as separate loops: a new session per body attempt, and
|
|
commit-only retry on unknown commit. The body is never replayed after a commit ambiguity.
|
|
- Registered operation names and manifests for query, aggregation and index; no free-form JSON query
|
|
and no unbounded pipeline.
|
|
- Keyset pagination with an authenticated cursor and a unique tie-breaker requirement.
|
|
- Change stream as an at-least-once projector with resume-token checkpointing and explicit
|
|
history-lost handling.
|
|
- TTL as physical cleanup only, never the sole basis for access denial or business scheduling.
|
|
- Manifest-owned index/validator state with an apply policy that never drops what it does not own.
|
|
- Low-cardinality observation tags, command redaction, and the credential reference indirection.
|
|
- The Stable release gate's evidence categories, and the Advanced promotion gate's requirement for
|
|
actual-topology evidence.
|
|
|
|
## 5. Verification
|
|
|
|
The two release-gate scripts (`scripts/verify-mongodb-platform.sh` and
|
|
`scripts/verify-mongodb-advanced.sh`) were removed on 2026-08-15. They wrapped the Gradle lanes below
|
|
and added two things Gradle does not do on its own: a lane that executed zero tests was reported as a
|
|
failure rather than counted as a pass, and a `promotion.json` recording the commit, server image and
|
|
contract-manifest hash. Neither exists until something replaces it, so a green run of the commands
|
|
below is weaker evidence than the gate was.
|
|
|
|
From `src/`:
|
|
|
|
```bash
|
|
./gradlew :adapter:outbound:persistence-mongo:check --console=plain
|
|
./gradlew verifyCleanArchitectureDependencies --console=plain
|
|
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
|
|
```
|
|
|
|
The container-backed lanes the gate ran behind `MONGODB_DOCKER=1` — `mongoCompatibilityTest`,
|
|
`mongoMigrationTest`, `mongoSecurityIntegrationTest`, `mongoReplicaSetTest`, `mongoFailoverTest`,
|
|
`mongoPerformanceTest` — are now invoked by name or not at all.
|