CQRS (Command Query Responsibility Segregation) is a pattern that separates the responsibilities of reading and writing data into distinct models. This separation can help optimize both aspects independently and improve the scalability and maintainability of applications. Let’s dive into an overview of CQRS and explore a simple implementation in C#.
CQRS Fundamentals
CQRS divides the application into two parts.
- Command Side: Responsible for handling requests that change the state of the system (e.g., creating, updating, or deleting data).
- Query Side: Responsible for handling requests that retrieve data without modifying it.
This segregation allows each side to evolve independently, optimize performance for their specific tasks, and potentially scale differently.
Benefits and Trade-Offs
- Benefits
- Optimized Data Models: The command and query models can be tailored to their respective needs, which can simplify complex queries and commands.
- Scalability: The read and write operations can be scaled independently. For instance, if reading is more frequent than writing, the read model can be scaled horizontally without affecting the write model.
- Security and Authorization: Different security requirements can be applied to the command and query sides, providing finer control over access.
- Trade-Offs
- Increased Complexity: Maintaining two separate models and their synchronization can add complexity. This often requires additional infrastructure, such as messaging systems or event stores.
- Data Consistency: Since the read and write sides are separated, you need to handle eventual consistency. This might involve asynchronous updates or complex data synchronization strategies.
Detailed Example with C#
Let’s build a more robust CQRS example, incorporating additional concepts like event sourcing and asynchronous processing.
Define the Models
We'll use the same UserCommand and UserDto models. For more complexity, we’ll include a domain event model.
// Domain Event
public class UserCreatedEvent
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Implement Command Handlers
We'll enhance the command handler to publish domain events after handling commands.
public interface ICommandHandler<in TCommand>
{
Task HandleAsync(TCommand command);
}
public class CreateUserCommandHandler : ICommandHandler<UserCommand>
{
private readonly List<UserDto> _userStore;
private readonly IEventPublisher _eventPublisher;
public CreateUserCommandHandler(List<UserDto> userStore, IEventPublisher eventPublisher)
{
_userStore = userStore;
_eventPublisher = eventPublisher;
}
public async Task HandleAsync(UserCommand command)
{
var user = new UserDto
{
Id = command.Id,
Name = command.Name,
Email = command.Email
};
_userStore.Add(user);
var userCreatedEvent = new UserCreatedEvent
{
Id = user.Id,
Name = user.Name,
Email = user.Email
};
await _eventPublisher.PublishAsync(userCreatedEvent);
}
}
ben jasonPosted Mar 21, 2026, 8:56 AM
One of the strongest advantages of CQRS is how it allows independent optimization. The command side can focus on enforcing business rules and maintaining consistency, while the query side can be tailored for fast data retrieval, often using denormalized or read-optimized structures.
Satyaki ChakrabortyPosted Feb 4, 2025, 3:41 PM
This is very basic CQRS, if you want to implement it in API then you need mediator pattern.