Model It in Mongo
Apologetic question: "When do you need a new wineskin?"
Project 13 — Model It in Mongo
“Neither is new wine put into old wineskins. If it is, the skins burst and the wine is spilled and the skins are destroyed. But new wine is put into fresh wineskins, and so both are preserved.” — Matthew 9:17
Chapter: 13 — Persistence III: MongoDB and the Document Model
Due: End of Week 13
Submit: A link to your code — a public GitHub repo URL with your pymongo code, your relational schema (Medium+), README.txt, and (required) agent-log.txt. Real local toolchain + git per Appendix A; mongod + pymongo + Postgres/SQLite setup per Appendix B.
Allowed tools: Python 3, pymongo, a local MongoDB, your Week 11/12 SQLite or Postgres setup, a real editor, git, the textbook.
AI — Phase 2 (wk 9–16): agentic AI is ON, and an agent-log.txt is REQUIRED. Log every task you delegated to an agent, what it did, where it went wrong, and where you intervened. This project is deliberately shaped so an agent cannot finish it alone — the modeling and the relational-vs-document decision are yours, by design.
The Setup
A growing congregation keeps a study journal — a shared, searchable archive of study entries the members build together. Each entry centers on a word, a passage, or a theme, and that is where the trouble starts: no two entries have the same shape. One entry is a Greek word study with strongs, transliteration, and a list of cross_refs. Another is a sermon-response note with speaker, series, and free-form points. A third is a question someone raised in small group, with nothing but a question and a list of answers that grows over weeks. The fields differ legitimately, the structure nests, and members almost always pull up one whole entry at a time to read it.
The previous volunteer built this on a relational schema and quit in frustration after the eleventh ALTER TABLE and a metadata table nobody could query. The pastor hands it to you with one sentence: “Find the right shape for this data — and tell me why.”
That is the assignment. Model it. Then defend the model.
Learning Targets
By completing this project, you will demonstrate that you can:
- Recognize document-shaped data and model it natively in MongoDB.
- Implement full CRUD against a collection with
pymongo, using filter documents and$-operators correctly. - Write a query that is natural in the document model and awkward in the relational one — and articulate why.
- Model the same data relationally and name, side by side, what each model makes easy and hard, including the denormalization (embed vs reference) tradeoff.
- Make the architect’s relational-vs-document decision from constraints, defend it, and name the conditions that would flip it.
- Direct an agent to scaffold code while reserving the modeling judgment for yourself — and document the seam honestly.
Normal Tier
Goal: Model the study journal in MongoDB and build a working CRUD tool against one collection, plus a query that justifies the document model.
Required features
- A document model for study entries, written down in your README: the fields a
journalcollection’s documents may hold, which are required by your convention (since Mongo won’t enforce it), which are optional, and where you chose to embed vs reference any related data — with a one-line reason for each such choice. - Seed data — a
seed.pythat inserts at least 8 entries into thejournalcollection, of at least 3 genuinely different shapes (e.g. a word study, a sermon note, a small-group question). The shape variation must be real, not cosmetic. - Full CRUD in a
journal.pymodule or CLI:- Create —
insert_onea new entry. - Read —
find_oneby_id(handle theObjectIdcorrectly) andfindby a filter. - Update —
update_onewith$set(and at least one$pushto an array field). - Delete —
delete_one. - Every write must use the correct
$-operator. A bare-dict replacement bug fails this criterion.
- Create —
- The SQL-hating query. One query, run against your collection, that exploits the document model — a filter on a nested field (dot notation) combined with array membership (e.g. “entries tagged
gracewhosemetadata.languageisGreek”). In the README, write out the SQL (with its joins / EAV table) that the relational version would have required, and one sentence on why it’s awkward there. - One client, reused. A single
MongoClientcreated once and shared. No per-operation clients. - Runnable end to end.
python seed.pythen your CRUD demo runs clean against a localmongod, printing observable before/after state for each operation.
Example output
$ python seed.py
Seeded 8 entries into studyjournal.journal (3 distinct shapes).
$ python journal.py demo
[CREATE] inserted _id=664f1a2b3c... title='charis (grace)'
[READ] found by _id: charis (grace) | tags=['grace', 'salvation']
[QUERY] grace + Greek (nested + array):
- charis (grace) metadata.language=Greek
- charisma (gift) metadata.language=Greek
[UPDATE] $push 'Eph 2:9' onto cross_refs of charis -> now 3 cross_refs
[DELETE] removed small-group question _id=664f1a2b40... -> 7 entries remain
Normal-tier rubric (out of 100)
| Criterion | Points |
|---|---|
| Document model written down: fields, required-by-convention, embed-vs-reference reasons | 14 |
| Seed script inserts ≥8 entries of ≥3 genuinely different shapes | 12 |
Create / Read (incl. correct ObjectId handling) | 14 |
Update with $set and at least one $push (no replacement bug) | 14 |
| Delete with a correct filter | 8 |
| The SQL-hating query: nested-field + array-membership, runs and is correct | 14 |
| README writes out the equivalent SQL and explains why it’s awkward | 8 |
Single reused MongoClient; runs clean end to end against local Mongo | 8 |
README + reflection + agent-log.txt present | 8 |
Medium Tier (+up to 25% extra credit)
M1. The same data, relationally
Model the same study-journal data in your Week 11 SQLite or Week 12 Postgres setup. Design the schema honestly — you will discover it takes multiple tables (an entries table, a tags table, a cross_refs table, and some way to hold the arbitrary metadata). Write the CREATE TABLE statements and a small loader that puts the same seed data in. Implement the same CRUD operations and the same grace-and-Greek query in SQL (parameterized — no string concatenation, the Week 11 injection lesson still stands).
M2. The side-by-side
Write comparison.docx: a table or two-column document stating, for this specific data, what the document model makes easy and hard versus what the relational model makes easy and hard. At minimum address each of:
- Adding an entry with a brand-new metadata field nobody anticipated.
- The grace-and-Greek query.
- Guaranteeing every entry has a non-empty
passage. - Renaming a tag everywhere it appears.
M3. The denormalization tradeoff, named
In comparison.docx, take one relationship in your data (e.g. entry→author, or entry→series) and show it modeled embedded and referenced in Mongo. State the tradeoff in concrete terms for your data: how much duplication does embedding cause, what does it save on reads, and what update problem does it create if the embedded data changes? Tie it back to the speed/space tradeoff from Phase 1.
Hard Tier (+up to 25% additional extra credit)
H1. The architect’s memo (the whole point of the arc)
Write architecture-memo.docx — one to two pages — for a concrete application of your choosing with stated, realistic constraints. Pick something real (a sermon archive, a membership system, an event log, a small-group platform) and write down its actual constraints across all five axes from §13.6: query patterns, consistency needs, schema stability, relationships, scale shape.
Then choose relational or document and defend it from those constraints — not from preference. The memo must:
- State the decision in one sentence up front.
- Justify it constraint by constraint (walk the five-axis table for your app).
- Name the conditions under which you would choose the other — the specific change in constraints that would flip your answer. (“I’d switch to document the moment the entries stopped sharing a stable schema and members began fetching whole aggregates exclusively.”)
- If the honest answer is mixed, say which piece of the data goes in which store and why the system spans both.
This is the deliverable an agent cannot write for you. It can format the memo; it cannot make the judgment, because the judgment depends on constraints only you have decided are true for your app. That is the line this project is drawn to expose.
H2. The agent seam, made explicit
In your agent-log.txt, include a dedicated section titled “Scaffold vs Judgment.” List concretely:
- Where the agent scaffolded: which
pymongoboilerplate, which CRUD functions, whichCREATE TABLEstatements you had it write, and how you verified each (you read it — Chapter 1 of Coding 2 — and ran it). - Where the judgment had to be yours: the document model design, the embed-vs-reference calls, the relational-vs-document decision. For each, write one sentence on why the agent could not make this call — what about it required a human deciding what is true about the problem.
A great H2 makes the seam unmistakable: the agent built the modules; you decided which modules exist and why.
Submission
Submit one URL — a public GitHub repo containing:
seed.py,journal.py(or equivalent CRUD module/CLI) — the Mongo code.schema.sql+loader.py(Medium) — the relational model of the same data.comparison.docx(Medium) — the side-by-side and the denormalization tradeoff.architecture-memo.docx(Hard) — the constraint-driven decision.README.txt— your reflection (template below).agent-log.txt— required. Every delegated task, what the agent did, where it erred, where you intervened, and (Hard) the Scaffold-vs-Judgment section.
# Project 13 — Model It in Mongo
**Tier targeted:** Normal / Medium / Hard
**Document model:** (the fields a journal entry may hold; required-by-convention vs optional)
**Embed vs reference:** (each related thing, and why you embedded or referenced it)
**The SQL-hating query:** (the filter, and the SQL + joins it replaces)
**Relational-vs-document decision (Hard):** (one sentence + the constraint that decided it)
**What I learned:** (one paragraph)
**What I'd change:** (one sentence)
**AI usage:** agentic AI ON — see agent-log.txt. Signed: <your name>
Hints (Read Before You Begin)
- Model on paper first. Before any
insert_one, write down what an entry is. The document model’s flexibility is a trap if you skip this — you’ll end up with a collection where every document is a slightly different snowflake and no query works across them. Decide your conventional shape, then let real variation live inside it. $setevery time. Re-read §13.7. The bare-dict update that replaces the whole document is the single most common way to lose points (and data) on this project.ObjectId, not string.from bson import ObjectId. Afind_one({"_id": ObjectId(s)})works;find_one({"_id": s})silently returnsNone. This will eat an hour if you forget it.- Let the agent scaffold, then read what it wrote. Hand the agent “write a
pymongoCRUD module for ajournalcollection with these operations,” then read every line the way Coding 2 Chapter 1 taught you. Hallucinated operators and missing$setare exactly the failure modes you’re auditing for. Log it. - The Medium-tier relational schema is supposed to hurt a little. That ache — the third table, the EAV metadata, the join for the grace-and-Greek query — is the evidence for your memo. Don’t smooth it over; document it.
- Don’t let the agent write the memo. It can format and proofread. The decision and its defense come from constraints you assert about your app. If your memo reads like it could apply to any app, it isn’t done.
What Mastery Looks Like (Beyond the Rubric)
A great Project 13 makes the document model look obvious for the study journal — and then, in the same breath, makes the cost honest. The Mongo code is clean and the SQL-hating query genuinely sings. But the relational schema is also built, faithfully, with its joins multiplying, so the comparison is earned rather than asserted.
A great memo could be handed to a real engineering lead and read as a real recommendation. It names constraints, weighs them, decides, and — the mark of an architect — states the exact condition under which the decision reverses. It does not say “Mongo is more modern” or “SQL is more reliable.” It says “this data, under these constraints, wants this shape, and here is what would change my mind.”
A great agent-log.txt shows a human firmly in the loop. The agent moved fast on the boilerplate. The human caught the missing $set, rejected the agent’s first too-clever schema, and made every call that depended on what the problem actually is. The seam between scaffold and judgment is visible, and on the right side of it sits a person who understood both shapes of data well enough to choose.
Coach’s Note — The trap of this project is the agent. It will cheerfully generate a
pymongomodule and a Mongo schema in seconds, and it will be mostly right, and the whole thing will look done. It is not done. The modeling decision — is this data document-shaped or relational, and why — is the part that matters, and it is the one part the agent cannot do for you, because it requires a human to decide what is true about the constraints. Coding 1 put the skill in your hands; Coding 2 put the judgment in your head; this is where you spend that judgment on a question the machine literally cannot answer.
When You’re Done
- Drop your database and re-run
seed.pyfrom scratch. Confirm a clean, repeatable build. - Run the SQL-hating query against Mongo and the same query against your relational model (Medium). Confirm both return the same answer — and feel the difference in the code that produced it.
- Read your
architecture-memo.docx(Hard) and ask: could this memo apply to any app, or only to the one I described? If any app, it isn’t specific enough. Rewrite. - Read your
agent-log.txtand ask: can a stranger see exactly where the agent stopped and I started? If not, make the seam explicit. - Submit.
- Read Chapter 14. Next week you put a face on all of this — the front end the congregation actually sees.
A theological footnote. New wine into fresh wineskins, and so both are preserved. The image is about fit — and notice that Jesus does not say the old wineskin is bad. It is good, for old wine. It bursts only when you force new, fermenting wine into it. The relational model is not the old, tired thing to be discarded for something newer; the document model is not the fashionable upgrade. Each is a good vessel for its proper contents, and the loss comes from the mismatch — wine spilled, skins destroyed, both lost. The architect’s whole craft, this week, is to match the vessel to what it must hold. Do that, and both are preserved.
See you next week.