Capstone — Designing a Real Application's Data Model
🏁 Capstone: Designing a Real Application's Data Model
mongodb1-7), the aggregation framework (Ch.1–2), transactions (Ch.4), and driver code (mongodb1-8) into one coherent application, end to end.
The Application: A Small Order-Processing System
Requirements: customers place orders for products; each order must record the price paid at that moment (not today's price, which may have changed since); placing an order must safely decrement stock and never double-charge or double-decrement; and the business needs a dashboard showing revenue by category and top-selling products.
Step 1: Schema Design (Chapter 3)
Applying Chapter 3's patterns deliberately, not by default:
The computed pattern also applies: each customer document carries a precomputed lifetime_spend, updated incrementally on every order rather than re-aggregated on every profile view — exactly Chapter 3's page-view-counter idea, applied to a running total.
Step 2: Indexes (Chapter 7 / mongodb1-7)
The queries this app actually runs decide the indexes it needs — not guesswork:
Step 3: The Transaction — Placing an Order (Chapter 4)
Placing an order touches three separate documents — a new order, the product's stock, and the customer's precomputed lifetime_spend — which is exactly the multi-document signal Chapter 4 flagged as needing a transaction, not three independent writes.
If the stock decrement fails (say, application logic rejects it because stock is already 0), the whole transaction aborts — the order is never left "placed" against a customer whose spend total was never actually updated.
Step 4: The Aggregation — A Sales Dashboard (Chapters 1 & 2)
The dashboard needs two different views in one call — a job for $facet — starting from a $match that can use the status index created in Step 2, per this course's own indexing-order gotcha (Chapter 1 and revisited in Chapter 7):
This chains together every stage this course covered: $match (Ch.1) using an index, $unwind (Ch.2) to break each order's embedded items array into individual rows, and $facet (Ch.2) running two independent grouped-and-sorted views from that same unwound input in a single round trip.
Step 5: Scaling Considerations (Chapters 5 & 6)
Not needed on day one, but worth designing with in mind: the dashboard aggregation is a read-heavy reporting query, a good candidate for secondaryPreferred read preference (Chapter 5) once the replica set is under real load. If orders ever outgrows a single replica set, customer_id — already indexed, already the natural per-customer query boundary — is the obvious shard key candidate (Chapter 6), hashed to avoid the write hotspot a raw, sequential _id or timestamp key would create.
| Course Concept | Where It's Used in This App |
|---|---|
| Embedding vs. referencing (Ch.3) | Order items embed a price/name snapshot; customers are referenced by ID |
| Computed pattern (Ch.3) | customers.lifetime_spend, updated via $inc on every order |
| Compound indexes (Ch.7) | { customer_id, createdAt } and { status }, matching real queries |
| Transactions (Ch.4) | Placing an order: insert + stock decrement + spend increment, atomically |
| $match / $unwind / $facet (Ch.1–2) | The dashboard's revenue-by-category and top-products aggregation |
| Read preference (Ch.5) | Dashboard reads candidate for secondaryPreferred at scale |
| Shard key choice (Ch.6) | Hashed customer_id, if/when orders needs sharding |
💻 Coding Challenges
Challenge 1: Add a Cancellation Path
Design the transaction for cancelling an order: it must set the order's status to "cancelled", restore the product's stock, and decrement the customer's lifetime_spend — all atomically.
Goal: Practice recognizing that a cancellation is the same multi-document-atomicity problem as placing an order, just reversed.
Challenge 2: Extend the Dashboard Pipeline
Add a third facet to Step 4's aggregation: ordersPerDay, counting completed orders grouped by calendar day.
Goal: Practice adding a third independent sub-pipeline to an existing $facet stage without disturbing the other two.
Challenge 3: Justify the Embedding Choice
A colleague suggests referencing the product instead of embedding a name/price_paid snapshot in each order item, to "keep it DRY." Explain, using this course's material, why that would actually introduce a bug.
Goal: Practice defending a specific schema design decision against a plausible-sounding but incorrect simplification.
Each step above was verified against this course's own earlier examples — but combining them is still worth testing end to end, not assumed to compose correctly. A concrete example: if orders is later sharded on customer_id (Step 5), the Step 3 transaction now spans documents that may live on different shards (an order and a product don't share the shard key) — MongoDB's distributed transactions support this, but with added latency and failure modes single-shard transactions don't have. Design decisions made independently, chapter by chapter, still need to be re-verified together once the whole system is assembled — the same discipline this course's earlier capstone-adjacent chapters (Ch.4's transaction retries, Ch.6's scatter-gather gotcha) each flagged on their own.
🎉 Course Complete
That's the full MongoDB Intermediate/Advanced course — from the aggregation framework through schema design at scale, transactions, replication, sharding, performance and security, and finally a real application combining all of it. Together with MongoDB Fundamentals, this completes both MongoDB courses on the site.