DDD在两个聚合中使用不变量修改每个事务的一个聚合

时间:2018-05-02 12:02:21

标签: domain-driven-design aggregateroot

假设我有一个聚合根租户和一个聚合根组织。倍数组织可以链接到单个租户租户只有组织 ID 在其中。

假设我在组织聚合中具有以下不变量:组织只能为特定产品类型订阅一个。

假设我在租户汇总中具有以下不变量:对于与租户相关的所有组织,只能存在一个产品类型的订阅。

我们如何使用每个事务聚合规则强制执行这些不变量? 在向组织添加订阅时,我们可以轻松验证第一个不变量,并触发域事件以更新(最终一致性)租户,但如果不变量会发生什么情况是否违反租户汇总?

是否暗示触发另一个域事件以回滚组织聚合中发生的事件?在成功修改第一个聚合之后,如果已将响应发送到UI,则似乎很棘手。

或者,这里真正的方法是在启动更新之前使用域服务来验证两个聚合的不变量吗?如果是这样,我们是否将不变量/规则直接放在域服务中,还是在聚合上放置一些布尔验证方法以保持逻辑?

更新 如果违反了一个不变量,UI必须阻止用户保存在UI中,该怎么办?在这种情况下,我们甚至都没有尝试更新聚合。

4 个答案:

答案 0 :(得分:4)

您可能想要考虑的一件事是您的域中缺少概念的可能性。您可能希望探索您的场景作为订阅计划概念的可能性,该概念本身就是一个聚合,并强制执行您当前试图放入租户/组织聚合中的所有这些规则。

当面对这样的情景时,我倾向于认为“如果没有任何系统可以促进这种操作,那么组织会怎么做”。在您的情况下,如果同一个租户中有多个人,每个人都对一个组织负责......他们如何同步他们的订阅以符合不变量?

在这样的练习中,您可能会达到一些已经探索过的场景:

  • 举行聚会活动(例如电话会议)以确保没有进行冗余订阅:这是域服务路径。

  • 每个人都自己订阅,然后相互通知,最终收回多余的费用:这就是事件+回滚路径。

  • 他们可能会妥协并保留一个共享分类帐,以便他们可以检查订阅如何进入公司范围,并且分类帐是此类决策的权威:这是缺少的聚合路径。

如果你强调这个问题,你可能会达到其他选择。

答案 1 :(得分:3)

  

我们如何使用每个交易规则的一个聚合来强制执行这些不变量?

有几个不同的答案。

一个是放弃“规则” - 将自己限制为每个事务的一个聚合并不重要。真正重要的是unit of work中的所有对象都存储在一起,因此事务是一个全有或全无的事件。

BEGIN TRANSACTION
    UPDATE ORGANIZATION
    UPDATE TENANT
COMMIT

此设计中的一个挑战是聚合不再描述存储的原子单位 - 此组织和此租户需要存储在同一个分片中的事实是隐式的,而不是显式的。

另一个是重新设计你的聚合 - 边界很难,而且我们的首选边界通常是错误的。 Udi Dahan在他的演讲Finding Service Boundaries中观察到(作为一个例子)与书籍 title 相关的域行为通常与书籍价格;它们是两个与常见事物有关的独立事物,但它们没有共同的规则。因此,它们可以被视为单独聚合的一部分。

因此,您可以重新设计组织/租户边界,以更正确地捕获它们之间的关系。因此,我们正确评估此规则所需的所有关系都在一个聚合中,因此必须存储在一起。

第三种可能性是接受这两个聚合是彼此独立的,而“不变”更像是指导而不是实际规则。这两个聚合就像协议中的参与者一样,我们不仅在协议中设计了幸福路径,还设计了失败模式。

这些协议的简单形式,我们有可逆行动来解决问题,被称为传奇。 Caitie McCaffrey在2015年就此发表了热烈的讨论,或者您可以阅读Clemens VastersBernd Rücker; Garcia-Molina和Salem在他们对long lived transactions的研究中引入了这个术语。

Process Managers是协调协议这个概念的另一个常用术语,你可能有一个比提交/回滚更复杂的状态图。

答案 2 :(得分:1)

我想到的第一个想法是拥有一个名为“tenantHasSubscription”的组织属性,该属性可以使用域事件进行更新。拥有此属性后,您可以在组织聚合中强制执行不变量。

答案 3 :(得分:1)

如果你想100%确定违反永远的不变量,那么所有命令SubscribeToProduct(TenantId, OrganizationId)必须由同一个聚合管理(可能是Tenant) ,内部有所有值来检查不变量 否则,为了进行操作,您将始终需要查询"外部"价值(从总体上看),这将引入"延迟"在打开窗口不一致的操作中。

如果你查询一个数据库有值,那么当结果在线上时,有人会更新它,因为数据库不会等你消耗你的读数以允许其他人修改它,因此您的聚合将使用陈旧数据来检查不变量。

显然这是一种极端主义,这并不意味着它肯定是危险的,但你必须计算失败发生的可能性,你怎么能在它发生时被警告,以及如何解决它(根据情况自动通过程序,或者手动干预)。