Skip to content

Event Sourcing Fundamentals

What event sourcing actually is and when you should use it

Event sourcing isn’t rocket science, but most explanations make it sound like it. Here’s what it actually is: instead of storing what your data looks like now, you store what happened to it.

Think of your bank account. Your bank doesn’t just store “John has $500.” They store every transaction: deposit $1000, withdraw $200, pay rent $300. Your current balance is calculated from that history.

That’s event sourcing. You store the events (what happened) instead of the current state (what things look like now).

[Diagram Placeholder: Traditional vs Event Sourcing Storage] Traditional: User table with current values Event Sourcing: Stream of UserRegistered, EmailChanged, ProfileUpdated events

Events are facts about what happened in your business:

  • OrderPlaced - Someone ordered pizza
  • PaymentProcessed - Their card was charged
  • OrderDelivered - Pizza reached the customer

These events are:

  • Immutable - You can’t change what happened
  • Append-only - New events get added, old ones stay
  • Timestamped - You know when everything happened

Events are grouped into streams. Usually one stream per business entity:

  • order-123 stream contains all events for order 123
  • user-456 stream contains all events for user 456

An aggregate is just the current state built from all events in a stream.

[Diagram Placeholder: Event Stream to Aggregate] Stream: [OrderPlaced, ItemAdded, ItemRemoved, OrderConfirmed] → Current Order State

// Update user email
await db.users.update(userId, { email: 'new@email.com' });
// Old email is gone forever
// Record what happened
await eventStore.append('user-123', {
type: 'EmailChanged',
data: {
oldEmail: 'old@email.com',
newEmail: 'new@email.com',
reason: 'user_request'
}
});
// History is preserved

[Diagram Placeholder: CRUD vs Event Sourcing Data Flow]

Use event sourcing when:

  • Audit trails matter - Financial systems, healthcare, legal
  • Business processes are complex - E-commerce, logistics, workflows
  • You need to replay scenarios - Debugging, analytics, compliance
  • Temporal queries are important - “What did our inventory look like last Tuesday?”

Don’t use event sourcing when:

  • Simple CRUD is enough - Basic user profiles, settings
  • Performance is critical - High-frequency trading, real-time games
  • Your team isn’t ready - Event sourcing adds complexity
  • Data rarely changes - Reference data, configuration

E-commerce Orders Every step matters: placed, paid, shipped, delivered, returned. You need the full story for customer service and analytics.

Financial Transactions Banks don’t just store balances. Every deposit, withdrawal, and transfer is recorded. Regulations require it.

Collaborative Editing Google Docs doesn’t store the current document. It stores every keystroke, every change. That’s how real-time collaboration works.

Don’t event source everything - Your user’s favorite color doesn’t need an audit trail.

Don’t make events too granular - MouseMoved events will kill your performance.

Don’t ignore snapshots - Replaying 10 million events to get current state is slow.

Don’t forget about deletes - GDPR compliance is tricky with immutable events.

Complete audit trail - You know exactly what happened and when.

Time travel - Query your system as it existed at any point in history.

Debugging superpowers - Replay events to reproduce bugs.

Business insights - Analyze how your business actually works.

Scalable reads - Build multiple projections for different use cases.

Complexity - More moving parts than simple CRUD.

Storage - Events accumulate over time.

Learning curve - Your team needs to think differently.

Eventual consistency - Projections might lag behind events.

Tooling - You need an event store, not just a database.

// Append events to a stream
await eventStore.append('order-123', [
{ type: 'OrderPlaced', data: { items: ['pizza'], total: 15.99 } },
{ type: 'PaymentProcessed', data: { amount: 15.99, method: 'card' } }
]);
// Read events from a stream
const events = await eventStore.readStream('order-123');
// Build current state from events
const currentOrder = events.reduce(orderReducer, initialState);

Event stores use optimistic concurrency control:

// This will fail if someone else modified the stream
await eventStore.append('order-123', newEvents, {
expectedVersion: 5
});

[Diagram Placeholder: Optimistic Concurrency Control]

Start small. Pick one aggregate that changes frequently and has business value. Don’t try to event source your entire system on day one.

Good first candidates:

  • Shopping carts
  • Order processing
  • User registration flows
  • Document workflows

Build it, learn from it, then expand.

Event sourcing isn’t magic. It’s just a different way to think about data. Sometimes it’s exactly what you need. Sometimes it’s overkill. The key is knowing the difference.