21 Mar 2024
CQRS (Command Query Responsibility Segregation) and Mediator pattern are two architectural patterns often used in ASP.NET Core WebAPI applications to achieve separation of concerns and improve maintainability, scalability, and testability of the codebase.
-
CQRS (Command Query Responsibility Segregation): CQRS is a pattern that segregates the operations that read data (queries) from the operations that modify data (commands) into separate models. This means you have separate components for handling read operations and write operations. This allows you to optimize each model differently, as reads and writes often have different requirements.
-
Mediator Pattern: The Mediator pattern is a behavioral design pattern that allows loose coupling between the components of a system by centralizing communication between these components. It involves introducing a mediator object that handles the communication between different components, thus reducing the direct dependencies between them.
In an ASP.NET Core WebAPI application, these two patterns can be used together to create a more modular and maintainable architecture.
Here's how they can work together:
-
Commands and Queries: In a CQRS architecture, commands represent operations that modify data (e.g., create, update, delete), while queries represent operations that read data (e.g., get, list). These commands and queries are typically implemented as separate classes or methods.
-
Mediator Implementation: The Mediator pattern can be used to handle the communication between the various command and query handlers. In ASP.NET Core, you can use a library like MediatR, which provides an implementation of the Mediator pattern.
-
Command Handlers: Command handlers are responsible for executing commands. They encapsulate the logic for modifying data in response to commands. When a command is received by the API, it is routed to the appropriate command handler by the mediator.
-
Query Handlers: Similarly, query handlers are responsible for executing queries. They encapsulate the logic for retrieving data in response to queries. When a query is received by the API, it is routed to the appropriate query handler by the mediator.
-
Controller Actions: In your ASP.NET Core WebAPI controllers, you would typically define actions for handling incoming HTTP requests. These actions would receive commands or queries from the client and pass them on to the mediator for processing.
-
Separation of Concerns: By using CQRS and the Mediator pattern together, you achieve a clear separation of concerns between commands and queries, as well as loose coupling between components. This makes your codebase easier to maintain, test, and scale.
Here's a simplified example of how you might implement CQRS with Mediator in ASP.NET Core WebAPI using MediatR:
// Command
public class CreateProductCommand : IRequest<int>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
// Command Handler
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
private readonly ApplicationDbContext _context;
public CreateProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
var product = new Product { Name = request.Name, Price = request.Price };
_context.Products.Add(product);
await _context.SaveChangesAsync(cancellationToken);
return product.Id;
}
}
// Query
public class GetProductsQuery : IRequest<List<Product>>
{
}
// Query Handler
public class GetProductsQueryHandler : IRequestHandler<GetProductsQuery, List<Product>>
{
private readonly ApplicationDbContext _context;
public GetProductsQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<Product>> Handle(GetProductsQuery request, CancellationToken cancellationToken)
{
return await _context.Products.ToListAsync(cancellationToken);
}
}
// Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<ActionResult<int>> Create(CreateProductCommand command)
{
var productId = await _mediator.Send(command);
return Ok(productId);
}
[HttpGet]
public async Task<ActionResult<List<Product>>> GetAll()
{
var query = new GetProductsQuery();
var products = await _mediator.Send(query);
return Ok(products);
}
}
In this example, MediatR library is used for Mediator implementation. CreateProductCommand represents a command to create a product, GetProductsQuery represents a query to get all products. Their corresponding handlers encapsulate the logic for handling these commands and queries. The controller actions then use the mediator to send these commands and queries to their respective handlers. This way, the controller actions remain lightweight and only deal with the HTTP request/response aspects, while the actual business logic resides in the command and query handlers.