CQRS is short for Command Query Responsibility Segregation. It is a pattern that separates read operations from update operations: queries return results without changing system state and have no side effects, while commands change system state. Idempotency means that executing the same command multiple times produces the same result with no side effects.
Why Use the CQRS Pattern
In traditional architectures, the same data model is used to query and update the database. This is simple and works well for basic CRUD operations.
CQRS separates reads and writes into distinct models: commands update data, and queries read data. The read store can be a read-only replica of the write store, or the read and write stores can have completely different structures. Using multiple read-only replicas can improve query performance, especially in distributed scenarios where the read replicas are located close to the application instances.
Implementing the Command Pattern in CQRS with MediatR
dotnet add package MediatR
public class CreateOrderCommand : IRequest<bool>
{
public string Product { get; set; }
public int Quantity { get; set; }
}
public class CreateOrderCommandHandler1 : IRequestHandler<CreateOrderCommand, bool>
{
public Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(1);
}
}
public class CreateOrderCommandHandler2 : IRequestHandler<CreateOrderCommand, bool>
{
public Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(2);
}
}
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderCommand command)
{
var orderId = await _mediator.Send(command);
return Ok(orderId);
}
}
builder.Services.AddMediatR(options => options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
Handling CQRS Commands with the Mediator Request Pipeline
LoggingBehavior, ValidatorBehavior, and TransactionBehavior
builder.Services.AddMediatR(options =>
{
options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
options.AddOpenBehavior(typeof(ValidatorBehavior<,>));
options.AddOpenBehavior(typeof(TransactionBehavior<,>));
});
Or use the Pipeline extension methods
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidatorBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));
Message Deduplication Mechanism
Implementing the Query Pattern in CQRS
public interface IOrderQueries
{
Task<OrderDetails> GetOrderAsync(int id);
Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(int userId);
}
builder.Services.AddScoped<IOrderQueries,OrderQueries>();
Handling Integration Events with Dapr.AspNetCore
dotnet add package Dapr.AspNetCore
services.AddDaprClient();
Refactoring Folders
The EntityFrameworks folder is renamed to the Infrastructure folder
Under the BasketService microservice’s Infrastructure folder, add a Repositories folder
Implementing the Outbox Pattern
builder.Services.AddTransient<IDistributedEventLogService, DistributedEventLogService<OrderingServiceDbContext>>();
Generic Type Mapping and Validation
References
https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs
https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns