Files
clean-architecture-backend-…/docs/adr/ADR-MONGO-001-platform-boundary.md
T

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:

  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.