Chapter 02 · BSON, Documents, Collections, Flexible Schema, and Data Types

Flexible Schema Does Not Mean No Schema: Implicit Contracts and Application Evolution

Treat flexible schema as an explicit evolving application contract, with compatibility windows, version markers, validation, migration evidence, and rollback.

Beginner95–120 minutesSchema-evolution + validator labMongoDB Community Server 8.3.8 · mongosh 2.10.0 · PyMongo 4.17.0Last reviewed: September 2026

Learning outcomes

AtlasMart deploys a new catalog service that adds schemaVersion, changes price representation from a plain number to a structured decimal/currency object, and introduces optional seller metadata. MongoDB allows old and new documents to coexist—but the application, indexes, validators, exports, analytics, and migrations still need a contract. “Flexible schema” shifts where schema is managed; it does not remove schema.

01

Identify implicit schemas created by application code, indexes, validators, encryption, analytics, and operational tooling.

02

Design backward/forward-compatible document evolution using explicit version markers and tolerant readers.

03

Distinguish missing fields, explicit null, defaults, and incompatible type changes during rolling deployments.

04

Use a lightweight MongoDB validator to expose bad writes without turning this chapter into the dedicated validation course chapter.

05

Plan migration, verification, rollback, and observability before changing a high-volume collection shape.

Chapter 02 reproducible baseline

Mandatory server examples use a disposable loopback-only standalone mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim. Driver examples pin pymongo==4.17.0. The lab is intentionally unauthenticated only because it is short-lived and published to 127.0.0.1; Chapter 01 already demonstrated the authenticated reusable lab. Do not expose this container on an untrusted interface. Atlas is optional and not required.

Generation-time execution note

The build environment does not provide Docker, mongod, or mongosh, and cannot install PyMongo from the network. Product/driver commands were reviewed against current official documentation but were not executed here. Expected-output blocks describe stable evidence shape, not fabricated captured output. Learners should record their actual versions and outputs.

1. A schema exists wherever code assumes a shape

If AtlasMart code executes doc["price"]["amount"], it assumes price exists, is an object, and amount has a compatible numeric type. That is a schema even if the database has no validator. Compound indexes assume fields/types/cardinality; encryption rules assume paths; BI pipelines assume columns; change-stream consumers assume event/document shape; backup/restore tests assume compatibility with deployed binaries and code.

Flexible documents are valuable because different product categories and migration states can coexist. They are risky when teams confuse “the server accepts it” with “every reader understands it.” The correct goal is controlled heterogeneity: know which variants are allowed, for how long, and how each writer/reader behaves.

Contract source Example assumption Failure if ignored
Application model price.amount is Decimal128 Type error or silent numeric coercion
Index sku exists and is selective Unexpected query behavior/cost
Validator schemaVersion and required fields Write rejected/warned depending action
Analytics/export one price representation Nulls, parsing failures, incorrect totals
Event consumer field renamed atomically everywhere Older consumer breaks during rolling deploy

2. Evolve with explicit versions and compatibility windows

Suppose version 1 stored price: 18.5. Version 2 stores price:{amount:Decimal128("18.50"),currency:"USD"}. A rolling deployment means old and new code can run simultaneously. A safe migration can use an explicit schemaVersion, a reader that understands both versions, a writer that emits only the new version after all readers are compatible, a backfill with idempotent filters, verification, and finally removal of legacy handling.

Do not write two authoritative fields indefinitely (“price” and “newPrice”) without ownership rules. Dual representation creates divergence risk. Define which field is source-of-truth during the migration and when the old representation becomes forbidden.

document contract · tolerant reader during migration
// v1 legacy product{ _id:"sku-mug-blue", schemaVersion:1, price:18.5 }// v2 product{  _id:"sku-mug-blue",  schemaVersion:2,  price:{ amount:Decimal128("18.50"), currency:"USD" }}// Reader concept (pseudocode)function readMoney(doc) {  if (doc.schemaVersion === 2) return doc.price;  if (doc.schemaVersion === 1) return {amount: decimalFromLegacy(doc.price), currency:"USD"};  throw new Error("unsupported schemaVersion");}

3. Null, missing, and defaults must be migration decisions

Adding seller.displayName does not automatically populate old documents. A missing field may mean “created before the feature,” while explicit null may mean “known to have no value,” “redacted,” or “not yet supplied.” If the application collapses both into an empty string, it loses useful state. A default applied only in application code can also create a split between persisted state and displayed state.

