Introduction
Most applications store only the current state of data. For example, when a customer updates their address, the database simply replaces the old value with the new one. While this approach is straightforward, it often loses valuable historical information about how and when changes occurred.
Event Sourcing offers a different approach. Instead of storing the current state, it stores every change as a sequence of events. The current state is then reconstructed by replaying those events.
This architectural pattern has gained popularity in domains where auditability, traceability, and business history are important. However, Event Sourcing is not suitable for every application and can introduce significant complexity if used incorrectly.
In this article, we'll explore how Event Sourcing works in .NET, its advantages and challenges, and the scenarios where it makes sense—and where it doesn't.
What Is Event Sourcing?
Event Sourcing is a design pattern where every state change is stored as an immutable event.
Instead of storing:
Account Balance = $500
The system stores:
Account Created
Money Deposited ($1000)
Money Withdrawn ($200)
Money Withdrawn ($300)
The current balance is calculated by replaying all events.
This provides a complete history of how the system reached its current state.
Traditional CRUD vs Event Sourcing
Traditional CRUD Approach
A database table stores only the latest data.
Accounts Table
Id Balance
1 $500
Previous changes are lost unless additional auditing mechanisms are implemented.
Event Sourcing Approach
The system stores all events.
AccountCreated
MoneyDeposited
MoneyWithdrawn
MoneyWithdrawn
Current state is derived from event history.
This enables full traceability and historical analysis.
Core Concepts of Event Sourcing
Before implementing Event Sourcing, it's important to understand its fundamental building blocks.
Events
Events represent facts that occurred in the past.
Examples:
OrderCreated
ProductAddedToCart
PaymentCompleted
UserRegistered
Events should be immutable once recorded.
Example:
public record OrderCreatedEvent(
Guid OrderId,
Guid CustomerId,
DateTime CreatedAt);
Events describe something that happened, not something that should happen.
Event Store
An Event Store is responsible for persisting events.
Example structure:
EventId
AggregateId
EventType
EventData
CreatedAt
Popular options include:
SQL Server
PostgreSQL
EventStoreDB
Azure Cosmos DB
Aggregates
Aggregates represent business entities whose state is rebuilt from events.
Example:
public class BankAccount
{
public decimal Balance { get; private set; }
public void Apply(
MoneyDepositedEvent @event)
{
Balance += @event.Amount;
}
}
The aggregate's state is reconstructed by applying events in order.
Implementing Event Sourcing in .NET
Let's build a simplified bank account example.
Create Events
public record MoneyDepositedEvent(
decimal Amount);
public record MoneyWithdrawnEvent(
decimal Amount);
Aggregate Implementation
public class BankAccount
{
public decimal Balance { get; private set; }
public void Apply(
MoneyDepositedEvent @event)
{
Balance += @event.Amount;
}
public void Apply(
MoneyWithdrawnEvent @event)
{
Balance -= @event.Amount;
}
}
Rebuild State from Events
var account = new BankAccount();
foreach (var @event in events)
{
switch (@event)
{
case MoneyDepositedEvent deposit:
account.Apply(deposit);
break;
case MoneyWithdrawnEvent withdraw:
account.Apply(withdraw);
break;
}
}
After replaying all events, the account reflects its current state.
Benefits of Event Sourcing
Complete Audit Trail
Every change is permanently recorded.
Benefits include:
Compliance support
Regulatory reporting
Historical analysis
Security investigations
Unlike traditional databases, nothing is lost.
Time Travel and Replay
Since all events are stored, developers can reconstruct application state at any point in time.
Examples:
This capability is extremely valuable in business-critical systems.
Easier Debugging
Understanding how data reached a specific state becomes much easier.
Developers can inspect event streams to identify exactly what happened.
Event-Driven Architecture Support
Event Sourcing naturally integrates with:
CQRS
Message brokers
Microservices
Distributed systems
Events can be published to other services without additional transformation.
Challenges of Event Sourcing
Despite its advantages, Event Sourcing introduces complexity.
Increased Development Complexity
Traditional CRUD:
product.Price = 100;
await dbContext.SaveChangesAsync();
Event Sourcing:
var @event =
new ProductPriceUpdatedEvent(100);
await eventStore.AppendAsync(@event);
Developers must manage event streams, projections, and aggregate reconstruction.
Event Versioning
Business requirements change over time.
Consider:
public record OrderCreatedEvent(
Guid OrderId);
Later:
public record OrderCreatedEvent(
Guid OrderId,
string Currency);
Handling old and new event formats becomes a significant challenge.
Query Complexity
Reading data often requires projections.
Instead of querying tables directly, systems frequently build read models from events.
This adds additional infrastructure and maintenance requirements.
Storage Growth
Because events are never deleted, storage requirements continually increase.
Large systems may generate millions or billions of events over time.
Snapshots for Performance
Replaying thousands of events can become expensive.
A common solution is snapshotting.
Example:
Event 1
Event 2
Event 3
Snapshot
Event 4
Event 5
Instead of replaying all events, the system loads the latest snapshot and applies only newer events.
This significantly improves performance.
When to Use Event Sourcing
Event Sourcing works best when business history matters.
Ideal scenarios include:
Financial Systems
Examples:
Banking platforms
Payment processing
Accounting systems
Every transaction must be traceable.
E-Commerce Platforms
Examples:
Historical visibility provides business value.
Healthcare Applications
Medical systems often require complete historical records for compliance and auditing.
Complex Domain-Driven Design Systems
Applications with sophisticated business rules often benefit from Event Sourcing's flexibility and traceability.
When to Avoid Event Sourcing
Many applications do not require Event Sourcing.
Examples include:
Simple CRUD Applications
Examples:
Internal admin portals
Basic inventory systems
Small business websites
Traditional relational databases are usually sufficient.
Reporting Dashboards
If the application primarily displays data rather than tracking business processes, Event Sourcing may add unnecessary complexity.
Small Projects
The additional infrastructure, learning curve, and maintenance costs often outweigh the benefits.
Applications Without Audit Requirements
If historical changes have little business value, storing every event may not be justified.
Event Sourcing and CQRS
Event Sourcing and CQRS are often used together but are not the same thing.
CQRS separates:
Event Sourcing stores:
Many Event Sourcing systems use CQRS to create optimized read models while preserving event streams for write operations.
However, either pattern can exist independently.
Best Practices
When implementing Event Sourcing in .NET:
Design events as immutable facts.
Use meaningful event names.
Store events in append-only streams.
Plan for event versioning early.
Implement snapshots for large event streams.
Build dedicated read models for queries.
Keep events focused on business outcomes.
Monitor storage growth over time.
Test event replay scenarios thoroughly.
Adopt Event Sourcing only when business requirements justify the added complexity.
Conclusion
Event Sourcing provides a powerful way to model systems where history, auditability, and business traceability are critical. By storing every change as an immutable event, applications gain complete visibility into how data evolves over time and can reconstruct state whenever needed.
However, these benefits come with trade-offs. Increased complexity, event versioning challenges, projection management, and storage considerations make Event Sourcing unsuitable for many traditional CRUD applications.
Before adopting Event Sourcing, carefully evaluate your business requirements. If your system depends on historical accuracy, compliance, auditing, or complex business workflows, Event Sourcing can be an excellent architectural choice. If not, a simpler relational model may provide better maintainability with significantly less overhead.