Implements the mongodb-superpowers-package design: Stable Tasks 1-50 and Advanced Tasks 1-15. The design assumes 19 Stable + 12 Advanced Gradle projects under modules/mongodb*. This repository's fail-closed registry declares exactly 19 leaf identities, so those modules become package boundaries inside the registered leaf :adapter:outbound:persistence-mongo, with the design's module dependency table enforced by ten ArchUnit rules. The mapping and every deviation are recorded in docs/mongodb/repository-adaptation.md. Contract highlights, all enforced by tests rather than convention: - Transaction body retry and commit retry are separate loops. A new session per body attempt; commit-only retry on an unknown commit. The body is never replayed after a commit ambiguity, so a failover cannot become a duplicate. - MongoExecutionOutcome keeps both ambiguous outcomes distinct from success and failure, and MongoFailureContext records only the design-permitted fields. - Failure classification reads server error labels before numeric codes. - BSON representations come from a pinned manifest, never a library default, and a golden type-signature gate fails on any drift. - Index and validator changes go through the manifest and the admin plane; metadata ownership gates every drop. - Every Advanced capability refuses construction unless its flag is enabled. Verified against real servers, not only unit tests. Running the lanes for the first time exposed four defects that a green `check` had hidden: - Four release lanes passed while executing zero tests; the gate now counts executed tests per lane and fails on zero. - The "single replica set" fixture was a standalone, because Testcontainers 2.x needs withReplicaSet(); its test only asserted a connection string. - The three-node fixture was three independent clusters, so no election could occur, and awaitNewPrimary() compared against the post-stop primary. - The migration lease checked modifiedCount, so a same-millisecond refresh read as a lost lease. scripts/verify-mongodb-platform.sh now reports: 9 lanes, 0 skipped, 0 failed, every evidence category produced. scripts/verify-mongodb-advanced.sh reports NOT PROMOTABLE: actual-topology evidence (real sharded cluster, real KMS, real target deployment) is unobtainable here, so it is named rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
3.7 KiB
Markdown
64 lines
3.7 KiB
Markdown
# ADR-MONGO-001 — MongoDB platform boundary
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-08-13
|
|
- **Design source:** `mongodb-superpowers-package/.../2026-08-11-mongodb-document-persistence-platform-design.md` §1, §2 (D-01, D-04, D-05), §5, §6
|
|
|
|
## Context
|
|
|
|
Two failure modes are common when a team wraps MongoDB.
|
|
|
|
The first is flattening: a shared `CommonMongoRepository<T, ID>` and a generic CRUD facade, which
|
|
forces every collection to share an id strategy, a consistency profile and a query surface. MongoDB's
|
|
single-document atomicity, aggregation model and change streams stop being reachable, and the first
|
|
collection that needs something different gets a cast or a leaky generic.
|
|
|
|
The second is unrestricted exposure: the driver and `runCommand` available everywhere. Then any
|
|
service can drop a collection, run an unbounded pipeline, or issue an admin command from a request
|
|
thread, and no review catches it because there is nothing structural to catch.
|
|
|
|
## Decision
|
|
|
|
The domain owns its documents; the platform owns the cross-cutting decisions. Four exposure planes:
|
|
|
|
| Plane | Contents | Client |
|
|
|---|---|---|
|
|
| D1 Standard document persistence | Spring Data repositories, typed queries, mapping manifest, atomic update primitives, optimistic revision | Stable API V1, `apiStrict=true` |
|
|
| D2 Advanced document operations | `MongoTemplate`, transactions/sessions, bulk, aggregation, keyset cursors, change streams | Stable API V1, `apiStrict=true` |
|
|
| D3 Explicit Mongo capability | Native BSON, time series, search/vector, CSFLE/QE, shard-aware operations | Separate capability client |
|
|
| D4 Admin plane | Collection, validator, index, migration, shard, repair | Separate admin client and credential |
|
|
|
|
Specifically:
|
|
|
|
1. **No `CommonMongoRepository<T, ID>`.** Each aggregate declares its own repository.
|
|
2. **D1/D2 run on Stable API V1 with `apiStrict=true`,** so a command outside the versioned API fails
|
|
at development time instead of on the next server upgrade.
|
|
3. **D3 is not a raw-client escape.** Every call passes a fixed admission order: capability registered
|
|
→ database profile → collection allowlist → operation name → timeout → consistency profile →
|
|
result limit → trace → redaction → command category → admin-command refusal → execute.
|
|
4. **D4 is a separate client with a separate credential.** No application-plane path reaches it;
|
|
`PolicyAwareMongoNativeGateway` refuses admin-category commands regardless of capability.
|
|
5. **Advanced and Experimental capabilities are opt-in modules**, never transitive dependencies of the
|
|
Stable surface.
|
|
|
|
## Consequences
|
|
|
|
**Positive.** MongoDB's semantics stay reachable. Misuse is refused structurally rather than reviewed
|
|
for. A server upgrade cannot silently change D1/D2 behaviour. Admin operations have their own audit
|
|
trail and credential.
|
|
|
|
**Negative.** Every operation needs a registered name and profile, so a new query is a small amount of
|
|
configuration rather than zero. A genuinely new capability requires a registration before it can be
|
|
used. Both are deliberate: the cost is paid once per operation, at review time.
|
|
|
|
**Rejected alternative — "expose the driver, rely on code review."** Review does not scale to every
|
|
query in every service, and the operations that matter (unbounded pipeline, `dropCollection`,
|
|
unanchored regex on user input) look unremarkable in a diff.
|
|
|
|
## Repository adaptation
|
|
|
|
The design assumes 19 Gradle modules under `modules/mongodb/`. This repository's fail-closed registry
|
|
declares exactly 19 leaf identities, so the modules became package boundaries inside
|
|
`:adapter:outbound:persistence-mongo`, enforced by ArchUnit. See
|
|
[docs/mongodb/repository-adaptation.md](../mongodb/repository-adaptation.md).
|