3.6 KiB
ADR-MONGO-001 — MongoDB platform boundary
- Status: Accepted
- Date: 2026-08-13
- Design source:
docs/superpowers/specs/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:
- No
CommonMongoRepository<T, ID>. Each aggregate declares its own repository. - 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. - 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.
- D4 is a separate client with a separate credential. No application-plane path reaches it;
PolicyAwareMongoNativeGatewayrefuses admin-category commands regardless of capability. - 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.