21 December 2018
Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates the logic for reading data from the logic for writing data. It’s an advanced design pattern often used to help scale applications by organizing the data management layer more efficiently. In this post, we’ll introduce the CQRS pattern, explain its core concepts, and walk through how to implement CQRS in a .NET Core application.
What is CQRS?
CQRS is a pattern that divides the application logic into two distinct parts:
- Commands: These are operations that modify data (e.g., create, update, delete). A command doesn’t return data other than a confirmation of the action.
- Queries: These are operations that retrieve data but do not modify it.
The core idea behind CQRS is to separate the concerns of reading and writing data, allowing for optimized performance and scalability for both operations. This approach is often used in systems that need to handle high loads, where the read and write operations have different performance requirements.
Benefits of CQRS
- Separation of Concerns: Read and write operations are handled separately, improving maintainability.
- Optimized Data Models: You can have different data models for reading and writing, which allows optimization for performance (e.g., query models can be denormalized for faster reads).
- Scalability: CQRS allows for better scaling of the read and write sides of the application independently.
- Improved Security: It simplifies access control, as different operations (queries vs commands) might have different security requirements.
Implementing CQRS in a .NET Core Application
Let’s dive into how to implement CQRS in a simple .NET Core application. In this example, we’ll use an application that manages Products in an inventory system. We’ll separate the command and query responsibilities.
Step 1: Define Command and Query Models
Start by creating Product models for both the read (Query) and write (Command) sides.
// Command: Product creation
public class CreateProductCommand
{
public string Name { get; set; }
public decimal Price { get; set; }
}
// Query: Product read
public class ProductQuery
{
public int ProductId { get; set; }
}
The CreateProductCommand
model is used to send data for creation purposes, and the ProductQuery
is used to fetch product information.
Step 2: Create Handlers for Commands and Queries
CQRS requires handlers for both commands and queries. These handlers implement the business logic for the operations.
Command Handler:
public class CreateProductCommandHandler
{
private readonly ApplicationDbContext _context;
public CreateProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task Handle(CreateProductCommand command)
{
var product = new Product
{
Name = command.Name,
Price = command.Price
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
}
}
The CreateProductCommandHandler
takes the command model, processes it, and modifies the data (inserting a new product).
Query Handler:
public class GetProductQueryHandler
{
private readonly ApplicationDbContext _context;
public GetProductQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(ProductQuery query)
{
return await _context.Products
.FirstOrDefaultAsync(p => p.ProductId == query.ProductId);
}
}
The GetProductQueryHandler
handles the query logic by fetching the required data.
Step 3: Set Up Controllers to Handle Commands and Queries
Next, we set up Controllers in .NET Core to handle the requests for commands and queries separately.
Product Command Controller:
[ApiController]
[Route("api/products")]
public class ProductCommandController : ControllerBase
{
private readonly CreateProductCommandHandler _commandHandler;
public ProductCommandController(CreateProductCommandHandler commandHandler)
{
_commandHandler = commandHandler;
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
{
await _commandHandler.Handle(command);
return Ok("Product created successfully.");
}
}
Product Query Controller:
[ApiController]
[Route("api/products")]
public class ProductQueryController : ControllerBase
{
private readonly GetProductQueryHandler _queryHandler;
public ProductQueryController(GetProductQueryHandler queryHandler)
{
_queryHandler = queryHandler;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _queryHandler.Handle(new ProductQuery { ProductId = id });
if (product == null)
return NotFound();
return Ok(product);
}
}
In these controllers, we’re using dependency injection to pass the respective command and query handlers. The ProductCommandController
handles the creation of new products, and the ProductQueryController
handles fetching product data.
Conclusion
In this post, we’ve introduced the Command Query Responsibility Segregation (CQRS) pattern and implemented it in a simple .NET Core application. By separating commands and queries, we gain several benefits such as better scalability, optimized data models, and clearer separation of concerns. While this was a basic example, you can apply CQRS to more complex systems and introduce further enhancements like event sourcing and asynchronous messaging for more advanced use cases.
CQRS can significantly improve your architecture, especially in applications with complex domains and high read/write operations. In the next post, we’ll dive deeper into handling CQRS with Event Sourcing, which is often used in conjunction with this pattern.