← ANTI-PATTERNS

CRUD as Domain Language

Persistence verbs masquerading as business events — a ubiquitous naming tier collapse

Principle violated

Domain events name what happened in the real world; CRUD names how storage changed. Conflating them forces interpretation — by people, automations, and models — that the log should not require.

Cognitive biasMissing knowledgePath dependencyShort-termism

CRUD-ism — using create, read, update, delete as domain vocabulary — is one of the most omnipresent anti-patterns in software. It is a specific epistemic tier collapse: persistence and projection vocabulary stands in for domain vocabulary, so capture mode, business meaning, and ground truth collapse into “what the store holds now.” The damage is not local to naming. It propagates information loss through almost every computer system — a root cause of systemic failure, wrong decisions, data-quality decay, and the loss of development scalability when teams cannot evolve software additively.

CustomerCreated, OrderUpdated, AccountDeleted read like domain language. They describe storage operations, not what happened in the real world. The log records that state changed; it does not record whether the customer onboarded, was imported, or converted from a prospect, whether the order was placed or accepted from a quote, or whether the account was offboarded or erased under regulation.

In software systems

The broken structure is naming tier-one facts after projection or ORM lifecycles — not event sourcing itself. Teams adopt append-only logs and DDD vocabulary but keep CRUD-shaped type strings from table design. EntityUpdated events carry full payload diffs without naming the act — address correction, compliance hold, merge from duplicate — so consumers must interpret meaning from before/after comparison or treat every change as equivalent.

Automation depends on the distinction. AddressCorrected is not CustomerRelocated. One fixes a typo on the existing address; the other records that the customer now lives elsewhere. Different downstream processes: no welcome pack, no territory reassignment, no fraud re-score for a typo fix — but perhaps all of those for a move. AddressUpdated cannot express the difference unless every publisher remembers optional reason fields and every consumer branches on them. The event name should carry the semantics; reason fields supplement, they should not rescue a CRUD name.

Generic Created collapses distinct real-world processes into one undifferentiated insert. Integration replay cannot apply different policies to import vs self-service onboarding. Analytics counts “creations” that mix incompatible lifecycles. Human operators and AI agents reasoning over the stream inherit the same ambiguity.

Development scalability fails the same way. When several distinct business acts share one Updated or Created type, the next distinction — onboarding vs import, correction vs relocation — requires changing the shared event contract and redeploying every consumer. Additive, append-only evolution stops: the team edits the monolith verb instead of appending a new fact type. CRUD-ism turns what should be open extension into coordinated schema surgery — the opposite of substrate that grows by new events.

CRUD names also smuggle in update-as-truth: the latest Updated event implies current state is authoritative without supersession or retirement events — feeding mutable history even inside “immutable” stores. Training material and framework scaffolds (UserCreated, ProductDeleted) reproduce the pattern until teams believe it is idiomatic rather than lossy.

The event log is append-only; CRUD belongs at projection storage — read models and current-state tables maintained by projection adapters. Confusing projection convenience for substrate — or letting CRUD names stand in for both — is how CRUD-ism returns through the back door.

In human organisations

Operations ask business questions — who approved the change, why was the account closed, was this a correction or a move — and the event log answers with AccountUpdated. Investigators reconstruct intent from ticket numbers and Slack threads because the system never named the act. Organisational memory inherits the same CRUD compression: “we created the record” instead of “we converted the prospect after contract signature.” Training and handover suffer the same interpretive tax as automation. Decisions get made on aggregates that hide which process actually ran — wrong policy, wrong owner, wrong audit trail.

In socio-technical systems

Product, operations, and engineering share CRUD-shaped language from the first REST tutorial onward. Dashboards count “updates.” OKRs track “creations.” Compliance asks for evidence of what happened; the export lists EntityUpdated with JSON diffs. The organisation learns to treat the database shape as the business story; the software reinforces that habit; data quality initiatives chase symptoms (cleansing, deduplication) without fixing the vocabulary that lost the distinctions in the first place.

Why it persists

Cognitive bias. If it is in the database with a familiar verb, it must be the fact.

Missing knowledge. Curricula teach ORM entities before event storming; REST resources before domain language. Few teams encounter domain events name the real world as a first-class principle.

Path dependency. Early schemas fossilise; renaming event types feels expensive compared to another optional reason field in the payload.

Short-termism. One generic Updated type ships faster than five domain verbs — until every new distinction becomes a breaking change.

Principled alternatives

Commitment boundary

Separate what was committed (domain events, append-only) from derived state (projections, CRUD at the adapter). Pair domain type with capture metadata when epistemic kind matters — captureForce: commit | assert | measure | formalise per Four Capture Modes. Reserve create/update/delete for projection storage adapters and labelled projections — never for tier-one event naming.

Name real-world verbs in event type: CustomerOnboarded, ProspectConvertedToCustomer, CustomerImported, AddressCorrected, CustomerRelocated, ShipmentDispatched, DecisionSuperseded. New distinctions become new event types — additive substrate — instead of breaking changes to a shared CRUD verb. Principle: Domain events name the real world. Full essay: Domain Events Are Not CRUD.