Write down the semantics. If a field becomes required, decide whether to backfill old documents first, use a compatibility period, or let the reader compute a default. Do not introduce a strict validator before existing data and rolling writers satisfy it.

4. Use validation as one contract layer—not the only one

Chapter 07 teaches validation deeply. Here we use a minimal $jsonSchema validator only to make schema evolution observable. MongoDB's JSON Schema support is based on draft 4 with MongoDB-specific differences and bsonType awareness. The validator can reject or warn on writes depending on configuration, but it cannot express every application invariant or guarantee every external consumer is compatible.

shell · start disposable schema-evolution lab
docker rm -f atlasmart-mongo-ch02-l4 2>/dev/null || truedocker run --name atlasmart-mongo-ch02-l4 -p 127.0.0.1:27025:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
mongosh · coexistence plus validator failure
mongosh "mongodb://127.0.0.1:27025/atlasmart?directConnection=true" --quiet --eval 'db.flex_products.drop();db.createCollection("flex_products", {validator:{$jsonSchema:{  bsonType:"object",  required:["schemaVersion","sku","price"],  properties:{    schemaVersion:{enum:[1,2]},    sku:{bsonType:"string"},    price:{oneOf:[      {bsonType:["double","int","long","decimal"]},      {bsonType:"object", required:["amount","currency"], properties:{        amount:{bsonType:"decimal"}, currency:{bsonType:"string"}      }}    ]}  }}}});db.flex_products.insertMany([ {schemaVersion:1,sku:"legacy-1",price:18.5}, {schemaVersion:2,sku:"modern-1",price:{amount:Decimal128("18.50"),currency:"USD"}}]);try { db.flex_products.insertOne({schemaVersion:2,sku:"bad-1",price:"18.50"}); }catch(e){ print(e.codeName || e.code, e.message); }printjson(db.runCommand({listCollections:1,filter:{name:"flex_products"}}).cursor.firstBatch[0].options);' 

The two allowed representations demonstrate controlled heterogeneity. The string-price write should fail validation. The collection metadata reveals the validator that actually governs new writes. This does not prove all existing documents satisfy the validator under every validationLevel/validationAction; those details are Chapter 07.

5. Deliberately wrong evolution: deploy the writer first

The most common schema incident is not MongoDB “accepting bad data”; it is a deployment sequence that creates data some still-running reader cannot understand. If a new writer changes price from a scalar to an object before all readers accept the object shape, old services can crash or miscompute totals. The reverse can also happen during rollback if new-only data has already been persisted.

The safer sequence is expand → migrate → contract: first make readers tolerant, then emit/backfill the new shape, observe version distributions and errors, then remove legacy support only after verification. Rollback planning must include data compatibility, not only reverting application binaries.

Production metrics should include counts by schemaVersion, validator failures/warnings, missing/incorrect types for critical fields, migration throughput/errors, and reader decode failures. A flexible schema is operationally mature when you can state which shapes exist and why.

The next lesson closes the chapter with mongosh techniques to inspect documents and collection metadata directly instead of relying on application assumptions.

Cleanup

shell · remove disposable Lesson 4 server
docker rm -f atlasmart-mongo-ch02-l4

Verification checklist

  • The allowed schema versions and ownership of each representation are documented.
  • Readers tolerate both old and new shapes before new writers are enabled.
  • The validator rejects the intentionally wrong string-price document.
  • Collection metadata confirms the validator actually installed on the server.
  • Migration observability and rollback include persisted-data compatibility, not only application binaries.

Check your understanding

  1. Why does MongoDB flexible schema not mean “no schema”?
  2. What is the purpose of schemaVersion?
  3. Why should readers usually be upgraded before writers during a shape change?
  4. Is a validator enough to guarantee application compatibility?
  5. Why are null and missing important during migrations?
Review the answers

Applications, indexes, validators, encryption definitions, analytics pipelines, and consumers all depend on document fields/types. The server merely permits more heterogeneity unless you constrain it.

It makes the document contract explicit so readers, writers, migrations, and observability can distinguish allowed historical/current shapes.

During a rolling deployment, old readers may still be running. Tolerant readers first create a compatibility window in which new data can be introduced safely.

No. It can constrain server writes, but it cannot prove every reader, external API, analytics job, or event consumer understands the allowed shapes.

They can represent different domain/migration states. Collapsing them can erase whether a value is explicitly absent versus never written by older data.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.