# adapter:outbound:persistence-mongo — module rules ## Registered identity - Module ID: `adapter-outbound-persistence-mongo` - Gradle path: `:adapter:outbound:persistence-mongo` - Focused test (derived from Gradle path): `./gradlew :adapter:outbound:persistence-mongo:test --console=plain` - Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0. - Registry SSOT: `src/config/architecture/modules.json`. Package root: `dev.caskeleton.adapter.outbound.mongo`. Driven (outbound) adapter — opt-in MongoDB Document Persistence Platform. Design rationale lives in [README.md](README.md); the mapping from the design package's assumed module layout onto this leaf lives in `docs/mongodb/repository-adaptation.md` and is the file to update when that mapping changes. ## Responsibility - Provide the opt-in Mongo client, template and **platform policy** surface without shipping a fake business domain. Real forks add their own document, repository, mapper, and application/domain port implementation. - It does **not** reimplement idempotency / outbox / lock on Mongo (those stay JPA-only). - Opt-in: `MongoPersistenceConfig` re-imports the Mongo auto-configuration (`@ImportAutoConfiguration`) only when `ca-skeleton.persistence-mongo.enabled=true` (default off). `MongoPlatformAutoConfiguration` is gated on the same flag. The connection URI and database come from Spring's standard `spring.data.mongodb.*` settings; platform profiles come from `ca-skeleton.persistence-mongo.platform.*`. - `MongoOptInAutoConfigurationImportFilter`, registered through `META-INF/spring.factories`, blocks Boot 4's classpath-driven sync/reactive/data/repository/health/metrics Mongo auto-configuration when the module enable flag is absent or false. ## Allowed - No project dependency at all. The registry entry's `allowed_dependencies` is `[]`, matching what the build actually uses; `domain-core`, `application-core` and `shared-contract` were listed and unused, which is a permission granted in advance for an adapter nobody has approved yet. `verifyCleanArchitectureDependencies` only checks that resolved edges are a subset of the declared ones, so an unused permission passes every run; `MongoRegistryPermissionParityTest` checks the other direction and fails when the two sets differ. - The composition root declares `implementation(project(':adapter:outbound:persistence-mongo'))` — with the reactive starter and the reactivestreams driver excluded, because there is no reactive port in the shipped Stable scope. The actual Gradle runtime graph is the runtime SSOT; runtime classpath, so the membership cannot drift from what the jar carries. Property-only activation therefore works here: the switch turns on a module that already ships, and shipping it off is not the same contract as leaving it out, because absence cannot be reversed at deploy time and hides every gating defect. `sample-portfolio` does not carry it. - External: `spring-boot-starter-data-mongodb` and `-reactive`, `spring-boot-autoconfigure`, `micrometer-core`, `slf4j-api`, `spring-boot-configuration-processor` (annotation processor). Versions come from the shared Spring Boot BOM; never pin the driver directly. - Test-only: ArchUnit, reactor-test, Testcontainers (`mongodb`, `toxiproxy`). ## Forbidden - Inbound adapters, sibling outbound adapters, `app-bootstrap`, `sample-portfolio` (ArchUnit `OUTBOUND_ADAPTERS_*` family rules). - Shipping placeholder `Example*` document, repository, record, or adapter types in production. - Adding idempotency/outbox/lock on Mongo without a separately approved contract. - Fully-qualified inline type references; more than one public top-level type per file. ### Package-boundary rules (`MongoModuleBoundaryTest`) These reproduce the design's module dependency table. Breaking one fails the build: - `…mongo.api..` must not import Spring, the MongoDB driver, BSON or Reactor. It is the framework-free core contract; `api/package-info.java` records why. - No Stable package may depend on `…mongo.advanced..`. - No production package may depend on `…mongo.testkit..`. - `imperative` ↛ `reactive`, `query` ↛ `aggregation`, `schema` ↛ execution packages, `observation` ↛ execution packages, `migration` ↛ `migration.flamingock`. ### Consistency-bound templates `MongoConsistencyBinder` derives one `MongoTemplate` per consistency profile. Spring Data exposes setters and no getters for a template's supporting contract, so the parts that must travel — write-concern resolver, write-result checking, lifecycle events, and the `ApplicationContext` that supplies entity callbacks and auditing — are passed explicitly as a `MongoTemplateSupportContract`. Reflection over private fields is forbidden here. A template configured with callbacks registered programmatically outside a context cannot be reproduced by the bound path; that configuration is unsupported rather than silently dropped. ### Public surface Every public top-level type in one jar means `public` is public to every adopter regardless of which package it sits in. `verifyMongoApiSurface` (in `check`) compares the surface against `docs/architecture/mongo-api-surface.txt`, which carries the count; growing it takes `updateMongoApiSurface -PapproveMongoApiSurfaceChange`, which is a review decision. The architecture rule catalogue (`…mongo.architecture`) is in the **testkit** source set, not production: it is ArchUnit input, and shipping it put rule text on every consumer's runtime classpath. Release gating is testkit-only for the same reason and is one implementation, not two: `…mongo.testkit.release` reads the JUnit XML a lane wrote and is what `scripts/verify-mongodb-platform.sh` and `src/config/mongodb/release-contracts.json` drive. A second pair on the production classpath — a hand-built set of category names and a gate that checked it — had no caller outside its own test and no source of truth behind the categories; it is gone rather than moved. Still pending, and deliberately not done as part of a review sweep: moving implementation packages under an `internal` root and lowering visibility inside them. That is a mechanical change over ~200 files and belongs in its own commit, after which the snapshot above is what proves the surface actually shrank. ### Credentials and production profiles A production `MongoProfileProperties` must declare TLS, authentication, strict Stable API, a non-standalone topology and finite positive timeouts; `validate()` enforces all of them. The URI is a `secret://` reference and `MongoCredentialResolver` is the only thing that turns one into a connection string — it hands the value to a caller-supplied function and never returns it, so the credential is a local for one call rather than a field, a bean property or a log line. Not shipped: the resolver implementation (a fork wires its own secret store), and the TLS lane (trusted CA, wrong CA, hostname mismatch, expired certificate) which needs a container with real certificates. Rotation ordering — new generation ready, traffic switch, old lease drain, close — is covered by `MongoClientGenerationConcurrencyTest`. ### Change-stream lifecycle `MongoChangeStreamPipeline` owns ordering: it drives events through `MongoChangeStreamRunner` with `concatMap` and refuses any event behind its cluster-time high-water mark, so a checkpoint write always means "everything up to here is done". Callers must feed it a `Flux` in stream order rather than calling `runOne` themselves. The driver-side source — `watch`, `resumeAfter`/`startAfter`, cursor lifetime and reconnection — is **not** shipped. A fork wires its `MongoChangeStreamSubscription` to the driver and feeds the pipeline; the resume checkpoint it must pass back is `MongoResumeCheckpointStore`'s. Saying this plainly is the point: the policy and value objects here do not add up to a running consumer. ### Platform invariants that are not stylistic - Transaction body retry and commit retry are **separate loops**: a new session per body attempt, and commit-only retry on an unknown commit. The body is never replayed after a commit ambiguity (`MongoTransactionRetryCoordinator`, ADR-MONGO-003). - `MongoExecutionOutcome`'s two ambiguous values must not be collapsed into success or failure. - BSON representations come from `MongoTypeRepresentationManifest`, never from a library default (ADR-MONGO-002). - Index and validator changes go through the manifest and the admin plane; ownership gates every drop (ADR-MONGO-004). - Every concrete Advanced type is annotated `@MongoAdvancedEntryPoint(capability)` or `@MongoAdvancedPolicy`, and an entry point takes a `MongoAdvancedCapabilityGuard` and calls `require` before it exists. `MongoAdvancedRules` enforces both halves; the classification rule is what keeps "we gate the executable ones" from meaning "the ones somebody remembered". - The flags are bound from `ca-skeleton.persistence-mongo.advanced..enabled` by `MongoAdvancedProperties`. `MongoAdvancedConfiguration` is imported by name, never auto-loaded: the Stable lane must reference nothing under `..advanced..`. - `MongoSearchOperations`, `MongoVectorSearchOperations` and `MongoTimeSeriesOperations` are **scaffold** — contracts with no implementation in this repository. A fork supplies the Atlas or time-series deployment and the implementation. - Observation tags are limited to `MongoObservationConvention`'s allowlist. ## Tests `MongoPersistenceConfigTest` proves default/false behavior through an actual `@EnableAutoConfiguration` context, typed enablement binding, and enabled infrastructure with a mock `MongoClient` plus a real `MongoTemplate` without a network connection. It must keep passing — the platform additions are opt-in and must not turn the module on by existing. Hermetic contract tests carry `@Tag("mongodb-contract")` and run in `mongoStableContractTest`, which `check` depends on. Container lanes carry `mongodb-replicaset` / `mongodb-failover` and run only in their own tasks; the default `test` task excludes them, because a lane that needs Docker inside `check` teaches people to skip `check`. ```bash cd src ./gradlew :adapter:outbound:persistence-mongo:check --console=plain ``` Release gates run from the repository root: `scripts/verify-mongodb-platform.sh` (Stable) and `scripts/verify-mongodb-advanced.sh` (Advanced).