拆分聚合根以避免并发冲突

Split aggregate root to avoid concurrency conflicts

越来越多地了解聚合根,特别是来自 Vaughn Vernon 的 I-DDD,我提出了一个关于并发的问题。

一个系统被多个用户同时访问。 (当前)核心领域 大约 "enrolments" 一个教育中心,因此有一个 Student 实体代表业务的主要客户。 Student 是一个 聚合根 ,当然有很多信息。

假设一个 Student 有一个 PersonalAddressAcademicInformation(过去的学校,成绩,...)都建模为 值对象 Aggregate 中,并且 - 为了论证 - 在同一个 Aggregate[=65] 中没有其他 Entity =]. Student AggregateEntity 中使用类似版本 属性 的乐观并发,因此对其数据的任何更改都会增加那个版本。

问题是,如果两个不同的用户试图同时修改同一个 StudentPersonalAddressAcademicInformation,其中一个尝试将会失败,即使地址和学术信息完全无关;虽然两者都是 VO,但它们属于同一个 Aggregate.

我想我可以拆分 Aggregate 以避免并发冲突,因为除了 [=89] 之外没有与 PersonalAddressAcademicInformation 相关的真正不变量=] 到相同的 Student。但是那些 VO 没有自己的身份。我将不得不创建另一个 Entity 并将它们放在不同的 Aggregate Roots 中,两者都包含与同一学生相关的信息,因此可以同时修改

所以问题是:

  1. 如何避免修改建模为值对象[=65]的相同实体Student)的无关信息的并发冲突=] (PersonalAddress, AcademicInformation)?
  2. 是一个 "good approach"™,用于将 Student 聚合 拆分为两个或多个不同的 聚合根 ,正如我之前解释的那样?
  3. 即使这种特殊情况可以通过其他方式解决(如果分享,我将不胜感激),这个问题是如何从更普遍的角度解决的?

我认为一切都取决于用户尝试并发修改信息的频率,并据此决定拆分聚合是否值得...但我不知道。

非常感谢。

1) 您可以通过其他方式使用乐观并发:基于旧值,而不是版本号。例如。改变学生的命令应该看起来像 new ChaneStudentCmd { StudentId = ...;旧地址街 = "xxx"; NewAddressStreet = "yyy" },实施应确保在更改之前当前的 strret 为 "xxx"。如果不是 "xxx" 应该抛出并发异常。

2) 我认为没有理由拆分聚合。

3) 一般方法可以使用更具体的更新命令,而不是简单的 "update all student properties"。业务层应该有关于那个确切的用户想要更新的信息。有了这些信息,考虑到并发性和其他需求,它将能够优雅地处理更新。

在我看来你可以保持原样,这是最简单的方法。但是,将不相关的属性或方法保留在一个 Aggregate/Entity/Class 中会违反 "high cohesion" 规则。 所以我想知道你的情况是否是 "smell" 未被发现的限界上下文。 PersonalAddressAcademicInformation 属于同一个限界上下文吗?我不知道你的领域和用例,但你应该考虑一下。

回答您的问题:

广告 1. 通过将不相关的信息分离到不同的限界上下文或聚合中来避免冲突。但是,相关信息仍然可能存在并发冲突。

广告 2。"good approach" 正确地对限界上下文和聚合建模(尽管这并不容易;))。因此并发冲突和不相关的信息是 "smells",这让您知道您错过了模型中的某些内容。

广告 3。同样,正确的限界上下文和聚合建模。

我并不是说在任何情况下都有办法通过分离 BC 和聚合来避免您遇到的情况,但我确实认为这是可能的。我并不是说,在一个聚合中没有不相关的属性和方法总是正确的,但这是 "high cohesion" 规则引导我去的地方。