Isaac.

architecture

CQRS and Event Sourcing

Separate reads and writes with CQRS and Event Sourcing patterns.

By Emem IsaacJanuary 21, 20223 min read
#cqrs#event sourcing#architecture#design patterns#domain-driven-design
Share:

A Simple Analogy

CQRS is like having separate checkout and returns lines. Commands (writes) go one way, Queries (reads) go another. Event Sourcing is like writing every transaction in a journal instead of just the current balance.


What Is CQRS?

CQRS (Command Query Responsibility Segregation) separates read and write operations into different models. Queries read from optimized views, Commands update the event store.


Why Use CQRS + Event Sourcing?

  • Scalability: Scale reads and writes independently
  • Auditability: Every change is an event
  • Temporal queries: Ask "what was state at time X"
  • Debugging: Complete history of changes
  • Event-driven: Trigger workflows on events

Event Store

public interface IEvent
{
    Guid AggregateId { get; }
    DateTime Timestamp { get; }
}

public class OrderPlaced : IEvent
{
    public Guid AggregateId { get; set; }
    public DateTime Timestamp { get; set; }
    public string CustomerId { get; set; }
    public decimal Total { get; set; }
}

public class OrderShipped : IEvent
{
    public Guid AggregateId { get; set; }
    public DateTime Timestamp { get; set; }
    public string TrackingNumber { get; set; }
}

Event Store Implementation

public class EventStore
{
    private readonly List<IEvent> _events = new();
    
    public void AppendEvent(IEvent @event)
    {
        @event.Timestamp = DateTime.UtcNow;
        _events.Add(@event);
    }
    
    public List<IEvent> GetEvents(Guid aggregateId)
    {
        return _events.Where(e => e.AggregateId == aggregateId).ToList();
    }
    
    public Order ReplayEvents(Guid orderId)
    {
        var order = new Order { Id = orderId };
        var events = GetEvents(orderId);
        
        foreach (var @event in events)
        {
            order = @event switch
            {
                OrderPlaced op => order with { CustomerId = op.CustomerId, Total = op.Total },
                OrderShipped os => order with { TrackingNumber = os.TrackingNumber },
                _ => order
            };
        }
        
        return order;
    }
}

Command Handler

public class PlaceOrderCommand
{
    public Guid OrderId { get; set; }
    public string CustomerId { get; set; }
    public decimal Total { get; set; }
}

public class PlaceOrderHandler
{
    private readonly EventStore _eventStore;
    
    public void Handle(PlaceOrderCommand command)
    {
        var @event = new OrderPlaced
        {
            AggregateId = command.OrderId,
            CustomerId = command.CustomerId,
            Total = command.Total
        };
        
        _eventStore.AppendEvent(@event);
    }
}

Read Model (Projection)

public class OrderReadModel
{
    private readonly Dictionary<Guid, OrderDto> _cache = new();
    
    public void HandleOrderPlaced(OrderPlaced @event)
    {
        _cache[@event.AggregateId] = new OrderDto
        {
            Id = @event.AggregateId,
            CustomerId = @event.CustomerId,
            Status = "Placed"
        };
    }
    
    public void HandleOrderShipped(OrderShipped @event)
    {
        _cache[@event.AggregateId].Status = "Shipped";
        _cache[@event.AggregateId].TrackingNumber = @event.TrackingNumber;
    }
    
    public OrderDto GetOrder(Guid id) => _cache[id];
}

Best Practices

  1. Immutable events: Never modify recorded events
  2. Event versioning: Handle schema changes
  3. Snapshots: Cache state for old aggregates
  4. Projections: Keep read models updated
  5. Idempotency: Handle duplicate events

Related Concepts

  • Sagas for long-running processes
  • Event bus for event distribution
  • Snapshot strategy for performance
  • Temporal queries and analytics

Summary

CQRS separates reads from writes, while Event Sourcing records every change as an immutable event. Together, they enable auditability, temporal queries, and independent scalability.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours