DEV Community

Tyson Cung
Tyson Cung

Posted on

Event Sourcing From Scratch: Why Your Database Has Been Lying to You

Traditional databases lie by omission. They show you the current state of your data — but not how it got there. You see that a user's balance is $500. You don't see that they deposited $1,000, bought a $400 item, got a $200 refund, then paid a $300 subscription. The history is gone, overwritten, reduced to a single number.

Event sourcing fixes this by storing every state change as an immutable event. It's the difference between a bank balance and a bank ledger. I've worked with event-sourced systems in production, and they solve problems that traditional CRUD architectures literally cannot — but they also introduce complexity you need to be honest about.

The Bank Ledger Analogy

Banks figured this out centuries ago. They don't store your balance by overwriting a number. They store every transaction as a separate line item:

+$1,000 (deposit)
-$400   (purchase)
+$200   (refund)
-$300   (subscription)
= $500  (derived balance)
Enter fullscreen mode Exit fullscreen mode

Your balance is derived from replaying the history. That's event sourcing. Every state change becomes an immutable fact. The current state is just a projection — a materialized view built by replaying events.

Who Actually Uses This?

Event sourcing isn't theoretical. Major production systems run on it:

  • Financial systems — every transaction is an event, audit trails are built-in
  • E-commerce platforms — order lifecycle from placement through fulfillment
  • Event ticketing (Ticketmaster, Eventbrite) — seat holds, releases, purchases
  • Version control (Git) — every commit is an event, the working directory is a projection
  • Kafka-based architectures — Kafka's append-only log is fundamentally an event store

LinkedIn processes over 7 trillion messages per day through Kafka. That's event sourcing at planet scale.

The Core Architecture

An event-sourced system has a few key components:

The Event Store — an append-only log where events are written. You never update or delete events. Each event has a type, timestamp, aggregate ID, and payload. EventStoreDB, Apache Kafka, and even PostgreSQL with an append-only table can serve as event stores.

Events — immutable facts about what happened. Not "set balance to 500" but "deposited 1000," "purchased item for 400," etc. Events are past tense because they've already happened. You can't argue with facts.

Aggregates — domain objects that process commands and emit events. An Order aggregate might accept a "PlaceOrder" command and emit an "OrderPlaced" event.

Projections — read models built by replaying events. You might project the same events into a "current balances" table, a "transaction history" view, and a "monthly spending report." Different shapes from the same data.

CQRS: The Natural Companion

Event sourcing naturally separates writes (appending events) from reads (querying projections). This is Command Query Responsibility Segregation — CQRS. Your write model is the event store. Your read models are projections optimized for specific queries.

This separation means you can scale reads and writes independently. Your event store handles high write throughput. Your projections can be denormalized, cached, and replicated for fast reads. Different projections can use different databases — one in PostgreSQL for relational queries, another in Elasticsearch for full-text search.

Snapshots: The Performance Escape Hatch

Replaying 10 million events to rebuild state is slow. Snapshots solve this: periodically save the current state, then only replay events after the snapshot. An aggregate with a snapshot at event 9,990,000 only needs to replay the last 10,000 events instead of all 10 million.

When You Should Use Event Sourcing

It's a good fit when:

  • Audit trails matter — finance, healthcare, compliance
  • You need temporal queries — "what was the state at 3pm Tuesday?"
  • Multiple read models from the same data
  • Event-driven architecture is already your pattern
  • Debugging production — you can replay exactly what happened

When You Shouldn't

Be honest with yourself about the downsides:

  • Complexity is real. CRUD is simpler. If your domain is straightforward, event sourcing is over-engineering.
  • Eventual consistency. Projections lag behind events. If your users need instant read-after-write consistency, you need careful design.
  • Schema evolution is painful. Events are immutable, but your event schema will change. Upcasting old events to new formats is tedious work.
  • Storage grows forever. You're keeping every event. Compaction and archival strategies become necessary.
  • Tooling is less mature. Fewer ORMs, fewer tutorials, smaller community than traditional CRUD.

My honest take: most applications don't need event sourcing. But for the ones that do — complex domains with audit requirements, temporal queries, or multiple downstream consumers — nothing else comes close.

Top comments (0)