64 lines
3.6 KiB
Markdown
64 lines
3.6 KiB
Markdown
# 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:
|
|
|
|
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).
|