引言:当CRUD力不从心时
\n\n想象一下,你在编写一个股票交易系统。每秒都有成千上万的订单。每个订单不仅仅是“插入一条记录”。它涉及余额检查、投资组合重新计算、审计、通知。现在想象一下,你不仅需要记录当前状态,还需要恢复昨天14:35:22.003发生了什么。普通的CRUD(创建、读取、更新、删除)在这里行不通了。欢迎来到Java中的CQRS和事件溯源的世界。
\n\n这两种模式不仅仅是时髦的词汇。它们是构建高性能、具备完整审计和“时间回溯”能力的系统的工具。在本文中,我们将探讨如何使用现代框架和简洁的代码在Java中实现CQRS(命令查询职责分离)和事件溯源。内容充实,附带示例。
\n\n什么是CQRS?简单解释
\n\nCQRS 是指命令(写入)和查询(读取)之间的职责分离。在经典的CRUD中,你使用同一个模型进行读写操作。而在CQRS中,你创建两个不同的模型:一个用于修改数据(命令),另一个用于检索数据(查询)。
\n\n为什么需要它?
\n- \n
- 性能:读写可以独立扩展。你可以有10个读取副本和2个写入副本。 \n
- 安全性:命令可以检查业务规则,而查询则只需返回数据。 \n
- 灵活性:读取可以使用反规范化视图,写入可以使用规范化视图。 \n
代码示例:命令与查询分离
\n\n假设我们有一个订单管理系统。我们不使用 OrderService.save(order),而是进行分离:
// 命令——改变状态的意图\npublic class CreateOrderCommand {\n private final String orderId;\n private final String customerId;\n private final List<OrderItem> items;\n\n // 构造函数、getter方法...\n}\n\n// 命令处理器\n@Component\npublic class CreateOrderCommandHandler {\n @Autowired\n private OrderRepository orderRepository;\n\n public void handle(CreateOrderCommand command) {\n // 业务规则验证\n if (command.getItems().isEmpty()) {\n throw new IllegalArgumentException("订单必须至少包含一个商品");\n }\n Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n orderRepository.save(order);\n }\n}\n\n// 查询——获取数据\npublic class GetOrderQuery {\n private final String orderId;\n // 构造函数、getter方法...\n}\n\n// 查询处理器\n@Component\npublic class GetOrderQueryHandler {\n @Autowired\n private OrderReadRepository orderReadRepository;\n\n public OrderDTO handle(GetOrderQuery query) {\n // 从优化的数据库中直接读取\n return orderReadRepository.findById(query.getOrderId());\n }\n}\n\n请注意:OrderRepository(写入)和 OrderReadRepository(读取)是不同的接口。它们可以指向不同的数据库或不同的模式。
事件溯源:存储历史而非状态
\n\n事件溯源是一种模式,我们不保存对象的当前状态,而是保存导致该状态的一系列事件。我们不存储“订单#123:状态 = 已送达”,而是存储:“事件1:订单已创建”,“事件2:商品已添加”,“事件3:订单已支付”,“事件4:订单已送达”。
\n\n如何在Java中实现
\n\n每个事件都是一个不可变对象。我们将它们保存在事件存储(一种专门的数据库)中。聚合的当前状态通过重放所有事件来计算。
\n\n// 基础事件\npublic interface Do