Event Sourcing is a data persistence pattern in which changes to application state are stored as a sequence of immutable events instead of storing only the latest state. The current state of an entity can be reconstructed by replaying its events, optionally starting from a previously stored snapshot.
- Maintains a complete history of state changes.
- Events are appended to an event stream instead of updating previous events.
Example: In a banking system, instead of storing only the current account balance, events such as MoneyDeposited, MoneyWithdrawn, and TransferCompleted are stored. The current balance can be reconstructed by applying these events in sequence.

Real World Applications
Event Sourcing finds applications in diverse domains and use cases where tracking and analyzing historical data is crucial. Below are some notable examples:
- Financial Systems: Can be used where a detailed history of transactions, account activity, and state changes is important for auditing and traceability.
- Healthcare Systems: Can be useful in systems that require detailed histories of patient-record changes, treatment updates, or workflow transitions.
- Supply Chain Management: Can track inventory movements, shipment updates, warehouse activity, and other state changes over time.
- E-commerce Platforms: Can preserve order lifecycle changes such as creation, payment, cancellation, fulfillment, refund, and inventory updates.
- Gaming Systems: Can be used where replayable game-state changes, player-account history, or auditability are important.
Components
Event Sourcing in system design revolves around several core concepts and components:
- Events: Immutable records of accepted state changes. Together, the events in an aggregate’s stream contain the information needed to reconstruct its state.
- Event Store: Durable storage that maintains the complete stream of events in order and acts as the source of truth for the system. Messaging systems such as Apache Kafka or RabbitMQ may be used to distribute events to other components, but event distribution is a separate responsibility from the authoritative Event Store.
- Aggregate: A logical group of related domain objects that processes commands, applies business logic, and generates events. Each aggregate is associated with its own event stream.
- Command: Requests issued by clients or system components to perform actions, which are validated and processed by aggregates.
- Projection: A read model built by processing events, typically optimized for querying, reporting, or presenting derived application state.
- Event Bus: Messaging infrastructure that enables asynchronous communication by allowing components to publish and subscribe to events.
Working
The steps to understand how event sourcing pattern works are:
- Capture Events Instead of State: Every system change is stored as an event, such as “order created” or “item added,” instead of only saving the final state.
- Store Events in Sequence: Events are stored in the exact order they occur, forming a chronological event stream that acts as the system’s source of truth.
- Reconstruct State by Replaying Events: The current state is rebuilt by replaying stored events in sequence. For long event streams, the system may load a snapshot first and replay only the events that occurred after that snapshot.
- Handle New Events: New changes generate additional events that are appended to the sequence without modifying previous events.
- Replay Events for Debugging: Events can be replayed to trace system behavior, understand state changes over time, and investigate issues or errors.
Implementation Example
Consider an event registration system.
1. Traditional Registration System
In a traditional CRUD-based registration system, user registrations and cancellations directly modify database records. Unless additional mechanisms are implemented, previous states and changes may not be preserved.
- Limited History: Database updates may overwrite previous values, making it difficult to reconstruct past registration states without maintaining separate history records.
- Auditability: Tracking when, why, and by whom changes were made requires additional mechanisms, such as audit logs or history tables.
- Notification Challenges: Sending notifications alongside database updates requires coordination to prevent missed or inconsistent notifications when failures occur.
#include <iostream>
#include <string>
#include <unordered_map>
#include <stdexcept>
using namespace std;
enum class RegistrationState {
REGISTERED,
CANCELLED
};
class Database {
public:
static unordered_map<string, RegistrationState> records;
static void update(const string& key, RegistrationState state) {
records[key] = state;
}
};
unordered_map<string, RegistrationState> Database::records;
class Registration {
private:
string userId;
string eventId;
bool isRegistered = false;
RegistrationState state = RegistrationState::CANCELLED;
public:
Registration(const string& userId, const string& eventId)
: userId(userId), eventId(eventId) {}
void registerUser() {
if (isRegistered) {
throw runtime_error("Registration already exists");
}
state = RegistrationState::REGISTERED;
isRegistered = true;
Database::update(userId + ":" + eventId, state);
}
void cancel() {
if (!isRegistered ||
state != RegistrationState::REGISTERED) {
throw runtime_error("No active registration");
}
state = RegistrationState::CANCELLED;
Database::update(userId + ":" + eventId, state);
}
};
int main() {
Registration registration("U101", "E201");
registration.registerUser();
cout << "After Registration: REGISTERED" << endl;
registration.cancel();
cout << "After Cancellation: CANCELLED" << endl;
cout << "Database: ";
for (const auto& record : Database::records) {
cout << (record.second == RegistrationState::REGISTERED
? "REGISTERED" : "CANCELLED");
}
return 0;
}
import java.util.HashMap;
import java.util.Map;
enum RegistrationState {
REGISTERED,
CANCELLED
}
class Database {
private static final Map<String, RegistrationState> records =
new HashMap<>();
public static void update(Registration registration) {
String key = registration.getUserId() + ":" +
registration.getEventId();
records.put(key, registration.getState());
}
public static RegistrationState getState(
String userId, String eventId) {
return records.get(userId + ":" + eventId);
}
}
class Registration {
private final String userId;
private final String eventId;
private RegistrationState state;
public Registration(String userId, String eventId) {
this.userId = userId;
this.eventId = eventId;
}
public void register() {
if (state != null) {
throw new IllegalStateException(
"Registration already exists"
);
}
state = RegistrationState.REGISTERED;
Database.update(this);
}
public void cancel() {
if (state != RegistrationState.REGISTERED) {
throw new IllegalStateException(
"Registration is not active"
);
}
state = RegistrationState.CANCELLED;
Database.update(this);
}
public String getUserId() {
return userId;
}
public String getEventId() {
return eventId;
}
public RegistrationState getState() {
return state;
}
}
public class Main {
public static void main(String[] args) {
Registration registration =
new Registration("U101", "E201");
registration.register();
System.out.println("After Registration: " +
Database.getState("U101", "E201"));
registration.cancel();
System.out.println("After Cancellation: " +
Database.getState("U101", "E201"));
}
}
class RegistrationState:
REGISTERED = "REGISTERED"
CANCELLED = "CANCELLED"
class Database:
records = {}
@staticmethod
def update(registration):
key = (registration.user_id, registration.event_id)
Database.records[key] = registration.state
class Registration:
def __init__(self, user_id, event_id):
self.user_id = user_id
self.event_id = event_id
self.state = None
def register(self):
if self.state is not None:
raise ValueError("Registration already exists")
self.state = RegistrationState.REGISTERED
Database.update(self)
def cancel(self):
if self.state != RegistrationState.REGISTERED:
raise ValueError("No active registration")
self.state = RegistrationState.CANCELLED
Database.update(self)
# Example
registration = Registration("U101", "E201")
registration.register()
print("After Registration:", Database.records)
registration.cancel()
print("After Cancellation:", Database.records)
const RegistrationState = {
REGISTERED: "REGISTERED",
CANCELLED: "CANCELLED"
};
class Database {
static records = new Map();
static update(registration) {
const key = `${registration.userId}:${registration.eventId}`;
this.records.set(key, registration.state);
}
}
class Registration {
constructor(userId, eventId) {
this.userId = userId;
this.eventId = eventId;
this.state = null;
}
register() {
if (this.state !== null) {
throw new Error("Registration already exists");
}
this.state = RegistrationState.REGISTERED;
Database.update(this);
}
cancel() {
if (this.state !== RegistrationState.REGISTERED) {
throw new Error("No active registration");
}
this.state = RegistrationState.CANCELLED;
Database.update(this);
}
}
// Example
const registration = new Registration("U101", "E201");
registration.register();
console.log("After Registration:", registration.state);
registration.cancel();
console.log("After Cancellation:", registration.state);
console.log("Database:", [...Database.records.values()]);
2. Registration System Using Event Sourcing
In an event-sourced registration system, changes to registration status are recorded as immutable events rather than storing only the latest state. These events preserve the history of changes and can be replayed to reconstruct the registration state.
- Command Processing: Commands such as RegisterUser and CancelRegistration are validated, and accepted state changes generate events such as RegistrationCreated and RegistrationCancelled.
- Event Storage: Events are appended to an event store, maintaining a chronological history of registration changes.
- State Reconstruction: The current or previous registration state can be reconstructed by replaying stored events.
- Event-Driven Notifications: Event subscribers can react to registration events and send notifications. Reliable delivery requires appropriate retry and failure-handling mechanisms.
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
// Registration states
enum class RegistrationState {
NOT_REGISTERED,
REGISTERED,
CANCELLED
};
// Base event
class Event {
public:
virtual ~Event() = default;
};
// Registration created event
class RegistrationCreatedEvent : public Event {
public:
string userId;
string eventId;
RegistrationCreatedEvent(string user, string event)
: userId(user), eventId(event) {}
};
// Registration cancelled event
class RegistrationCancelledEvent : public Event {};
// Event Store
class EventStore {
private:
vector<shared_ptr<Event>> events;
public:
void append(shared_ptr<Event> event) {
events.push_back(event);
}
const vector<shared_ptr<Event>>& getEvents() const {
return events;
}
};
// Registration Aggregate
class Registration {
private:
RegistrationState state = RegistrationState::NOT_REGISTERED;
string userId;
string eventId;
public:
void registerUser(const string& user,
const string& event,
EventStore& store) {
if (state != RegistrationState::NOT_REGISTERED) {
throw runtime_error(
"Registration already exists"
);
}
auto e = make_shared<RegistrationCreatedEvent>(
user, event
);
store.append(e);
apply(*e);
}
void cancel(EventStore& store) {
if (state != RegistrationState::REGISTERED) {
throw runtime_error(
"No active registration"
);
}
auto e = make_shared<RegistrationCancelledEvent>();
store.append(e);
apply(*e);
}
void apply(const Event& event) {
if (auto e = dynamic_cast<
const RegistrationCreatedEvent*>(&event)) {
userId = e->userId;
eventId = e->eventId;
state = RegistrationState::REGISTERED;
}
else if (dynamic_cast<
const RegistrationCancelledEvent*>(&event)) {
state = RegistrationState::CANCELLED;
}
}
// Rebuild state from stored events
void replay(const EventStore& store) {
state = RegistrationState::NOT_REGISTERED;
userId.clear();
eventId.clear();
for (const auto& event : store.getEvents()) {
apply(*event);
}
}
RegistrationState getState() const {
return state;
}
};
string stateToString(RegistrationState state) {
switch (state) {
case RegistrationState::NOT_REGISTERED:
return "NOT_REGISTERED";
case RegistrationState::REGISTERED:
return "REGISTERED";
case RegistrationState::CANCELLED:
return "CANCELLED";
}
return "UNKNOWN";
}
int main() {
EventStore store;
Registration registration;
registration.registerUser("U101", "E201", store);
cout << "After Registration: "
<< stateToString(registration.getState())
<< endl;
registration.cancel(store);
cout << "After Cancellation: "
<< stateToString(registration.getState())
<< endl;
// Reconstruct the registration from events
Registration restored;
restored.replay(store);
cout << "After Event Replay: "
<< stateToString(restored.getState())
<< endl;
cout << "Total Events Stored: "
<< store.getEvents().size()
<< endl;
return 0;
}
import java.util.ArrayList;
import java.util.List;
enum RegistrationState {
NOT_REGISTERED,
REGISTERED,
CANCELLED
}
// Base event
interface Event {}
// Registration events
record RegistrationCreatedEvent(
String userId, String eventId
) implements Event {}
record RegistrationCancelledEvent()
implements Event {}
// Simple in-memory event store
class EventStore {
private final List<Event> events = new ArrayList<>();
public void append(Event event) {
events.add(event);
}
public List<Event> getEvents() {
return List.copyOf(events);
}
}
// Registration aggregate
class Registration {
private RegistrationState state =
RegistrationState.NOT_REGISTERED;
private String userId;
private String eventId;
public void register(
String userId,
String eventId,
EventStore store) {
if (state != RegistrationState.NOT_REGISTERED) {
throw new IllegalStateException(
"Registration already exists"
);
}
Event event = new RegistrationCreatedEvent(
userId, eventId
);
store.append(event);
apply(event);
}
public void cancel(EventStore store) {
if (state != RegistrationState.REGISTERED) {
throw new IllegalStateException(
"No active registration"
);
}
Event event = new RegistrationCancelledEvent();
store.append(event);
apply(event);
}
// Apply an event to update the current state
public void apply(Event event) {
if (event instanceof RegistrationCreatedEvent e) {
userId = e.userId();
eventId = e.eventId();
state = RegistrationState.REGISTERED;
} else if (event instanceof RegistrationCancelledEvent) {
state = RegistrationState.CANCELLED;
}
}
// Reconstruct state from event history
public void replay(List<Event> events) {
state = RegistrationState.NOT_REGISTERED;
userId = null;
eventId = null;
for (Event event : events) {
apply(event);
}
}
public RegistrationState getState() {
return state;
}
}
public class Main {
public static void main(String[] args) {
EventStore store = new EventStore();
Registration registration = new Registration();
registration.register("U101", "E201", store);
System.out.println("After Registration: "
+ registration.getState());
registration.cancel(store);
System.out.println("After Cancellation: "
+ registration.getState());
Registration restored = new Registration();
restored.replay(store.getEvents());
System.out.println("After Replay: "
+ restored.getState());
System.out.println("Total Events: "
+ store.getEvents().size());
}
}
class RegistrationState:
NOT_REGISTERED = "NOT_REGISTERED"
REGISTERED = "REGISTERED"
CANCELLED = "CANCELLED"
# Events
class Event:
pass
class RegistrationCreatedEvent(Event):
def __init__(self, user_id, event_id):
self.user_id = user_id
self.event_id = event_id
class RegistrationCancelledEvent(Event):
pass
# In-memory Event Store
class EventStore:
def __init__(self):
self.events = []
def append(self, event):
self.events.append(event)
def get_events(self):
return list(self.events)
# Registration Aggregate
class Registration:
def __init__(self):
self.state = RegistrationState.NOT_REGISTERED
self.user_id = None
self.event_id = None
def register(self, user_id, event_id, store):
if self.state != RegistrationState.NOT_REGISTERED:
raise ValueError("Registration already exists")
event = RegistrationCreatedEvent(user_id, event_id)
store.append(event)
self.apply(event)
def cancel(self, store):
if self.state != RegistrationState.REGISTERED:
raise ValueError("No active registration")
event = RegistrationCancelledEvent()
store.append(event)
self.apply(event)
def apply(self, event):
if isinstance(event, RegistrationCreatedEvent):
self.state = RegistrationState.REGISTERED
self.user_id = event.user_id
self.event_id = event.event_id
elif isinstance(event, RegistrationCancelledEvent):
self.state = RegistrationState.CANCELLED
def replay(self, events):
self.state = RegistrationState.NOT_REGISTERED
self.user_id = None
self.event_id = None
for event in events:
self.apply(event)
# Example
store = EventStore()
registration = Registration()
registration.register("U101", "E201", store)
print("After Registration:", registration.state)
registration.cancel(store)
print("After Cancellation:", registration.state)
# Reconstruct state from stored events
restored = Registration()
restored.replay(store.get_events())
print("After Event Replay:", restored.state)
print("Total Events Stored:", len(store.get_events()))
const RegistrationState = {
NOT_REGISTERED: "NOT_REGISTERED",
REGISTERED: "REGISTERED",
CANCELLED: "CANCELLED"
};
// Events
class RegistrationCreatedEvent {
constructor(userId, eventId) {
this.userId = userId;
this.eventId = eventId;
}
}
class RegistrationCancelledEvent {}
// In-memory Event Store
class EventStore {
constructor() {
this.events = [];
}
append(event) {
this.events.push(event);
}
getEvents() {
return [...this.events];
}
}
// Registration Aggregate
class Registration {
constructor() {
this.state = RegistrationState.NOT_REGISTERED;
this.userId = null;
this.eventId = null;
}
register(userId, eventId, store) {
if (this.state !== RegistrationState.NOT_REGISTERED) {
throw new Error("Registration already exists");
}
const event = new RegistrationCreatedEvent(
userId, eventId
);
store.append(event);
this.apply(event);
}
cancel(store) {
if (this.state !== RegistrationState.REGISTERED) {
throw new Error("No active registration");
}
const event = new RegistrationCancelledEvent();
store.append(event);
this.apply(event);
}
apply(event) {
if (event instanceof RegistrationCreatedEvent) {
this.state = RegistrationState.REGISTERED;
this.userId = event.userId;
this.eventId = event.eventId;
} else if (event instanceof RegistrationCancelledEvent) {
this.state = RegistrationState.CANCELLED;
}
}
// Reconstruct state from stored events
replay(events) {
this.state = RegistrationState.NOT_REGISTERED;
this.userId = null;
this.eventId = null;
for (const event of events) {
this.apply(event);
}
}
getState() {
return this.state;
}
}
// Example
const store = new EventStore();
const registration = new Registration();
registration.register("U101", "E201", store);
console.log("After Registration:", registration.getState());
registration.cancel(store);
console.log("After Cancellation:", registration.getState());
// Restore registration from event history
const restored = new Registration();
restored.replay(store.getEvents());
console.log("After Event Replay:", restored.getState());
console.log("Total Events Stored:", store.getEvents().length);
When to Use Event Sourcing Pattern
Event Sourcing is suitable for systems where preserving state changes and reconstructing historical states are important.
- Complete Audit History is Required: The system must preserve a detailed record of state changes.
- Historical State Reconstruction is Needed: You need to determine what an entity looked like at an earlier point in time.
- Complex State Transitions Exist: Business rules depend on how an entity reached its current state.
- Events Have Business Value: The sequence of domain events is important, not just the latest state.
- New Projections May Be Needed: Historical events can be replayed to build additional read models or reports.
- State Recovery is Important: Application state must be reconstructable from an authoritative event history.
When Not to Use Event Sourcing Pattern
Event Sourcing may introduce unnecessary complexity when an application does not require historical tracking or event-based state reconstruction.
- Simple CRUD Applications: Traditional databases are often sufficient when only the current state is required.
- High Storage Requirements: Maintaining a growing event history can increase storage and data management costs.
- Complex Consistency Management: Coordinating event processing and keeping multiple read models consistent requires additional design, especially in distributed systems.
- Replay and Maintenance Overhead: Reconstructing state from long event streams can be expensive without snapshots. Event schema evolution and versioning also require careful management.
Benefits of Event Sourcing
Event Sourcing improves traceability, debugging, and state management by storing changes as a sequence of events. Key benefits include:
- Complete Audit History: Preserves accepted state changes as events, providing a history of how the system reached its current state.
- State Reconstruction: Rebuilds current or previous states by replaying stored events.
- Temporal Queries: Enables retrieval of historical states, such as an order's status at a particular time, using event history.
- Debugging and Analysis: Helps developers investigate application behavior by examining event sequences.
- New Projections: Allows new read models to be created by replaying existing events without modifying the original event history.
- Integration Opportunities: Enables stored domain events to support analytics, notifications, and communication with other services.
Challenges of Event Sourcing
Event Sourcing introduces challenges related to event storage, consistency, concurrency, and system complexity. Key challenges include:
- Event Schema Evolution: Requires versioning and backward compatibility to support historical event formats.
- Storage and Replay Performance: Growing event histories increase storage requirements and replay time, often requiring snapshots and archival strategies.
- Eventual Consistency: Asynchronously updated read models may temporarily contain outdated data.
- Concurrency Management: Simultaneous updates require mechanisms such as optimistic concurrency control to prevent conflicting writes.
- Debugging and Replay Complexity: Tracing event flows is difficult, and replaying events must avoid repeating external side effects such as payments or notifications.