Files
DongHyeonkaandClaude Opus 5 d57d2f62a0 feat(mongodb): implement the MongoDB document persistence platform
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>
2026-08-14 13:41:00 +09:00

4.3 KiB

Document Modeling Guide

Design §7–§9. The platform does not own your documents — D-01 is explicit that the domain owns @Document, repositories, queries, index requirements and schema version. What the platform owns is the set of modeling decisions that are expensive to reverse once a collection holds production data.

1. There is no CommonMongoRepository

A generic CommonMongoRepository<T, ID> is listed under explicitly unsupported (§3.4), and the reason is not purity. A shared supertype forces every collection to share an id strategy, a consistency profile and a query surface, and the first collection that needs a different one either gets a cast or a leaky generic parameter. Declare a Spring Data repository per aggregate.

2. Embed or reference

MongoDocumentModelManifest records the decision per collection so it is reviewable, and MongoDocumentModelValidator refuses the combinations that do not survive growth.

Descriptor Use when
EmbeddedCollectionDescriptor The child is read with the parent, is bounded, and has no independent lifecycle. Declare maxElements; an unbounded array is the single most common way a document reaches the size limit.
MongoReferenceDescriptor The child is queried independently, is unbounded, or outlives the parent. Declare MongoReferenceLifecycle so the deletion story is written down rather than discovered.

The validator rejects an embedded collection without a bound, and a reference whose lifecycle says the child is owned by the parent but which is also referenced from elsewhere.

3. Size budget

MongoDocumentSizeBudget:

Constant Bytes Meaning
MONGODB_HARD_LIMIT_BYTES 16 MiB MongoDB's own limit.
PLATFORM_CEILING_BYTES 4 MiB The largest budget the platform will accept.
DEFAULT_BYTES 2 MiB MongoDocumentSizeBudget.standard().

A budget above the ceiling is refused at construction. Budgeting to 16 MiB means the failing write is the first symptom, and by then the collection is already full of near-limit documents.

4. Identity

DomainDocumentId and MongoIdRepresentation fix how a domain identifier becomes _id. Pick the representation once per collection and record it in the manifest:

  • OBJECT_ID — server-generated, monotonic, 12 bytes. Good default when the domain has no natural id.
  • UUID_BINARY — a domain UUID stored as Binary subtype 4 (STANDARD). Never store a UUID as a string "because it is easier to read"; it doubles the index size and loses the type.
  • STRING — a natural key that is genuinely a string (a slug, an external system's id).

An _id choice is effectively permanent: it is the shard key candidate, the resume-token join key and the pagination tie-breaker.

5. Schema version

Every long-lived collection carries DocumentSchemaVersion. MongoSchemaVersionPolicy and MongoSchemaVersionRange say which versions the running code can read; a document outside the range raises MongoDataSchemaUnsupportedException rather than being silently mapped with missing fields.

Write the range down before the migration, not after: the range is what lets old and new instances run at once during a rolling deploy.

6. Type metadata

@LongLivedMongoDocument marks a document whose stored type alias must not be a Java class name. MongoTypeMetadataRegistry maps alias → class. Storing the FQCN means moving or renaming the class becomes a data migration; storing an alias keeps it a refactor. See bson-mapping-guide.md §4.

7. Collection profiles

MongoCollectionProfileRegistry binds a CollectionProfileName to its consistency profile, budget and allowlist. A collection that is not registered cannot be reached through MongoImperativeExecutor or ReactiveMongoExecutor — the allowlist is the mechanism that keeps an unreviewed collection from appearing in production by accident.

8. What to write down before the first insert

  1. Embed/reference decision per child collection, with bounds.
  2. Size budget.
  3. _id representation.
  4. Schema version range.
  5. Index manifest (see schema-index-migration-guide.md).
  6. Consistency profile (see consistency-transaction-guide.md).

Each of these is cheap now and a migration later.