关于DDD架构中聚合作为CQRS/Event Sourcing处理器的实现咨询
从DDD+CQRS+ES的聚合设计到领域建模实操建议
嘿,看来你已经摸到DDD进阶玩法的门儿了——CQRS和Event Sourcing(ES)确实是把DDD里的领域边界、聚合根这些抽象概念落地的利器,尤其是你提到的「聚合自身处理命令和事件」这种设计,刚好能最大化聚合的内聚性,把核心业务逻辑牢牢锁在领域层里,避免散落到外部的处理器中。
给你分享几个实操方向,帮你把这个思路落地到具体的领域建模里:
先锁定一个极小的业务场景,别贪大
一开始别碰复杂的系统,比如选「电商订单的创建、支付与确认」这个小场景就足够。先把核心业务规则一条条列清楚:订单创建时必须关联至少一件商品
未支付的订单24小时后自动取消
确认订单前必须完成支付
这些规则就是你要通过聚合、命令、事件来落地的核心逻辑。拆解聚合、命令与事件,让聚合成为绝对核心
- 聚合根就设为
Order,所有和订单相关的核心判断都由它来做:比如收到「确认订单」的命令时,它要自己检查当前状态是不是「已支付」,不符合就抛出领域异常,绝对不能让外部服务来做这个判断。 - 命令是触发业务动作的请求:比如
CreateOrderCommand(带商品列表、用户ID)、ConfirmOrderCommand(带订单ID)、CancelOrderCommand(带订单ID),这些命令直接发给Order聚合,由聚合处理。 - 事件是聚合状态合法变更后的结果:比如
OrderCreatedEvent、OrderPaidEvent、OrderConfirmedEvent,这些事件由聚合自己生成,一旦生成就代表状态已经符合业务规则,后续可以用来同步查询端数据,或者触发库存扣减这类下游动作。
- 聚合根就设为
落地Event Sourcing:用事件重构聚合状态
ES的核心思路是只持久化事件,聚合的当前状态通过重放历史事件得到。比如Order聚合可以这么实现:public class Order { private OrderId orderId; private List<OrderItem> items; private OrderStatus status; private List<DomainEvent> pendingEvents = new ArrayList<>(); // 用历史事件初始化聚合,还原状态 public Order(List<DomainEvent> historyEvents) { for (DomainEvent event : historyEvents) { applyEvent(event); } } // 处理创建订单命令 public static Order create(CreateOrderCommand command) { // 校验业务规则:至少一件商品 if (command.getItems().isEmpty()) { throw new DomainException("订单必须包含至少一件商品"); } Order order = new Order(new ArrayList<>()); OrderCreatedEvent event = new OrderCreatedEvent( command.getOrderId(), command.getUserId(), command.getItems() ); order.applyEvent(event); order.pendingEvents.add(event); return order; } // 处理确认订单命令 public void confirm(ConfirmOrderCommand command) { // 校验业务规则:必须已支付 if (this.status != OrderStatus.PAID) { throw new DomainException("仅已支付的订单可确认"); } OrderConfirmedEvent event = new OrderConfirmedEvent(this.orderId); applyEvent(event); this.pendingEvents.add(event); } // 应用事件更新聚合状态 private void applyEvent(DomainEvent event) { if (event instanceof OrderCreatedEvent createdEvent) { this.orderId = createdEvent.getOrderId(); this.items = createdEvent.getItems(); this.status = OrderStatus.CREATED; } else if (event instanceof OrderPaidEvent paidEvent) { this.status = OrderStatus.PAID; } else if (event instanceof OrderConfirmedEvent confirmedEvent) { this.status = OrderStatus.CONFIRMED; } } // 获取待发布的事件,由基础设施层持久化并发布 public List<DomainEvent> getPendingEvents() { return Collections.unmodifiableList(pendingEvents); } }这里的关键是:命令只负责触发动作,聚合自己做规则校验,生成事件后更新自身状态,所有状态变更都必须通过事件来完成。
CQRS的查询端要单独设计
命令端只关心业务逻辑和事件持久化,查询端需要构建适合查询的模型。比如用户要查自己的所有订单,你不需要去重放每个订单的事件,而是可以监听所有订单相关的事件,实时更新一个OrderQueryModel(包含订单ID、商品名称、状态、创建时间、支付时间等用户需要的字段),查询时直接从这个模型读数据,效率会高很多。
内容的提问来源于stack exchange,提问作者Rodrigo Riskalla Leal
相关产品推荐
相关产品推荐

