Production Notes #07 · Part of Binary and Beyond. LinkedIn newsletter edition follows.
Most enterprise products begin as a spreadsheet with a login screen.
There are rows. There are columns. Someone needs to create a record, find it later, change a field, and occasionally delete it. The first architecture meeting lasts twenty minutes. Someone draws four boxes on a whiteboard: Create, Read, Update, Delete.
CRUD.
It feels like a finished design. Controllers map to verbs. Tables map to nouns. Screens map to forms. The database is empty and fast. Staging looks calm. The demo wins the room.
Then the product meets a real workflow.
And CRUD, which never asked for a database upgrade, quietly stops being enough.
The day "update the order" stopped meaning anything
Picture a mid-market commerce team that built an internal order console the usual way.
Orders live in a table.
Status is a string column: draft, paid, fulfilment, shipped, cancelled.
The UI offers edit. Support can change the status. Finance can change the amount. Ops can change the warehouse code. Everyone has an update endpoint. Everyone feels productive.
On a busy Tuesday, three things happen in the same hour.
A payment webhook marks the order paid.
A support agent, looking at an older screen, sets it back to draft because the customer asked to add a line item.
A fulfilment job, which already reserved stock against the paid version, ships two SKUs and writes shipped.
Nobody "broke the database."
Postgres was fine. Indexes were fine. Connection pools were fine.
What broke was the fiction that an order is a row you update.
An order is a set of irreversible commitments: money authorised, inventory promised, a customer told something true. CRUD treats those commitments as fields. Production treats them as events that cannot be politely overwritten.
Production Notes #01 argued that divergent state is harder than scale. CRUD is one of the fastest ways to manufacture divergent state inside a single application: every "update" is a permission to rewrite history without asking whether history was already acted on.
CRUD is a storage shape. Products are workflows.
Create, read, update, and delete describe how bytes move in a table.
They do not describe:
- who is allowed to change what after a side effect has fired
- what must remain true after a partial failure
- which writes are safe to retry (Production Notes #05, Production Notes #06)
- how two departments can disagree about the same noun without corrupting the other
Those questions show up the moment a record is more than a form.
A customer is not a row you update when marketing wants a new segment field and billing wants a tax residency field and support wants a preferred language. Those are different authorities colliding on one noun.
An invoice is not a row you delete when finance has already reported it. Deletion is a legal and accounting event wearing a HTTP verb.
A subscription is not a status string. It is a timeline of renewals, pauses, dunning attempts, and entitlements that other systems already consumed.
CRUD stays useful for the boring nouns: tags, feature flags admin lists, content drafts nobody has promised to a customer yet. It fails when the noun is also a promise.
Why teams blame the database first
When the console gets slow or weird, the meeting usually opens with infrastructure.
"We need read replicas."
"We should shard."
"Maybe move the hot table to a document store."
Sometimes those moves are correct.
Often they are a category error.
The product is not waiting on disk. The product is waiting on meaning. Every new screen adds another writer to the same row. Every integration adds another reader that assumed yesterday's shape. Every "quick status field" becomes a public API for people who never attended the design review.
The database did not stop scaling.
The CRUD model stopped explaining the business.
You can throw hardware at a table that is being used as a shared scratchpad for five workflows. You cannot buy your way out of undefined transitions. That is the same lesson as partial failure: green infrastructure does not equal an intact business promise.
I have watched teams spend a quarter on database tuning while the real incident pattern was three admin screens and two webhooks writing the same status column with different meanings of "done." The query plan was innocent. The product vocabulary was not.
What replaces "just update it"
Mature systems stop asking "how do we edit this record?" and start asking "what happened, and what is allowed next?"
That usually looks like:
Explicit states, not free-text status. A state machine with named transitions beats a dropdown that accepts anything a form can POST.
Commands, not generic updates. CapturePayment, ReleaseInventory, IssueCredit are different operations with different preconditions. A blanket PATCH /orders/:id is how support accidentally undoes fulfilment.
Append-only history for commitments. Money, inventory allocations, and customer-visible messages need a log of what was true when someone acted. Overwriting a column erases the evidence you will need in the war room.
Bounded ownership. Which team is allowed to change which fields after which transition? If the answer is "whoever has the edit button," you do not have a product model. You have a wiki with SQL underneath.
Idempotent write paths. Once workflows span webhooks and humans, "twice" is normal. CRUD update endpoints are rarely designed for that. Idempotency keys and transition guards are.
None of this requires abandoning SQL.
It requires abandoning the idea that four verbs are a domain model.
When we rebuild platforms that outgrew their first admin console, the database is usually salvageable. The expensive rewrite is the product vocabulary: which actions exist, which are forbidden, and who is allowed to run them after the first side effect has already fired.
CRUD in AI-shaped products
AI features make this sharper, not softer.
An agent that can "update the CRM" is a CRUD client with confidence. If your CRM record is a free-edit bag of fields, the agent will invent transitions no human product manager approved. If your domain is expressed as commands with preconditions, the agent has fewer ways to quietly rewrite history.
The control layer is not the model. It is whether your system still thinks the world is a form.
Questions to ask before the next "simple CRUD" feature
I use this checklist when a brief says "just add edit for X" or "expose a REST resource for Y."
- Is this noun a form, or a promise? If money, inventory, entitlement, or a customer-visible claim is involved, CRUD is a storage detail, not the product model.
- What transitions are illegal after a side effect? Name them. If you cannot, the edit button will invent them in production.
- Who owns each field after each state? Support, finance, ops, and the integration user are not one writer.
- What must we remember forever even if the UI wants a delete? Audit, tax, and dispute paths hate destructive updates.
- If this write happens twice, what should remain true? If the answer is unclear, you are not ready for webhooks or agents on this path.
- Which screens are really workflows wearing a table? Replace "edit status" with a named action that encodes the workflow.
- Are we scaling the database because the model is wrong? Measure contention and conflicting writers before you buy replicas.
If the answers collapse to "we'll validate in the controller," you still have CRUD with manners. You do not yet have a domain that survives contact with operations.
A different standard for "done"
A resource API is not done when Postman returns 200 for create, read, update, and delete.
It is done when you can explain, for every irreversible commitment the business makes, which command creates it, which states forbid it, and which systems are allowed to act on it afterward.
That standard feels heavy for an admin screen.
It feels obvious the first time three teams overwrite the same order and each has a screenshot proving they were right.
The teams I trust in production are not the ones with the most exotic datastores. They are the ones who noticed that CRUD stopped describing their product months before the database asked for help.
Databases scale further than most teams need.
CRUD assumptions do not.
The bottleneck was never the table. It was pretending a workflow was a form.
Related reading
- State Is Harder Than Scale: divergent meaning, not raw load, is what usually hurts
- Idempotency Is the Unsung Hero of Enterprise Software: write paths that survive "twice"
- Designing Software for Partial Failure: green hops are not an intact promise
- SaaS development: when platforms outgrow form-shaped domain models
Production Notes #07 · Part of Binary and Beyond. LinkedIn newsletter edition follows. Building enterprise platforms that outgrow CRUD without rewriting the business every quarter? Start a conversation.
