Skip to content

Event Sourcing Pattern

Introduction

Traditional systems persist current state in a database:

  • Orders table has the latest order record.
  • Bank accounts store the latest balance.

But this approach loses valuable history. For example:

  • How did the account balance change over time?
  • Which events led to the current state?

The Event Sourcing Pattern solves this by storing all changes (events) to application state as an append-only log.

  • Current state is reconstructed by replaying events.
  • Provides complete audit trails, replayability, and integration opportunities.

Intent

The Event Sourcing Pattern’s intent is to persist application state as a sequence of events, not just the latest snapshot, enabling auditability, traceability, and replay.


Structure

Core Components

  1. Event Store

    • Append-only log of domain events.
  2. Event Publisher

    • Persists and publishes new events.
  3. Event Handlers / Projectors

    • Consume events to update read models.
  4. Read Models (Projections)

    • Query-optimized views derived from events.
mermaid
graph TD
  A[Command] --> B[Event]
  B --> C[Event Store]
  C --> D[Read Model Projector]
  D --> E[(Read Model)]

✅ Source of truth = events.
✅ Read models are projections.


Participants

  1. Commands

    • User intent that triggers domain behavior.
  2. Domain Events

    • Immutable records of what happened.
    • Example: OrderPlaced, PaymentCompleted.
  3. Event Store

    • Stores events in order, append-only.
  4. Projections

    • Build query-optimized models.

Collaboration Flow

  1. Command executes business logic.
  2. Emits domain event.
  3. Event persisted in Event Store.
  4. Handlers project event into read models.

Implementation in Java

Domain Event

java
public class OrderPlacedEvent {
    private final String orderId;
    private final double total;
    private final Instant timestamp;

    public OrderPlacedEvent(String orderId, double total) {
        this.orderId = orderId; this.total = total;
        this.timestamp = Instant.now();
    }
    public String getOrderId() { return orderId; }
    public double getTotal() { return total; }
    public Instant getTimestamp() { return timestamp; }
}

Event Store

java
public interface EventStore {
    void save(Object event);
    List<Object> getEvents(String aggregateId);
}

Event-Sourced Repository

java
public class OrderRepository implements EventStore {
    private final Map<String, List<Object>> store = new HashMap<>();

    @Override
    public void save(Object event) {
        store.computeIfAbsent("orders", k -> new ArrayList<>()).add(event);
    }

    @Override
    public List<Object> getEvents(String aggregateId) {
        return store.getOrDefault(aggregateId, new ArrayList<>());
    }
}

Projection (Read Model)

java
@Service
public class OrderProjection {
    private final Map<String, Double> orderTotals = new HashMap<>();

    public void on(OrderPlacedEvent event) {
        orderTotals.put(event.getOrderId(), event.getTotal());
    }

    public Double getTotal(String orderId) {
        return orderTotals.get(orderId);
    }
}

✅ Events stored, not just final state.
✅ Read model derived from replay.


Consequences

Benefits

  1. Auditability – Complete history of changes.
  2. Replayability – Rebuild state anytime.
  3. Integration – Events published to other systems.
  4. Debugging – Understand exact sequence of actions.
  5. Event-Driven Friendly – Naturally integrates with CQRS.

Drawbacks

  1. Complexity – More moving parts.
  2. Storage Growth – Event store grows indefinitely.
  3. Event Versioning – Schema evolution is tricky.
  4. Consistency – Must rebuild projections carefully.

Real-World Case Studies

1. Banking Systems

  • Transactions stored as events.
  • Balance derived by summing transactions.

2. E-commerce Platforms

  • Orders tracked as events (OrderPlaced, OrderShipped).
  • Enables customer history and analytics.

3. Event Stores (Tools)

  • Axon Framework, EventStoreDB, Kafka.
  • Provide ready-made support for event sourcing.

Extended Java Case Study

Traditional Approach (CRUD)

java
// Latest state only
public class BankAccount {
    private String id;
    private double balance;

    public void deposit(double amount) { balance += amount; }
    public void withdraw(double amount) { balance -= amount; }
}

❌ Loses history of deposits/withdrawals.

Event Sourcing Approach

java
public class BankAccount {
    private String id;
    private double balance;
    private List<Object> changes = new ArrayList<>();

    public void deposit(double amount) {
        applyChange(new MoneyDepositedEvent(id, amount));
    }

    public void withdraw(double amount) {
        applyChange(new MoneyWithdrawnEvent(id, amount));
    }

    private void applyChange(Object event) {
        if(event instanceof MoneyDepositedEvent e) balance += e.getAmount();
        if(event instanceof MoneyWithdrawnEvent e) balance -= e.getAmount();
        changes.add(event);
    }
}

✅ Full history retained.
✅ Balance reconstructed from events.


Interview Prep

Q1: What is the Event Sourcing Pattern?

Answer: A pattern where application state is persisted as a sequence of events, not just final state, enabling auditability and replay.

Q2: What are pros and cons of event sourcing?

Answer: Pros: auditability, replayability, integration. Cons: complexity, storage growth, event versioning.

Q3: How does event sourcing relate to CQRS?

Answer: Commands produce events (write side). Queries read projections built from events (read side).

Q4: When should you use event sourcing?

Answer: In systems needing auditability, history, or replay (banking, trading, e-commerce).

Q5: What challenges exist?

Answer: Event schema evolution, storage growth, rebuilding projections.


Visualizing Event Sourcing Pattern

mermaid
graph TD
  Cmd[Command] --> EV[Event]
  EV --> Store[Event Store]
  Store --> Proj[Projection]
  Proj --> RM[(Read Model)]

✅ Events as source of truth.
✅ Read models derived from projections.


Key Takeaways

  • Event Sourcing stores events, not just final state.
  • Benefits: auditability, replayability, integration.
  • Challenges: complexity, storage, event versioning.
  • Works well with CQRS and event-driven systems.

Next Lesson

Next, we’ll cover Saga Pattern — managing long-running distributed transactions in microservices.

Continue to Saga Pattern →


Connect: LinkedIn

© 2025 Official CTO. All rights reserved.