The Bank Ledger Analogy
Traditional savings:
- Piggy bank: Only shows current amount
- Where did my money go? No idea!
Bank ledger:
- Every transaction recorded: Deposit 20, Transfer $50
- Full history: Exactly how you got here
- Can recalculate balance from the ledger anytime
Event sourcing is a bank ledger for your app. Store every change as an event.
What Is Event Sourcing?
Traditional: Store current state
User { balance: 80 }
Event Sourcing: Store all events
1. AccountCreated { balance: 0 }
2. MoneyDeposited { amount: 100 }
3. MoneyWithdrawn { amount: 20 }
Current state? Replay events: 0 + 100 - 20 = 80
Key Difference
Traditional (State):
Overwrite with each change
Only know "what is"
History lost
Event Sourcing:
Append each change
Know "what happened"
Full history preserved
How It Works
Writing
User action: "Withdraw $20"
1. Validate business rules
2. Create event: MoneyWithdrawn { amount: 20 }
3. Append to event store (treat existing events as immutable)
4. Update read model (current state cache)
Reading Current State
Option A: Replay all events
Load events → Apply each → Current state
Option B: Use snapshot + recent events
Load snapshot (state at event 100)
Apply events 101-105
Current state
Option C: Query read model
Pre-computed current state
Updated when events are stored
Event Store
Append-only log of events:
┌─────┬────────────────────┬───────────────────────────┐
│ ID │ Type │ Data │
├─────┼────────────────────┼───────────────────────────┤
│ 1 │ AccountCreated │ { id: 123 } │
│ 2 │ MoneyDeposited │ { amount: 100 } │
│ 3 │ MoneyDeposited │ { amount: 50 } │
│ 4 │ MoneyWithdrawn │ { amount: 20 } │
│ 5 │ AccountFrozen │ { reason: "suspicious" } │
└─────┴────────────────────┴───────────────────────────┘
Events are immutable.
Typically treated as append-only.
Avoid changing or deleting historical events.
Why Use Event Sourcing?
1. Complete Audit Trail
Traditional: "Balance is -$500. What happened?"
Event Sourcing: Shows every transaction that led there.
Perfect for:
- Financial systems
- Medical records
- Legal compliance
2. Time Travel
Q: "What was the account state on March 1st?"
A: Replay events up to March 1st.
Debugging: Reproduce exact state at time of bug.
3. Replay and Rebuild
Bug in calculation logic?
1. Fix the logic
2. Replay all events
3. Correct state!
Can't do this with lost state.
4. Event-Driven Architecture
Events can trigger:
- Analytics updates
- Notifications
- Third-party integrations
- Multiple read models
Decoupled systems react to events.
Snapshots
The Problem
Account with 10,000 events.
Replay all 10,000 to get current state?
Too slow!
The Solution
Periodically save snapshots:
Snapshot at event 1000:
{ balance: 5000, frozen: false }
Get current state:
1. Load snapshot (event 1000)
2. Replay events 1001-10000
Much faster!
Snapshot Strategy
Options:
- Every N events
- Every N minutes
- On specific events
- On demand
Projections (Read Models)
Events optimized for writing.
Queries need different structures.
Solution: Projections (read models)
Events → Process → Read Model
Account Events → Balance Projection
Order Events → Order Summary Projection
Activity Events → Dashboard Projection
Multiple Projections
Same events, different views:
Order Events →
→ Customer Order History (by customer)
→ Product Sales Report (by product)
→ Daily Revenue (by date)
Each optimized for its use case.
Event Sourcing + CQRS
Common pairing:
CQRS: Separate read and write models
Event Sourcing: Write model is events
Write side:
Commands → Events → Event Store
Read side:
Events → Projections → Read Database
Queries → Read Database → Response
Scales reads and writes independently.
Challenges
1. Complexity
More moving parts:
Event store + Read models + Projections
Simple CRUD might not need this.
2. Event Schema Evolution
Old events: { amount: 100 }
New events: { amount: 100, currency: "USD" }
Need versioning and migration strategies.
3. Eventually Consistent
Event written → Projection updated
Small delay between.
Read model might be slightly behind.
4. Querying Events
"Find all accounts with balance > $1000"
Can't query events directly!
Need projections or materialized views.
When to Use
Good Fit
✓ Audit requirements
✓ Complex business logic
✓ Need to understand how state evolved
✓ Time-travel debugging valuable
✓ Multi-view requirements (many read models)
Probably Not Needed
✗ Simple CRUD applications
✗ No audit requirements
✗ Single view of data
✗ Small, simple domains
Common Mistakes
1. Storing Commands, Not Events
Bad: "UpdateBalanceCommand"
Good: "MoneyWithdrawn" (what actually happened)
Events are facts, not intentions.
2. Too Much Detail in Events
Event: { user, time, balance, ip, browser, screen, ... }
Store what's needed. Derive what you can.
3. Mutable Events
Avoid updating events.
They represent historical facts.
Need correction? Add new compensating event.
FAQ
Q: Isn't this just logging?
Logs are side effects. Events are the source of truth. You rebuild state from events.
Q: How do I delete data (GDPR)?
Crypto shredding: Encrypt personal data, delete key. Or: Tombstone events marking data as removed.
Q: What database for event store?
Purpose-built: EventStoreDB, Axon Or: PostgreSQL, MongoDB with append-only pattern
Q: How big do event stores get?
Large! But events compress well. Archiving older events is common.
Summary
Event sourcing stores all changes as events, enabling complete history, time travel, and flexible rebuilding.
Key Takeaways:
- Store events, not just current state
- Usually append-only (avoid updating/deleting historical events)
- Replay events to get state
- Use snapshots for performance
- Projections create read models
- Perfect for audit, compliance, complex domains
- More complex than traditional CRUD
Event sourcing tells you not just where you are, but how you got there!
Related Concepts
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.