Architecture
What is event sourcing? A clear explanation with a bank account example
Event sourcing stores every change as an immutable event and derives current state by replaying them. How it works, where it fits, and where it hurts.
By Raktim Ranjit · Published · 3 min read
Short answer: event sourcing is a way of storing data where you keep a log of every change that happened, as immutable events, instead of overwriting the current state. To know the present state, you replay the events. A bank statement is the everyday analogy: the balance is derived from the list of deposits and withdrawals, not stored as a number that gets edited.
How does it differ from a normal database?
In a typical design, a row holds the current value. When the customer's address changes, you run an UPDATE and the old address is gone unless you built an audit table. In event sourcing, you append AddressChanged { customer: 7, new: ... } and never edit or delete earlier events. The current address is the result of folding all events for that customer.
What does it look like in code?
type Event =
| { type: "Opened"; accountId: string }
| { type: "Deposited"; accountId: string; amount: number }
| { type: "Withdrew"; accountId: string; amount: number };
type Account = { balance: number; open: boolean };
function apply(state: Account, e: Event): Account {
switch (e.type) {
case "Opened": return { balance: 0, open: true };
case "Deposited": return { ...state, balance: state.balance + e.amount };
case "Withdrew": return { ...state, balance: state.balance - e.amount };
}
}
const balance = events.reduce(apply, { balance: 0, open: false }).balance;Business rules run before an event is written. A withdrawal command checks the current balance, and if there is enough, appends Withdrew. If not, it appends nothing.
What are the benefits?
- A complete audit trail by construction. Nothing is overwritten, so you can answer what happened and when.
- You can rebuild state at any time, including as of last Tuesday.
- Multiple read models. You can derive a table for the dashboard, another for search and another for reports, from the same events.
- Debugging by replay. Reproduce a bug by re-running its events.
- Natural fit for domains already based on a ledger, like accounting and inventory.
What are the costs?
- Complexity. Everyone must learn the model. Simple CRUD becomes a lot of ceremony.
- Event schema evolution. Events live forever. Changing an event's shape needs versioning and upcasting code.
- Eventual consistency if read models update asynchronously. A user may not see their own change instantly unless you design for it.
- Queries are harder without a projection. You rarely query the event log directly.
- Privacy rules. If someone has a right to erasure, an append-only log is awkward. Common answers are encrypting personal data per subject and deleting the key, or keeping personal data out of events.
How is it related to CQRS?
Command Query Responsibility Segregation splits writes (commands that produce events) from reads (queries against projections). They are often mentioned together because event sourcing makes separate read models natural. You can use either without the other.
Do you need a special database?
No. A PostgreSQL table with stream_id, version, type, data and created_at, plus a unique constraint on (stream_id, version) for optimistic concurrency, gets you far. Dedicated event stores exist and are worth looking at for high volume.
When is it a good idea?
- The history is part of the business: money, stock, legal records, compliance.
- You need to explain how a number came to be.
- Several different views of the same facts are needed.
When is it not? Settings pages, user profiles, content management, and most admin screens. Use a normal table.
Is there a middle path?
Yes, and it is common. Keep an append-only ledger for the part of the domain that is a ledger, and a plain table for everything else. Rechvix works this way for stock: movements are append-only and a balance table is a projection, as described in stock balances belong in application code. You get the audit trail and replay where it matters without turning the whole system into events.
References
Author
Raktim Ranjit is a software engineer and the founder of NodeDR Infotech. He builds and maintains the software described here.