Home/Learn/System Design/Event Sourcing

Event Sourcing

Advanced
Architectural Patterns

Event sourcing stores the state of a system as a sequence of immutable events rather than mutable rows. Current state is derived by replaying events. It provides a complete audit trail and enables temporal queries.

Overview

In traditional systems, the database stores the current state — when you update a record, the previous value is lost. Event sourcing takes a different approach: instead of storing current state, it stores every state-changing event as an immutable fact in an event store (append-only log). The current state is derived by replaying all events from the beginning (or from a snapshot). For example, a bank account does not store "balance = $500"; it stores events: "AccountCreated", "Deposited($1000)", "Withdrawn($300)", "Deposited($200)", "Withdrawn($400)". Replaying these yields the current balance. Benefits include complete audit trail (every change is recorded), temporal queries (what was the state at any point in time?), debugging (replay events to reproduce bugs), and event-driven integration (events can be consumed by other services). The main challenges are event store size (snapshots needed), event schema evolution, and the learning curve.

Event Store vs Traditional DB

A traditional DB stores current state (mutable). An event store stores a sequence of immutable events. Current state is computed by replaying events from a starting point.

Conceptual — event store vs traditional state
// Traditional DB — stores current state
// | account_id | balance |
// |     A1     |  500.00 |
// Previous values are lost on UPDATE

// Event sourcing — stores all changes as events
// Event Store:
// | event_id | aggregate_id | type         | data              | timestamp   |
// | 1        | A1           | Created      | { owner: "Alice" }| 2025-01-01  |
// | 2        | A1           | Deposited    | { amount: 1000 }  | 2025-01-05  |
// | 3        | A1           | Withdrawn    | { amount: 300 }   | 2025-01-10  |
// | 4        | A1           | Deposited    | { amount: 200 }   | 2025-02-01  |
// | 5        | A1           | Withdrawn    | { amount: 400 }   | 2025-03-01  |
//
// Current balance = replay: 0 + 1000 - 300 + 200 - 400 = 500
// Balance on Jan 10? Replay events 1-3: 0 + 1000 - 300 = 700

Implementation Pattern

Commands produce events. Events are appended to the event store. Projections (read models) are built by consuming events. Snapshots optimise replay for aggregates with many events.

Java — event-sourced aggregate
// Event sourcing implementation
public class BankAccount {
    private String id;
    private BigDecimal balance = BigDecimal.ZERO;
    private List<DomainEvent> uncommittedEvents = new ArrayList<>();

    // Command → validates + produces event
    public void deposit(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0)
            throw new IllegalArgumentException("Amount must be positive");
        apply(new MoneyDeposited(id, amount, Instant.now()));
    }

    public void withdraw(BigDecimal amount) {
        if (balance.compareTo(amount) < 0)
            throw new InsufficientFundsException();
        apply(new MoneyWithdrawn(id, amount, Instant.now()));
    }

    // Event handler — updates in-memory state
    private void on(MoneyDeposited event) {
        this.balance = balance.add(event.getAmount());
    }
    private void on(MoneyWithdrawn event) {
        this.balance = balance.subtract(event.getAmount());
    }

    // Reconstitute from event history
    public static BankAccount fromHistory(List<DomainEvent> events) {
        BankAccount account = new BankAccount();
        events.forEach(account::apply);
        return account;
    }
}

Snapshots & Projections

Snapshots capture state at a point in time to avoid replaying all events. Projections build read-optimised views from events (often combined with CQRS).

Java — snapshots and projections
// Snapshot — avoid replaying 10,000 events
// Every 100 events, save a snapshot of current state
// Load: snapshot + replay only events AFTER snapshot
//
// Event 1–100 → Snapshot at event 100: { balance: 5000 }
// Events 101–105 → replay 5 events from snapshot
// Much faster than replaying all 105 events

// Projection — build read model from events
@Component
public class AccountBalanceProjection {
    @EventHandler
    public void on(MoneyDeposited event) {
        // Update read-optimised balance table
        jdbcTemplate.update(
            "UPDATE account_balances SET balance = balance + ? WHERE id = ?",
            event.getAmount(), event.getAccountId());
    }

    @EventHandler
    public void on(MoneyWithdrawn event) {
        jdbcTemplate.update(
            "UPDATE account_balances SET balance = balance - ? WHERE id = ?",
            event.getAmount(), event.getAccountId());
    }
}
// Read model is eventually consistent with event store
// But optimised for fast queries (no replay needed)

Key Points to Remember

  • 1Event sourcing stores all state changes as immutable events — current state is derived by replay.
  • 2Provides complete audit trail, temporal queries, and natural integration with event-driven systems.
  • 3Snapshots prevent performance degradation from replaying long event histories.
  • 4Projections build read-optimised views from events — often combined with CQRS.
  • 5Event schema evolution is the biggest long-term challenge — plan for versioning from day one.

Interview Questions

Sign in to ask Aria
1

What is event sourcing and how does it differ from traditional state storage?

EasyTCS
2

How do snapshots improve performance in event-sourced systems?

MediumAmazon
3

How does event sourcing relate to CQRS?

MediumGoogle
4

What are the challenges of event schema evolution?

HardFlipkart
5

Design an event-sourced banking system that supports temporal queries.

HardNetflix

Ask Aria about Event Sourcing

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…