CQRS和事件溯源在C#中的应用:复杂系统的架构

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

当常规CRUD不再奏效:CQRS和事件溯源入门

\n\n

想象一下,您正在为银行编写一个系统。每秒都有数百名用户检查余额、转账、开立存款。您的C#应用程序使用标准的Entity Framework和典型的CRUD操作运行。突然——数据库崩溃了。为什么?因为一个繁重的报表在写入事务时锁定了表。熟悉吗?

\n\n

在C#中开发复杂系统的开发人员迟早会遇到经典架构的瓶颈。两个强大的模式可以帮到您:CQRS(命令查询职责分离)和事件溯源。让我们通过实际示例(而非枯燥的理论)来实践它们。

\n\n

什么是CQRS,为什么它不仅仅是“分离”?

\n\n

CQRS并不是简单地将方法放入不同的文件夹。它是一种理念:命令(改变状态)和查询(读取状态)应该分开存在。在经典的CRUD中,您只有一个数据模型用于读取和写入。而在CQRS中,您至少有两个。

\n\n

经典方法的问题

\n

假设您有一个UserRepository,其中包含GetUserById方法。当您读取用户以显示在屏幕上时,您需要3个字段。当您更新时,需要10个字段和验证。结果,一个模型变成了一个充满null字段和混乱映射器的怪物。

\n\n

在CQRS中,您分离了职责:

\n
    \n
  • Command — 执行操作(例如CreateOrderCommand)
  • \n
  • Query — 返回数据(例如GetOrderQuery)
  • \n
  • Read Model — 扁平化的DTO,针对特定屏幕优化
  • \n
  • Write Model — 包含业务逻辑的丰富领域模型
  • \n
\n\n

使用MediatR在C#中编写CQRS

\n

在C#中实现CQRS最流行的方法是使用MediatR库。它实现了中介者模式,并完美契合命令和查询的概念。

\n\n

步骤1:定义命令

\n
public record CreateOrderCommand(\n    Guid CustomerId,\n    List<OrderItemDto> Items,\n    string ShippingAddress\n) : IRequest<Guid>;  // 返回创建的订单ID
\n\n

步骤2:命令处理器

\n
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>\n{\n    private readonly IOrderRepository _orderRepository;\n    private readonly IUnitOfWork _unitOfWork;\n\n    public CreateOrderCommandHandler(IOrderRepository orderRepository, IUnitOfWork unitOfWork)\n    {\n        _orderRepository = orderRepository;\n        _unitOfWork = unitOfWork;\n    }\n\n    public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)\n    {\n        // 业务逻辑:检查、验证、创建\n        var order = new Order(request.CustomerId, request.ShippingAddress);\n        \n        foreach (var item in request.Items)\n        {\n            order.AddItem(item.ProductId, item.Quantity, item.Price);\n        }\n\n        await _orderRepository.AddAsync(order);\n        await _unitOfWork.SaveChangesAsync(cancellationToken);\n        \n        return order.Id;\n    }\n}
\n\n

步骤3:定义查询

\n
public record GetOrderSummaryQuery(Guid OrderId) : IRequest<OrderSummaryDto>;
\n\n

步骤4:查询处理器(从单独的数据库读取!)

\n
public class GetOrderSummaryQueryHandler : IRequestHandler<GetOrderSummaryQuery, OrderSummaryDto>\n{\n    private readonly IReadDbContext _readDbContext; // 用于读取的单独上下文\n\n    public GetOrderSummaryQueryHandler(IReadDbContext read                

博客

书籍推荐