Skip to the content.

Event Sourcing

Overview

Event Sourcing is a design pattern where all changes to application state are stored as a sequence of immutable events, rather than directly storing the current state.
This approach provides auditability, temporal queries, and better traceability for distributed systems, especially when combined with CQRS and microservices.


1. Key Concepts

Concept Description
Event Immutable record of a state-changing action (e.g., OrderCreated, PaymentProcessed).
Event Store Storage system for persisting events (can be relational DB, NoSQL, or specialized event store).
Aggregate Domain object whose state is derived by replaying events.
Replay Reconstructing current state by applying events in order.
Projection Read-optimized view of events for queries.

2. Event Store

Definition

The event store is the primary source of truth in event sourcing. It persists events in order and supports:

Storage Options

| Option | Description | |——–|————-| | Relational DB | Store events as JSON/serialized objects in tables | | NoSQL DB | Document-oriented storage (MongoDB, Cassandra) | | Specialized Event Store | EventStoreDB, Kafka (as an event log) |

Example – Event Table Schema

| Column | Type | Description | |——–|——|————-| | id | UUID | Unique event ID | | aggregate_id | UUID | ID of the aggregate | | event_type | String | Type of event (e.g., OrderCreated) | | payload | JSON | Event data | | timestamp | DateTime | Event creation time | | version | Integer | Event sequence number |


3. Writing Events

OrderCreatedEvent event = new OrderCreatedEvent(orderId, amount, userId);
eventStore.appendEvent(orderId, event);

4. Replaying Events

Purpose

Replaying events reconstructs the current state of an aggregate or projection.

Example – Aggregate State Reconstruction

public class OrderAggregate {
    private String orderId;
    private int amount;
    private String status;

    public void replay(List<Event> events) {
        for (Event e : events) {
            if (e instanceof OrderCreatedEvent oc) {
                this.orderId = oc.getOrderId();
                this.amount = oc.getAmount();
                this.status = "CREATED";
            } else if (e instanceof PaymentProcessedEvent pp) {
                this.status = pp.isSuccess() ? "PAID" : "FAILED";
            }
        }
    }
}

Snapshots


5. Event Sourcing Workflow

[Command] → [Validate] → [Generate Event(s)] → [Append to Event Store] → [Update Projections / Notify Subscribers]
  1. Command: Request to change state (e.g., CreateOrder)
  2. Validation: Business rules check
  3. Event Generation: Produce immutable events
  4. Persist Event: Append to event store
  5. Projection Update: Update read models for queries
  6. Notification: Publish events to other services (optional)

6. Integration with CQRS


7. Advantages


8. Challenges


9. Event Versioning & Evolution


10. Example Event Stream – Order Lifecycle

Event Payload
OrderCreated {orderId: 123, amount: 500, userId: 567}
PaymentProcessed {orderId: 123, status: SUCCESS}
OrderShipped {orderId: 123, shippedAt: "2025-10-22"}
OrderDelivered {orderId: 123, deliveredAt: "2025-10-25"}

Replaying this event stream reconstructs the complete state of the order.


11. Best Practices


References

```