Every time you run an UPDATE statement, something disappears. The old value — whatever was there a moment ago — is gone. Most databases are designed to forget. Every UPDATE overwrites what came before, every DELETE removes it entirely, and your application is left with only a snapshot of the present.
We accept this because it's natural. But what if your system needs to answer a different question — not "what is the current state?" but "how did we get here?"
That's the question Event Sourcing answers.
Consider a bank account. In a CRUD model:
You know the balance is $1,100. You don't know whether it was a deposit, a refund, a correction, or a bug. The history is erased.
Now imagine a fraud investigator asks: "Show me every transaction on this account in the last 30 days." With CRUD, you need a separate audit log. And that audit log is always an afterthought — bolted on, inconsistent, and never quite complete.
Instead of storing the current state, you store the sequence of events that produced it:
The current state is derived, not stored. You can always get it by replaying the events. But you can also answer questions CRUD never could:
The event log IS the source of truth. The current state is just a cached calculation.
Financial systems. Banks, payment processors, trading platforms. Regulatory requirements often mandate a complete audit trail. Event Sourcing gives you this for free — it's not a bolt-on, it's the architecture.
Collaborative editing. Google Docs doesn't store the document — it stores every keystroke, every deletion, every cursor movement. The document is the result of replaying all operations.
E-commerce order management. An order isn't just "shipped." It was placed, confirmed, payment authorized, picked, packed, shipped, and maybe partially returned. Each step matters for customer support, analytics, and dispute resolution.
Debugging production issues. When something goes wrong, you can replay events up to the point of failure. It's like having a DVR for your system state.
Events are append-only. They accumulate forever. A high-traffic system generates millions of events per day. You need a strategy:
If the event store is the source of truth, read models need to be built from events. There's a delay between writing an event and the read model being updated. Your UI might show stale data for milliseconds or seconds.
Write: append event → event store
Read: event store → projection → read database → UI
The projection step introduces delay.
Events are immutable. You can't go back and change event v1 to event v2. You need versioning and upcasting strategies:
Want to query "all users who deposited more than $1,000 last month"? In CRUD, it's a SQL query. In Event Sourcing, you need a projection — a read model built specifically for that query pattern. You'll likely maintain multiple projections for different use cases.
Event Sourcing almost always pairs with CQRS (Command Query Responsibility Segregation):
Commands → Validate → Append Event → Event Store
↓
Projections
↓
Read Model (SQL, Elastic, etc.)
↓
Queries
The write model and read model are different data structures, optimized for their specific purposes. The event store bridges them.
Use Event Sourcing when:
Don't use Event Sourcing when:
Event Sourcing is powerful but demanding. It solves problems that CRUD literally cannot solve — complete audit trails, temporal queries, and replay-based debugging. But it adds complexity that most applications don't need.
If you're building a banking system and you're NOT using Event Sourcing, you're probably bolting on audit logs and praying they're consistent. If you're building a blog and you ARE using Event Sourcing, you're building a jet engine to power a bicycle.
Know your domain. Pick accordingly.
Store the journey, not just the destination — but only if the journey matters.
— blanho
Redundant pipelines, intelligent segment selection, and a custom storage layer — inside Netflix's Live Origin architecture.
Logging, auth, retries, rate limiting — the stuff nobody designs upfront but everyone suffers from later.
Every architecture solves one problem and creates three new ones. Here's what nobody tells you before you commit.
-- User deposits $100
UPDATE accounts SET balance = 1100 WHERE id = 42;
-- What was the balance before? When did it change? Why?
-- Gone. All of it.// Instead of UPDATE balance = 1100
const events = [
{ type: "AccountOpened", amount: 0, timestamp: "2026-01-01" },
{ type: "MoneyDeposited", amount: 500, timestamp: "2026-01-15" },
{ type: "MoneyDeposited", amount: 300, timestamp: "2026-02-01" },
{ type: "MoneyWithdrawn", amount: 200, timestamp: "2026-02-10" },
{ type: "MoneyDeposited", amount: 500, timestamp: "2026-03-01" },
];
// Current balance = replay all events
// 0 + 500 + 300 - 200 + 500 = 1100// Event v1 (2024)
{ type: "UserRegistered", name: "Alice" }
// Event v2 (2025) - added email
{ type: "UserRegistered", name: "Alice", email: "alice@example.com" }
// Upcaster: when reading v1 events, transform them to v2
function upcast(event) {
if (event.version === 1) {
return { ...event, email: null, version: 2 };
}
return event;
}