领域驱动设计:如何设计具有依赖关系的关系聚合
Posted
技术标签:
【中文标题】领域驱动设计:如何设计具有依赖关系的关系聚合【英文标题】:Domain-Driven Design: How to design relational aggregates with a dependency 【发布时间】:2017-03-21 21:04:56 【问题描述】:我的域名是关于项目管理的。我有一个程序(聚合根),它必须有一个客户(聚合根)。所以我在创建一个新程序时需要一个 CustomerID,因为我已经阅读了聚合应该只通过引用保持对其他聚合的引用。
这是我的业务规则:
客户可以随着时间的推移变得活跃和不活跃。
如果客户因某种原因被停用,则与该客户关联的所有程序也应停用。
如果客户处于非活动状态,则无法激活程序。
规则 #1 & #2 我已实施。难倒我的是#3。
我能想到 3 个解决方案:
程序持有对客户聚合的引用。
引入一个域服务来检查客户是否处于活动状态并将其传递给 Program.Activate(CustomerActiveCheckService 服务)。
让应用程序服务查找客户并将其传递给 Program.Activate(Customer customer)。
哪个是最好的解决方案?
更新
我看到@ConstaninGALBENU 和@plalx 的两种观点,我想提出一个折衷方案。我可以创建CustomerStatusChecker
服务吗?该方法将具有以下签名:CustomerStatus CheckStatus(CustomerID id);
然后我可以像这样传递Program
服务:`Program.Activate(CustomerStatusChecker service);
这个设计有什么问题吗?
【问题讨论】:
【参考方案1】:哪个是最好的解决方案?
没有最佳解决方案;有取舍。
但与要求 #2 和 #3 一致的一种可能的解决方案是,您现有的模型是错误的 - Program 实体不是孤立的聚合,而是 Customer 实体的一部分,因此应该由相同的控制聚合根。
提示可能是这种情况:程序的生命周期适合客户的生命周期;程序通常不会从一个客户迁移到另一个客户,每个客户的活动程序数量是有限的。
另一种可能性是需求是“错误的”。探索这一点的一种方法是查看活动/非活动是否是由模型做出的决定,或者它是否是在其他地方做出并报告给模型的决定。另一个是检查如果违反此“规则”对企业的成本。
如果模型没有立即发现客户,或者这是一个廉价的问题,那么您可能有一些空间来检测冲突并将其报告给人类,而不是试图让模型完成所有工作作品(参见:Greg Young,Stop Over Engineering)。
在这些情况下,让主代码路径可以很好地猜测,并实现操作员可以用来修复错误的替代路径。
在解决方案 #2 和 #3(我根本不喜欢 #1)之间进行选择时,我鼓励将 I/O 操作排除在模型之外。因此,除非您已经在内存中拥有最新版本的客户,否则我不喜欢将域服务作为选择。将客户状态的副本传递给域模型将 I/O 关注点保留在它们所属的应用程序组件中(有关此想法的更多信息,请参阅 Gary Bernhardt 的 Boundaries)。
【讨论】:
我不会太担心模型在这里出错。正如 Vaugn Vernon 所建议的那样,保持 AR 很小;业务需求可能会发生变化,较小的 AR 可以更好地应对变化。 关于设计的决定基于几件事。首先,程序负责维护随时间变化的需求。其次,如果我让客户成为 AR over Program,我必须对员工遵循相同的逻辑。那么我该如何处理一个跟踪哪些员工在哪些项目中的 ParticipantRoster 对象呢?【参考方案2】:解决方案 1:它违反了不持有对其他聚合实例的引用的规则。该规则确保在事务中只修改一个聚合。如果您需要在单个事务中修改多个聚合,那么您的设计肯定是错误的。
解决方案 2:我真的不喜欢在聚合中注入服务。我的聚合是纯函数,不涉及外部世界(I/O、存储库等)。
解决方案 3:在某种程度上等同于 1,即使它是临时引用(Program
可以在 Customer
上调用命令方法,从而在与 Program
相同的事务边界中修改 Customer
。
我的解决方案:在应用程序服务内部进行检查,然后调用 Program.activate ()
或将 customerStatus
传递给 Program.activate ()
并让 Program
聚合决定它是否引发异常或发出事件。
更新:
这个想法是您应该只将只读/不可变数据传递给Program
AR,以确保它不会修改其事务边界中的其他 AR。 另外,我们不应该让Program
依赖于它不需要的东西,就像整个Customer
AR。
另外,如果架构是事件驱动的,那么通过监听Customer
发出的正确事件,您可以使Program
AR 保持同步:如果尚未激活或停用它,则将其设为“不可激活”如果它已经被激活,例如使用Saga
。
【讨论】:
如果传递一个customerStatus,如何确保传递正确的customerStatus? 我不同意解决方案 #3 等同于 #1,因为很明显 AR 不是边界的一部分,而边界不属于 #1。实际上,Vaughn Vernon 在他的 IDDD 书中都推荐了(#2 和 #3)。 我想说#5 可能是在Program
中维护一个canBeActivated
标志,通过侦听CustomerActivated
和CustomerDeactivated
等域事件来保持最新。我想这几乎就是您停用 Program
实例的方式了?
@BrianGray 应用服务通过 id 从 repo 加载 正确 客户,然后访问 status
属性。这个想法是将只读值传递给Program
,这样它就不会尝试改变其他AR
@ConstantinGALBENU 你如何确保什么? Program
不会变异 Customer
?好吧,这将是一件很难做的事情,而且几乎必须是故意的,我怀疑程序员会故意引入错误。我可以问同样的问题,您如何确定Program
收到的状态属于正确的客户?以上是关于领域驱动设计:如何设计具有依赖关系的关系聚合的主要内容,如果未能解决你的问题,请参考以下文章
基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则