You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:05:04