Architecture Tradeoff Explorer

Every comparison answers the same five questions — use when, avoid when, complexity, operational cost, failure modes — so the decision is about your constraints, not the fashion of the year.

Store every change as an immutable event and derive the current state by replay, or store the current state and overwrite it. Event sourcing gives a perfect audit log and temporal queries; state storage gives simplicity and a query you can run today.

Event sourcing
Event Sourcing
State storage
Database · Transactions and ACID
Use whenThe history is the product — ledgers, audit-critical domains, workflows where "how did it get here" must be answered exactly — and you can afford projections.The current state is what matters, the domain is CRUD-shaped, and an audit table or change log covers the compliance need. The default.
Avoid whenEvents would be EntityUpdated{diff} with no domain meaning, deletion under GDPR is a requirement you cannot meet, or the team has never run projections.Regulators or the domain need a complete, replayable history and overwrites lose facts you will be asked for.
ComplexityHigh: event schemas and versioning (upcasting), snapshots, projections, replay tooling, idempotent handlers, the discipline never to mutate history.Low: a row is updated; the database enforces constraints.
Operational costAn append-only store that grows forever, snapshotting, projection lag, rebuilds that replay millions of events.Standard database operations; audit via triggers or a change-data-capture stream if needed.
Failure modesA replay that no longer works because an old event shape was not upcast; crypto-shredding as the only way to "delete"; an aggregate with 2 million events and no snapshot.Lost history on overwrite, a bug whose effect cannot be reconstructed, an audit table that drifts from the state it claims to describe.
Data flowCommand → validate against replayed state → append event → projections updateRequest → UPDATE row → done
ConsistencyStrong per aggregate (optimistic concurrency on the event version); eventual for projectionsTransactional across whatever the transaction touches
Temporal queries"What was the balance on 3 March" is a replay to that pointOnly if you kept history explicitly
Relationship to CQRSAlmost always paired: the event log is the write side, projections are the read sideIndependent; CQRS-lite works over plain state