领域驱动设计概念澄清

背景和价值

这是一个非常经典的 领域驱动设计(DDD) 问题:

“一个命令(Command)只能对应一个实体吗?”


✅ 简短回答:

不是的。

在 DDD 中,一个 Command 可以对应多个实体、值对象甚至多个聚合根,这取决于业务规则和操作的复杂度。


🧠 深入解释

🔹 什么是 Command?

在 CQRS 或事件溯源(Event Sourcing)架构中,Command 是用户或系统发出的“意图”,表示“我想要做什么”。

例如:

  • RegisterUserCommand:注册用户
  • PlaceOrderCommand:下单
  • CancelOrderCommand:取消订单
  • TransferMoneyCommand:转账

这些命令通常由应用层处理,并最终导致一个或多个聚合(Aggregate)发生变化。


📦 聚合(Aggregate)是关键!

在 DDD 中,Command 应该作用于聚合根(Aggregate Root),而不是直接作用于实体或值对象。

每个 Command 一般只针对一个聚合根,但这个聚合根内部可以包含多个实体或值对象。


🤔 那么什么时候会涉及多个聚合根?

有时候一个业务操作确实需要跨多个聚合根,比如:

场景:银行系统中的转账操作

Command:

class TransferMoneyCommand:
    def __init__(self, from_account_id, to_account_id, amount):
        self.from_account_id = from_account_id
        self.to_account_id = to_account_id
        self.amount = amount

对应的 Aggregates:

  • BankAccount(from_account)
  • BankAccount(to_account)

此时,一个命令确实会影响两个聚合根。

但在 DDD 中,这种情况建议:

  • 使用 Saga 模式Process Manager 来协调多个聚合的操作
  • 不要在单个 Command Handler 中同时修改多个聚合根的状态,避免并发冲突和一致性问题

✅ 最佳实践总结

规则 推荐做法
✅ 一个 Command 对应一个聚合根 更清晰、更安全
⚠️ 一个 Command 对应多个聚合根 可行,但需谨慎使用,建议用 Saga 协调
✅ 一个聚合根可包含多个实体/值对象 合理的设计方式
✅ Command 的粒度要与业务一致 不宜过粗也不宜过细

💡 类比生活

你可以把 Command 想象成“客户的一次请求”,而聚合根是“负责处理请求的核心责任人”。

举个例子:

  • 客户说:“我要订一份披萨。”(这是 Command)
  • 披萨店的“订单系统”(Order Aggregate)就负责协调:
    • 查库存(Product 实体)
    • 设置地址(Address VO)
    • 记录付款信息(Payment 实体)

所以一个命令最终可能涉及到多个子实体,但都属于同一个聚合根管理。


📌 总结一句话:

在 DDD 中,一个 Command 通常只作用于一个聚合根(Aggregate Root),但这个聚合根可以包含多个实体(Entity)和值对象(Value Object)。如果确实需要跨多个聚合根,应使用 Saga 或 Process Manager 来协调,而非直接在一个 Command 中处理。

posted @ 2025-07-09 08:05  向着朝阳  阅读(13)  评论(0)    收藏  举报