Skip to content

Let's Talk Event Sourcing

6 min read
engineeringarchitectureevent-driven architecture

Most databases are amnesiac by design: they remember where you ended up, never how you got there.

Every UPDATE statement overwrites the past, every DELETE erases itokay... most times soft deletes are the norm. So the latest snapshot is not really lost. On every change you end up with a snapshot of the now and zero context for how you got here.

Event sourcing is one approach to solving this — instead of mutating state in place, you record every change as an immutable event. Want the current balance? Replay the transactions. Want to know why an order was cancelled at a determined time? It's right there in the log. The current state is just a left fold over an event logyou walk through every event in order and accumulate them into a single value — like summing a list, but for state, not the truth itself.

In the example bellow you can check a little bit of the idea:

Transaction Timeline
No transactions yet. Use the buttons below.
Amount Received0
Amount Paid0
Balance0
Rejected Count0

With every transaction you can reduce the proper balance. You can even derive the exact balance at any given point in time — replay events up to last Tuesday at 3pm and you have it, no audit table, no "what was the value before this update?". The log is the source of truth. The current state is just a cached convenience: a snapshot of the state.

Each event becomes an audit log entry for "free". The actions and changes are auditable and observable by design, not an afterthought you scramble to implement when compliance comes knocking.

You know who did what, when, and in what order. Debugging a bug report from a customer? Replay their session. Spotted something bizarre in production? Walk the event stream back. The system stops being a black box.

The tradeoffs

None of this is free. Event sourcing has real costs and pretending otherwise is how you end up with an over-engineered mess six months in.

Storage grows forever. You're keeping an append only log of every event, ever. No UPDATE ever shrinks your log. A system that processes thousands of events per second will accumulate millions of records fast — and unlike a regular table, you can't just prune old rows without losing history. The mitigation here is compactionmerging older events into a single condensed record, discarding the individual entries — like squashing git commits and snapshottingperiodically saving the current derived state so future replays can start from that checkpoint instead of event zero.

Reads get expensive. Getting the current state means replaying every event since the last checkpoint. That's fine for ten events, not fine for ten million. The standard answer is a read model (or projection) — a separate, denormalized view of the current state that you maintain by consuming events as they come in. Your writes stay append-only and pure; your reads hit the projection instead of the raw log. Two representations, one source of truth.

Eventual consistency is real. In distributed systems your projection might be some amount of time™ behind the event log. A user submits a form and immediately refreshes — they might not see their own change yet. This isn't a bug, it's the model, but it's the kind of thing that will confuse users and surprise engineers who haven't designed for it. Strategies to mitigate the problem exist: read-your-own-writesafter a write, route that user's next read to a replica that is guaranteed to have caught up — so they see their own change, optimistic UI updatesshow the user their change immediately in the UI before the server confirms it — roll back if it fails, fencing tokensa monotonically increasing number attached to each write — if a stale client tries to write with an old token, the server rejects it. They work. But you have to know you need them.

The pattern holds: the problems are real, the mitigations are known, the tradeoff is complexity. Event sourcing doesn't eliminate hard problems — it trades some for others that are often more tractable.

There is a lot more to cover — projections, CQRS, schema evolution, when to just use a regular table — but that's for another day. For now, the important thing is the mental shift: stop thinking of your database as the state, and start thinking of it as the outcome of a history.

Kind regards,
Leonardo

What's next

These are things we'll get to eventually — but if you're curious, nothing stops you from exploring now:

  • Snapshots and projections — how you avoid replaying 10 million events on every read
  • CQRS — why event sourcing and CQRS are often mentioned together, and why they're not the same thing
  • Event schema evolution — what happens when you rename a field six months later (spoiler: it's painful)
  • Idempotency and replays — side effects, external calls, and why replaying events naively will ruin your day

Related Posts

Hello WorldUnderstand who am I and a tiny bit of what I did
The First Game ChangerOne tiny thing that changed how I wrote software forever