DEV Community

Tyson Cung
Tyson Cung

Posted on

Event Sourcing Broke My Brain — Then Fixed My Architecture

I spent three years building CRUD apps before someone showed me event sourcing. My first reaction: "This is insane. Why would anyone store every change instead of just the current state?"

My second reaction, about two weeks later: "Oh. This is how everything should work."

Your Database Is Lying to You

A traditional database stores current state. Your user's account balance is $500. That's all you know. How did they get to $500? What happened yesterday? Was there a refund? A double charge? A disputed transaction?

With CRUD, you'd need separate audit tables, changelog triggers, and a prayer that nothing got missed. Most teams skip this entirely and discover the gap during an incident — when a customer says they were charged twice and your database says their balance is correct.

Event sourcing flips this. Instead of storing "balance = $500," you store every event that led there:

AccountCreated { balance: 0 }
DepositMade { amount: 1000 }
WithdrawalMade { amount: 300 }
TransferSent { amount: 200 }
Enter fullscreen mode Exit fullscreen mode

The current balance is derived by replaying these events. You can always recalculate it. You can also answer questions that CRUD databases can't: "Show me every state this account has ever been in."

Banks Figured This Out Centuries Ago

Double-entry bookkeeping — invented in the 15th century — is event sourcing. You never erase a ledger entry. You append corrections. The running total is always derivable from the full history.

When a bank processes a refund, they don't go back and delete the original charge. They add a new entry: "Credit: $50, reason: refund for transaction #4821." The history is complete, auditable, and immutable.

Modern banking systems, payment processors like Stripe, and even Git itself all use this same principle. Git doesn't store your current codebase — it stores every commit, every change, in sequence. Your working directory is a projection of that event history.

The Three Core Pieces

1. Event Store — An append-only log. Events go in, nothing comes out (as in, nothing gets deleted or modified). Each event has a type, a timestamp, and a payload. The store is the source of truth.

2. Aggregates — Domain objects that process commands and emit events. When you tell a shopping cart to "add item," the cart aggregate validates the command and produces an ItemAdded event. The aggregate's current state is rebuilt by replaying its events.

3. Projections — Read-optimized views built from events. Think of them as materialized views that update whenever new events arrive. You might project the same events into a dashboard table, a search index, and an analytics database — each optimized for its specific query pattern.

This naturally leads to CQRS (Command Query Responsibility Segregation): writes go through aggregates and produce events; reads come from projections. The write model and read model are separate, scaled independently, optimized for their specific jobs.

A Concrete Example

Say you're building an e-commerce order system. With CRUD:

UPDATE orders SET status = 'shipped' WHERE id = 42;
Enter fullscreen mode Exit fullscreen mode

The previous status? Gone. When did it change? Hope you have a modified_at column. Who changed it? Better check the application logs.

With event sourcing:

OrderPlaced { orderId: 42, items: [...], total: 89.99 }
PaymentProcessed { orderId: 42, amount: 89.99 }
OrderShipped { orderId: 42, trackingNumber: "1Z999..." }
Enter fullscreen mode Exit fullscreen mode

You know exactly what happened, when, and in what order. Need to add a new feature that tracks time between order and shipment? Replay the events. The data was always there — you just didn't need it before.

When Event Sourcing Is Worth the Complexity

I'm not going to pretend this is free. Event sourcing adds real complexity. You need to handle event versioning (what happens when event schemas change?). You need snapshot strategies for aggregates with thousands of events (replaying 50,000 events to get current state is slow). You need to think about eventual consistency, because projections update asynchronously.

It pays off when:

  • Audit requirements are strict — financial services, healthcare, legal tech. Regulators want to see every state change, not just the current state.
  • Temporal queries matter — "What did this account look like last Tuesday at 3pm?" Event sourcing answers this natively. CRUD requires time-travel infrastructure you probably don't have.
  • Multiple read models serve different consumers — your mobile app needs a different data shape than your admin dashboard. Projections handle this elegantly.
  • Debugging production issues — replay the exact sequence of events that led to a bug. Reproduce it deterministically. Fix it. Done.

It's overkill when:

  • Simple CRUD is genuinely enough — a blog, a TODO app, a settings page. Don't add architectural complexity for its own sake.
  • Your team is small and moving fast — event sourcing has a learning curve. If you're three people shipping an MVP, use Postgres and move on.
  • Event ordering doesn't matter — if your domain doesn't care about the sequence of changes, you're paying the event sourcing tax for nothing.

The Tools

EventStoreDB (now Kurrent) is purpose-built for this pattern — Greg Young designed it specifically around event sourcing. Kafka works as an event store if you configure retention and compaction carefully, though it wasn't designed for it. Axon Framework handles the wiring in Java/Kotlin. Marten does the same for .NET with PostgreSQL as the backing store.

You can also build a minimal event store on top of any database with an append-only table. It won't scale like EventStoreDB, but it'll teach you the pattern without learning a new database.

The Mental Shift

The hardest part isn't the code. It's thinking in events instead of state. Instead of "the order is shipped," think "a shipment event occurred for this order." Instead of "the user changed their email," think "an EmailChanged event was recorded."

Once this clicks, you start seeing events everywhere. Your bank statements are event logs. Your browser history is an event log. Your life is a sequence of events that projects into your current state.

Whether that's enlightening or existentially unsettling is between you and your therapist.


What architecture patterns should I break down next? CQRS deep dive? Saga pattern? Domain-driven design? Drop it in the comments.

Top comments (0)