Event-Driven Architecture, CQRS and Event Sourcing
Three related patterns that are constantly conflated. What each actually gives you, what each costs, and why event sourcing is far more expensive than it looks.
Covers: Events vs commands, event-carried state transfer, CQRS read models, event sourcing, projections, replay and versioning
Event-driven architecture, CQRS and event sourcing are three separate decisions. You can do any one without the others, and conflating them is the most common way candidates get into trouble here - usually by proposing event sourcing when all they needed was an event.
30-second answer
A command is an instruction to a specific handler to do something, named imperatively - `ChargePayment` - and it can be rejected. An event is a statement that something already happened, named in the past tense - `PaymentCharged` - and cannot be rejected because it is history. Commands have one intended handler; events have zero or many subscribers. On content, prefer events that carry enough state for common consumers to act without calling back, but include the entity ID and a version so a consumer that needs more can fetch it.
| Command | Event | |
|---|---|---|
| Naming | Imperative: ReserveInventory | Past tense: InventoryReserved |
| Intent | Please do this | This happened |
| Handlers | Exactly one | Zero or many |
| Can be rejected | Yes | No - it is history |
| Coupling | Sender knows the receiver | Publisher does not know subscribers |
| Failure meaning | The action did not happen | The action happened; a consumer failed to react |
Thin events or fat events?
| Thin (notification) | Fat (event-carried state transfer) | |
|---|---|---|
| Payload | { order_id: 4711 } | Full order with items, totals and customer |
| Consumer must call back | Yes | No |
| Coupling | Consumers depend on the producer's API | Consumers depend on the event schema |
| Load on producer | High - every consumer calls back | None |
| Staleness | Fresh at read time | Snapshot at publish time |
| Payload size | Tiny | Can become large |
{
"event_id": "01J8ZK9V0000000000000000", // unique -> consumer deduplication
"event_type": "OrderPaid",
"event_version": 2, // schema version, for evolution
"occurred_at": "2026-07-26T09:14:22.113Z",
"aggregate_id": "order_4711",
"sequence": 17, // per-aggregate order -> detect gaps
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"data": {
"order_id": "order_4711",
"customer_id": "cust_88",
"total_cents": 24900,
"currency": "GBP"
}
}Say these points
- Commands are imperative, singular and rejectable; events are past tense, broadcast and factual.
- Include IDs, timestamp, sequence/version, trace ID and the commonly needed fields.
- A version number lets consumers detect out-of-order delivery.
- Event schemas are public contracts - additive changes and a registry from day one.
Avoid these mistakes
- Naming events imperatively, which turns them into disguised commands.
- Fat events with no version, so consumers cannot order snapshots.
- Assuming loose coupling means the schema can change freely.
- Publishing events before the transaction commits (use the outbox).
Expect these follow-ups
- How do you evolve an event schema that four teams consume?
- When is a synchronous call better than an event?
- How do you debug a flow that spans six event hops?
30-second answer
CQRS separates the write model from the read model so each can be optimised independently - normalised and validated for writes, denormalised and query-shaped for reads, kept in sync asynchronously. It is worth it when read and write workloads differ dramatically in shape or volume. Event sourcing goes further: the immutable event log becomes the source of truth and current state is a projection of it, giving a perfect audit trail and the ability to rebuild any view or answer historical questions. It is worth it in genuinely event-shaped domains - finance, ledgers, order lifecycles - and is usually a mistake elsewhere, because you inherit schema versioning, replay cost, snapshotting and the loss of simple ad-hoc queries.
CQRS with event sourcing
Writes append events; projections build whatever read shapes you need. Every read model is disposable and rebuildable.
Flow
- CommandAggregate
- AggregateEvent store(append)
- Event storeOrder list view
- Event storeSearch index
- Event storeAnalytics
| Gain | Cost | |
|---|---|---|
| CQRS | Read and write models optimised separately; reads scale independently | Two models to maintain; eventual consistency between them |
| Event sourcing | Full audit trail; time travel; rebuild any projection; retrospective analytics | Schema versioning forever; replay cost; snapshots; no ad-hoc SQL over current state |
When each is the right call
| Situation | Recommendation |
|---|---|
| Reads 1000× writes, different shape | CQRS alone - often just a read replica plus a denormalised view |
| Regulatory audit trail required | Event sourcing - this is its strongest case |
| Financial ledger, order lifecycle | Event sourcing - the domain is genuinely event-shaped |
| 'Who changed this and when?' is a core feature | Event sourcing, or a simpler audit-log table |
| Standard CRUD product | Neither - a well-indexed relational schema |
| Team has not used it before, deadline is tight | Neither - the learning cost is real and front-loaded |
Say these points
- CQRS separates read and write models; event sourcing makes the event log the source of truth.
- Projections are disposable and rebuildable - that is the main practical superpower.
- Event sourcing costs schema versioning, replay, snapshots and GDPR complexity.
- It fits genuinely event-shaped domains: ledgers, order lifecycles, regulated audit.
- For most 'we need history' requirements, an audit-log table gets 80% for 10% of the cost.
Avoid these mistakes
- Treating CQRS and event sourcing as one decision.
- Event sourcing a CRUD domain.
- No snapshot strategy, so aggregate loads degrade over time.
- Ignoring that projections are eventually consistent and the UI must reflect that.
- No plan for deleting personal data from an immutable log.
Expect these follow-ups
- How do you handle a bug that wrote incorrect events into an immutable log?
- How do you satisfy a GDPR erasure request under event sourcing?
- How do you keep a projection consistent when the projector itself crashes mid-batch?
Showing 2 of 2 questions for event-driven-architecture-cqrs-and-event-sourcing.
Check your understanding
4 questions · no sign-up, nothing stored
Hands-on challenge
Build it - this is what you talk about in a deep-dive round.
Model an order lifecycle as events
Design the event model for an order that can be placed, paid, partially shipped, partially refunded and cancelled.
Requirements
- List every event with its past-tense name and payload, including the envelope fields.
- Show how current order state is derived by folding the event stream.
- Design two projections with different shapes and show that both are rebuildable.
- Define a snapshot strategy and justify the interval.
- Describe how you would add a new field to an event consumed by three teams.
Stretch goals
- Handle a GDPR erasure request against the immutable log.
- Design the repair process for a bug that emitted incorrect events for a week.