Rather than storing only the current state of an entity, Event Sourcing preserves the complete sequence of events that produced that state.
The current state is reconstructed by replaying those events from the beginning or, more efficiently, from the latest snapshot.
Why Do We Need Event Sourcing?
Consider an online banking application where a customer deposits and withdraws money. In a traditional application, only the latest account balance is stored.Account Balance = $8,000
Although the application knows the current balance, it no longer knows how that balance was reached. With Event Sourcing, every business operation is preserved as an immutable event.
+---------+-------------------------+
| Version | Event |
+---------+-------------------------+
| 1 | AccountCreated |
| 2 | MoneyDeposited ($10,000)|
| 3 | MoneyWithdrawn ($2,000) |
+---------+-------------------------+
Replaying these events reconstructs the current account balance of $8,000 while preserving the complete history of every business operation.
What Is an Event?
An event represents something that has already happened in the application. Unlike a database record, an event is immutable, meaning it is never modified or deleted after being stored.Events are typically named in the past tense because they describe completed business operations.
+----------------------+
| Event |
+----------------------+
| OrderCreated |
| PaymentCompleted |
| InventoryReserved |
| CustomerRegistered |
+----------------------+
For example, when a customer places an order, the application creates an OrderCreated event.
{
"eventId": "evt-1001",
"eventType": "OrderCreated",
"orderId": 101,
"customerId": 2001,
"customerName": "John Doe",
"totalAmount": 499.99,
"currency": "USD",
"version":1,
"orderDate": "2026-07-31T10:15:30Z"
}
Instead of updating the current state of an order, Event Sourcing records every business operation as a new event, preserving the complete history of the entity.
What Is an Event Store?
An Event Store is the persistent storage used by Event Sourcing to record every business event generated by an application.An Event Store is a logical concept rather than a specific database. It can be implemented using a relational database such as PostgreSQL, MySQL, or SQL Server, a NoSQL database, or a dedicated event database such as EventStoreDB.
Regardless of the underlying technology, the Event Store becomes the application's source of truth, preserving every business event in the order it occurred.
EventStoreDB (KurrentDB) is a specialized, open-source operational database designed for Event Sourcing, event-driven architectures, and microservices. It stores data as immutable, append-only sequences of events rather than traditional mutable rows and tables.
How Event Sourcing Works?
Unlike traditional applications that update database rows, Event Sourcing stores every business operation as a new event in an Event Store.Suppose a customer places an order and later completes the payment.
Instead of updating an existing orders record, each business operation appends another immutable event to the event store.
OrderCreatedEvent orderCreated = new OrderCreatedEvent(orderId, customerId, amount);
eventStore.save(orderCreated);
PaymentCompletedEvent paymentCompleted = new PaymentCompletedEvent(orderId);
eventStore.save(paymentCompleted);
Over time, the event store becomes the complete history of every business operation.
+----------+--------------+----------------+---------------------+---------+----------------------+
| Event ID | Entity ID | Aggregate Type | Event Type | Version | Occurred At |
+----------+--------------+----------------+---------------------+---------+----------------------+
| evt-1001 | 101 | Order | OrderCreated | 1 | 2026-07-31 10:15:30 |
| evt-1002 | 101 | Order | InventoryReserved | 2 | 2026-07-31 10:15:32 |
| evt-1003 | 101 | Order | PaymentCompleted | 3 | 2026-07-31 10:15:40 |
| evt-1004 | 101 | Order | ShipmentCreated | 4 | 2026-07-31 10:20:10 |
+----------+--------------+----------------+---------------------+---------+----------------------+
Building the Current State
Unlike a traditional CRUD application that retrieves the latest state from a database row, Event Sourcing reconstructs the current state by replaying all events associated with an entity.Suppose the application needs to load Order 101. It first retrieves all events for that order from the Event Store, ordered by their version.
SELECT version, event_type
FROM event_store
WHERE aggregate_id = 101
ORDER BY version;
The query returns the following events.
+---------+----------------------+
| Version | Event |
+---------+----------------------+
| 1 | OrderCreated |
| 2 | InventoryReserved |
| 3 | PaymentCompleted |
| 4 | ShipmentCreated |
+---------+----------------------+
The application replays these events in version order, applying each event to reconstruct the current state of Order 101.
Optimistic Concurrency Control
Since multiple users or services may attempt to update the same entity simultaneously, Event Sourcing commonly uses Optimistic Concurrency Control (OCC) to prevent conflicting updates.Each event stored in the Event Store has a version number. Before appending a new event, the application verifies that the latest version matches the version it previously read. Suppose the latest version of Order 101 is 4.
+--------------+---------+----------------------+
| Entity ID. | Version | Event |
+--------------+---------+----------------------+
| 101 | 1 | OrderCreated |
| 101 | 2 | InventoryReserved |
| 101 | 3 | PaymentCompleted |
| 101 | 4 | ShipmentCreated |
+--------------+---------+----------------------+
The application attempts to append a new event.
eventStore.append(
orderId,
expectedVersion = 4,
new OrderDeliveredEvent(orderId)
);
If the current version is still 4, the event is appended as Version 5. If another transaction has already written Version 5, the append operation fails with a concurrency error.
Append New Event
(Expected Version = 4)
|
v
+------------------+
| Event Store |
+------------------+
|
Current Version = 4 ?
/ \
Yes No
| |
v v
Append as Concurrency
Version 5 Error
Unlike traditional databases that rely on row-level locks, Optimistic Concurrency Control assumes conflicts are uncommon and detects them using version numbers. This allows multiple clients to read the same entity concurrently while preventing conflicting updates.
Snapshots
As the number of events grows, rebuilding an entity by replaying every event can become expensive.To improve performance, Event Sourcing systems periodically create Snapshots, which store the reconstructed state of an entity at a particular version.
Suppose a snapshot exists for Order 101 after processing the first ten events.
CREATE TABLE snapshots (
aggregate_id BIGINT PRIMARY KEY,
aggregate_type VARCHAR(50),
version INT,
snapshot_data JSONB,
created_at TIMESTAMP
);
When the application loads the order, it first retrieves the latest snapshot.
SELECT *
FROM snapshots
WHERE aggregate_id = 101;
Instead of replaying every event from the beginning, the application starts from the snapshot and replays only the events that occurred afterward.
Event Store
|
v
Events v1 ... v10
|
v
Snapshot (v10)
|
v
Replay Events v11 - v14
|
v
Current Order State
By loading the latest snapshot and replaying only the newer events, the application reconstructs the current state much faster while still preserving the complete history of all events.
Event Sourcing with CQRS
Event Sourcing and CQRS are frequently used together.The Command side records every business operation as an immutable event in the Event Store, while the Query side consumes those events to build and maintain one or more read models optimized for specific query patterns.
Suppose a customer places an order. Instead of updating a database row directly, the command side appends an OrderCreated event to the Event Store.
Create Order
|
v
+----------------------+
| Event Store |
+----------------------+
|
OrderCreated Event
|
v
+----------------------+
| Projection Builder |
+----------------------+
|
v
+----------------------+
| order_view |
+----------------------+
The query side consumes the event and updates a read model optimized for fast queries.
CREATE TABLE order_view (
order_id BIGINT PRIMARY KEY,
customer_name VARCHAR(100),
total_amount DECIMAL(10,2),
payment_status VARCHAR(20),
status VARCHAR(20)
);
When the customer later completes payment, the command side appends a new PaymentCompleted event.
PaymentCompletedEvent event =
new PaymentCompletedEvent(orderId);
eventStore.save(event);
The query side consumes the new event and updates the existing read model.
UPDATE order_view
SET payment_status = 'COMPLETED'
WHERE order_id = 101;
Over time, additional events such as InventoryReserved and ShipmentCreated continue updating the read model.
Because the complete history is preserved in the Event Store, read models can be rebuilt at any time simply by replaying the event stream. This makes Event Sourcing and CQRS a natural combination for scalable, event-driven systems.
Event Sourcing with Apache Kafka
Although an Event Store and Apache Kafka both store events, they serve different purposes.The Event Store is the application's permanent system of record for business state, while Kafka is a distributed event streaming platform that transports events to downstream services.
Suppose a customer places an order. The command side first appends an OrderCreated event to the Event Store. After the transaction succeeds, the same event is published to Kafka.
OrderCreatedEvent event =
new OrderCreatedEvent(
orderId,
customerId,
amount
);
eventStore.save(event);
kafkaTemplate.send("orders", event);
Kafka then delivers the event to all subscribed services.
Create Order
|
v
+----------------------+
| Event Store |
+----------------------+
|
OrderCreated Event
|
v
+----------------------+
| Kafka |
+----------------------+
| | |
| | |
v v v
Inventory Analytics Notifications
Service Service Service
Each service processes the event independently.
@KafkaListener(topics = "orders")
public void consume(OrderCreatedEvent event) {
inventoryService.reserve(event.getOrderId());
}
For example, the Inventory Service reserves products, the Notification Service sends an order confirmation, and the Analytics Service updates reporting data by consuming the same OrderCreated event.
The Event Store remains the authoritative source for rebuilding application state, while Kafka provides reliable event distribution and asynchronous communication between independent services.
When Should You Use Event Sourcing?
Event Sourcing is most beneficial when maintaining a complete history of business operations is a core requirement.It is commonly used in banking systems, financial trading platforms, insurance applications, inventory management, logistics systems, and other audit-sensitive domains where every state change must be preserved.
Because the complete event history is retained, applications can reconstruct previous states, replay events to rebuild read models, recover projections, and simplify auditing and debugging.
When Should It Be Avoided?
Event Sourcing introduces additional complexity because the current application state must be reconstructed from a sequence of events.Applications must carefully handle event versioning, schema evolution, snapshots, event ordering, and backward compatibility as event definitions evolve over time.
Since events are immutable, existing records cannot simply be updated or deleted. Instead, corrections are made by appending new compensating events.
For simple CRUD applications where only the latest state is required and maintaining historical events provides little business value, the additional complexity often outweighs the benefits.
Summary
Event Sourcing stores every business operation as an immutable event instead of maintaining only the latest database state.By replaying events, applications can reconstruct the current state, recover previous states, rebuild read models, and maintain a complete audit history.
Although it introduces additional complexity, Event Sourcing is well suited for systems that require traceability, auditability, and scalable event-driven architectures.