Nor*_*ard 4 domain-driven-design aggregateroot
假设我有一个聚合根Tenant和一个聚合根Organization。多个组织可以链接到单个租户。承租人只有在标识中的组织在它的聚合。
假设我在组织聚合中有以下不变量:组织只能有一个特定产品类型的订阅。
假设我在租户聚合中有以下不变量:与租户相关的所有组织中必须只存在一个产品类型订阅。
我们如何使用每个交易规则的一个聚合来强制执行这些不变量?在向Organization添加订阅时,我们可以轻松验证第一个不变量,并触发域事件以更新(最终一致性)租户,但是如果不变量在租户聚合中被违反会发生什么?
这是否意味着触发另一个域事件以回滚组织聚合中发生的事情?在成功修改第一个聚合后将响应发送到 UI 的情况下,这似乎很棘手。
或者这里的真正方法是在启动更新之前使用域服务来验证两个聚合的不变量?如果是这样,我们是将不变量/规则直接放在域服务中,还是将某种布尔验证方法放在聚合上以保持逻辑在那里?
更新 如果 UI 必须阻止用户在违反一个不变量的情况下保存在 UI 中怎么办?在这种情况下,我们甚至不会尝试更新聚合。
您可能需要考虑的一件事是您的领域中可能缺少一个概念。您可能想探索您的方案是否有可能将某些内容作为订阅计划概念,它本身就是一个聚合并强制执行您当前尝试放入租户/组织聚合中的所有这些规则。
面对这样的场景时,我倾向于对自己思考“如果根本没有促进这种操作的系统,组织会做什么”。在您的情况下,如果来自同一个租户的多个人,每个人都负责一个组织......他们将如何同步他们的订阅以符合不变量?
在这样的练习中,您可能会遇到一些已经探索过的场景:
举办聚会活动(例如电话会议)以确保没有进行多余的订阅:这就是域服务路径。
每个人都进行自己的订阅并相互通知,最终对多余的订阅收费:这就是事件 + 回滚路径。
他们可能会妥协并保留一个共享的分类帐,在那里他们可以检查订阅在公司范围内的进展情况,并且分类帐是此类决策的权威:这是缺少的聚合路径。
如果你足够强调这个问题,你可能会找到其他选择。
我们如何使用每个交易规则的一个聚合来强制执行这些不变量?
有几个不同的答案。
一种是放弃“规则”——将自己限制为每笔交易的一个聚合并不重要。真正重要的是工作单元中的所有对象都存储在一起,因此事务是一个全有或全无的事件。
BEGIN TRANSACTION
UPDATE ORGANIZATION
UPDATE TENANT
COMMIT
Run Code Online (Sandbox Code Playgroud)
这种设计的一个挑战是聚合不再描述存储的原子单元——这个组织和这个租户需要存储在同一个分片中的事实是隐含的,而不是明确的。
另一个是重新设计你的聚合——边界很难,而且通常情况下,我们对边界的第一选择是错误的。Udi Dahan 在他的演讲“ Finding Service Boundaries”中观察到(例如)与书名相关的领域行为通常与书价关系不大或没有关系;它们是两个独立的事物,与一个共同的事物有关系,但它们没有共同的规则。因此,它们可以被视为单独聚合的一部分。
因此,您可以重新设计组织/租户边界,以更准确地捕捉它们之间的关系。因此,我们需要正确评估此规则的所有关系都在一个聚合中,因此必须存储在一起。
第三种可能是接受这两个聚合是相互独立的,“不变量”更像是一个指导方针,而不是一个实际的规则。这两个聚合体就像一个协议的参与者,我们在协议中不仅设计了快乐路径,还设计了失败模式。
这些协议的简单形式,我们有可逆的动作来解决问题,被称为传奇。 凯蒂·麦卡弗里( Caitie McCaffrey ) 在 2015 年对此发表了广受好评的演讲,或者您可以阅读Clemens Vasters或Bernd Rücker;Garcia-Molina 和 Salem 在他们对长期交易的研究中引入了这个术语。
进程管理器是协调协议这一概念的另一个常用术语,其中您可能拥有比提交/回滚更复杂的状态图。