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

ordering-service-flow

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

ordering-service-identified-command

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

ordering-service-validator-mapper

References

https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs

https://learn.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns

https://www.xcode.me/Training?keyword=cqrs