Skip to content
Home » Event Sourcing Pattern: An Overview

Event Sourcing Pattern: An Overview

What is Event Sourcing?

Event Sourcing is a design pattern that enables developers to maintain the state of an application by storing all the changes (events) that have affected its state over time. This pattern treats changes as a series of events that can be queried, interpreted, and replayed to recreate past states or predict future states.

In traditional systems, we usually save the latest state of the data. However, in the event sourcing pattern, instead of storing the current state, all changes to the application state are stored as a sequence of events. This creates a comprehensive log that can be used to recreate the state of the system at any given point in time.

The Unified Event Datastore

In an event-sourcing model, events are usually stored in a unified “event store”, rather than each entity having its own table. Each entity however, will have its own series of events in the event store that describe its state changes over time. An event store is a type of database optimized for storage and query of events.

When an event occurs, it’s written to the event store. Each event is associated with the ID of the entity it pertains to, a timestamp, and possibly other metadata. Events are stored in the order they were applied for each entity.

This unified event store provides a single source of truth for the state of all entities in the system.

In an event-sourced system, the event store table, when realized as a relational database table, may be created as follows

CREATE TABLE Events (
    eventId UUID PRIMARY KEY,
    eventType VARCHAR(255),
    entityType VARCHAR(255),
    entityId UUID,
    timestamp TIMESTAMP,
    payload JSON
);
SQL

This is a very basic representation of an event store table, and you might need additional fields depending on your requirements. For example, you could include a version field for each entity to help with concurrency control, or metadata field to store additional data related to the event.

Creating and Storing Events

Suppose you have an Order entity in your system that has a list of items and a total amount. The follow code snippets shows the class responsible for creating and storing events. 

public class Order {
    private String id;
    private List<Item> items = new ArrayList<>();
    private double totalAmount;
    private List<Event> events = new ArrayList<>();

    //constructor to create a new order
    public Order(String id) {
        this.id = id;
        Event event = new OrderCreatedEvent(id, Instant.now());
        apply(event);
        events.add(event);
    }

    public void addItem(Item item) {
        Event event = new ItemAddedToOrderEvent(id, item, Instant.now());
        apply(event);
        events.add(event);

        updateTotalAmount();
    }

    private void updateTotalAmount() {
        totalAmount = items.stream().mapToDouble(Item::getPrice).sum();
        Event event = new OrderTotalUpdatedEvent(id, totalAmount, Instant.now());
        apply(event);
        events.add(event);
    }

    private void apply(Event event) {
        if (event instanceof OrderCreatedEvent) {
            this.id = ((OrderCreatedEvent) event).getId();
        } else if (event instanceof ItemAddedToOrderEvent) {
            items.add(((ItemAddedToOrderEvent) event).getItem());
        } else if (event instanceof OrderTotalUpdatedEvent) {
            totalAmount = ((OrderTotalUpdatedEvent) event).getTotalAmount();
        }
    }

}
Java

Whenever a new Order is created an OrderCreatedEvent is created and added to the list of events (which is persisted to the database). When a new item is added to the order, two new events ItemAddedToOrderEvent and OrderTotalUpdatedEvent are created to record the addition of an item to the order and the updating of the sum of prices of all items respectively. The event stream would look something like the following. 

// Order Created
{
  "eventId": "12346",
  "eventType": "OrderCreated",
  "entityType": "Order",
  "entityId": "10",
  "timestamp": "2023-07-12T10:17:30Z",
  "payload": {
    "id":"10"
  }
}

// Item added to order event
{
  "eventId": "12347",
  "eventType": "ItemAddedToOrder",
  "entityType": "Order",
  "entityId": "10",
  "timestamp": "2023-07-12T10:17:30Z",
  "payload": {
    "item": {
      "productId": "101",
      "quantity": 2,
      "price": 50
    }
  }
}

// Order total updated event
{
  "eventId": "12348",
  "eventType": "OrderTotalUpdated",
  "entityType": "Order",
  "entityId": "10",
  "timestamp": "2023-07-12T10:18:30Z",
  "payload": {
    "totalAmount": 100
  }
}
Java

Reconstructing Entities

Reconstructing an object in an event sourcing system involves reading and applying all the events related to that object from the event store. This is often referred to as “rehydrating” or “replaying” the object’s state. Lets update our class to add rehydration capability.

public class Order {
    private String id;
    private List<Item> items = new ArrayList<>();
    private double totalAmount;
    private List<Event> events = new ArrayList<>();

    // constructor to create a new order
    public Order(String id) {
        this.id = id;
        Event event = new OrderCreatedEvent(id, Instant.now());
        apply(event);
        events.add(event);
    }
    
    // Constructor to rehydrate an existing order
    public Order(List<Event> events) {
        this.events = events;
        replay();
    }

    public void addItem(Item item) {
        Event event = new ItemAddedToOrderEvent(id, item, Instant.now());
        apply(event);
        events.add(event);

        updateTotalAmount();
    }

    private void updateTotalAmount() {
        totalAmount = items.stream().mapToDouble(Item::getPrice).sum();
        Event event = new OrderTotalUpdatedEvent(id, totalAmount, Instant.now());
        apply(event);
        events.add(event);
    }

    private void apply(Event event) {
        if (event instanceof OrderCreatedEvent) {
            this.id = ((OrderCreatedEvent) event).getId();
        } else if (event instanceof ItemAddedToOrderEvent) {
            items.add(((ItemAddedToOrderEvent) event).getItem());
        } else if (event instanceof OrderTotalUpdatedEvent) {
            totalAmount = ((OrderTotalUpdatedEvent) event).getTotalAmount();
        }
    }
    
    private void replay() {
        for (Event event : events) {
            apply(event);
        }
    }
}
Java

When a Order object is created, it’s given a list of Event objects. These events are replayed by calling apply on each one in the replay method, resulting in a Object object in the state it was in after all these events occurred.

The apply method takes an Event object and adjusts the Order state based on the type of event. In a real-world application, you would typically have a repository or a similar construct that is responsible for fetching the event list from your event store based on an entity ID, and then you would use this list to construct your object.

This way, no matter how complex the object, as long as you have the sequence of events, you can replay them to get the current state of the object. This is the core concept behind Event Sourcing.

Pros and Cons of Event Sourcing Pattern

Pros

  1. Complete History: It provides an audit log of all changes to the system over time.
  2. Debugging and Diagnosis: Event logs can be used to understand what led to the current state, making it easier to diagnose and fix issues.
  3. Temporal Queries: It allows querying the state of the system at any point in time.
  4. Event Replay: You can restore the state of the system or even individual objects by replaying the events.

Cons

  1. Complexity: Event sourcing can increase the complexity of the system, and may require a mindset shift for developers accustomed to CRUD-style state handling.
  2. Event Versioning: As the system evolves, handling changes to the event schema can be challenging.
  3. Performance: Depending on the implementation, the process of rebuilding state by replaying events can be time-consuming.

Similar Patterns

A pattern that’s commonly associated with Event Sourcing is Command Query Responsibility Segregation (CQRS). CQRS is an architectural pattern that separates the responsibility of command operations (that change state) from query operations (that read state).

Typically, systems that use event sourcing use CQRS as well. Events (from Event Sourcing) represent the changes (commands) to the state, while the current state can be derived from these events and be used for queries.

While CQRS and Event Sourcing are often used together, they can be used independently. Not all CQRS systems use Event Sourcing, and not all Event Sourcing systems use CQRS.

In conclusion, Event Sourcing is a powerful design pattern that provides significant benefits but also comes with its challenges. It is not suitable for every application but can provide immense value when used in the right context.