拆分聚合根以避免并发冲突
Split aggregate root to avoid concurrency conflicts
越来越多地了解聚合根,特别是来自 Vaughn Vernon 的 I-DDD,我提出了一个关于并发的问题。
一个系统被多个用户同时访问。 (当前)核心领域 大约 "enrolments" 一个教育中心,因此有一个 Student
实体代表业务的主要客户。 Student
是一个 聚合根 ,当然有很多信息。
假设一个 Student
有一个 PersonalAddress
和 AcademicInformation
(过去的学校,成绩,...)都建模为 值对象 在 Aggregate 中,并且 - 为了论证 - 在同一个 Aggregate[=65] 中没有其他 Entity =]. Student
Aggregate 在 Entity 中使用类似版本 属性 的乐观并发,因此对其数据的任何更改都会增加那个版本。
问题是,如果两个不同的用户试图同时修改同一个 Student
的 PersonalAddress
和 AcademicInformation
,其中一个尝试将会失败,即使地址和学术信息完全无关;虽然两者都是 VO,但它们属于同一个 Aggregate.
我想我可以拆分 Aggregate 以避免并发冲突,因为除了 [=89] 之外没有与 PersonalAddress
和 AcademicInformation
相关的真正不变量=] 到相同的 Student
。但是那些 VO 没有自己的身份。我将不得不创建另一个 Entity 并将它们放在不同的 Aggregate Roots 中,两者都包含与同一学生相关的信息,因此可以同时修改
所以问题是:
- 如何避免修改建模为值对象[=65]的相同实体(
Student
)的无关信息的并发冲突=] (PersonalAddress
, AcademicInformation
)?
- 是一个 "good approach"™,用于将
Student
聚合 拆分为两个或多个不同的 聚合根 ,正如我之前解释的那样?
- 即使这种特殊情况可以通过其他方式解决(如果分享,我将不胜感激),这个问题是如何从更普遍的角度解决的?
我认为一切都取决于用户尝试并发修改信息的频率,并据此决定拆分聚合是否值得...但我不知道。
非常感谢。
1) 您可以通过其他方式使用乐观并发:基于旧值,而不是版本号。例如。改变学生的命令应该看起来像 new ChaneStudentCmd { StudentId = ...;旧地址街 = "xxx"; NewAddressStreet = "yyy" },实施应确保在更改之前当前的 strret 为 "xxx"。如果不是 "xxx" 应该抛出并发异常。
2) 我认为没有理由拆分聚合。
3) 一般方法可以使用更具体的更新命令,而不是简单的 "update all student properties"。业务层应该有关于那个确切的用户想要更新的信息。有了这些信息,考虑到并发性和其他需求,它将能够优雅地处理更新。
在我看来你可以保持原样,这是最简单的方法。但是,将不相关的属性或方法保留在一个 Aggregate/Entity/Class 中会违反 "high cohesion" 规则。
所以我想知道你的情况是否是 "smell" 未被发现的限界上下文。 PersonalAddress
和 AcademicInformation
属于同一个限界上下文吗?我不知道你的领域和用例,但你应该考虑一下。
回答您的问题:
广告 1. 通过将不相关的信息分离到不同的限界上下文或聚合中来避免冲突。但是,相关信息仍然可能存在并发冲突。
广告 2。"good approach" 正确地对限界上下文和聚合建模(尽管这并不容易;))。因此并发冲突和不相关的信息是 "smells",这让您知道您错过了模型中的某些内容。
广告 3。同样,正确的限界上下文和聚合建模。
我并不是说在任何情况下都有办法通过分离 BC 和聚合来避免您遇到的情况,但我确实认为这是可能的。我并不是说,在一个聚合中没有不相关的属性和方法总是正确的,但这是 "high cohesion" 规则引导我去的地方。
越来越多地了解聚合根,特别是来自 Vaughn Vernon 的 I-DDD,我提出了一个关于并发的问题。
一个系统被多个用户同时访问。 (当前)核心领域 大约 "enrolments" 一个教育中心,因此有一个 Student
实体代表业务的主要客户。 Student
是一个 聚合根 ,当然有很多信息。
假设一个 Student
有一个 PersonalAddress
和 AcademicInformation
(过去的学校,成绩,...)都建模为 值对象 在 Aggregate 中,并且 - 为了论证 - 在同一个 Aggregate[=65] 中没有其他 Entity =]. Student
Aggregate 在 Entity 中使用类似版本 属性 的乐观并发,因此对其数据的任何更改都会增加那个版本。
问题是,如果两个不同的用户试图同时修改同一个 Student
的 PersonalAddress
和 AcademicInformation
,其中一个尝试将会失败,即使地址和学术信息完全无关;虽然两者都是 VO,但它们属于同一个 Aggregate.
我想我可以拆分 Aggregate 以避免并发冲突,因为除了 [=89] 之外没有与 PersonalAddress
和 AcademicInformation
相关的真正不变量=] 到相同的 Student
。但是那些 VO 没有自己的身份。我将不得不创建另一个 Entity 并将它们放在不同的 Aggregate Roots 中,两者都包含与同一学生相关的信息,因此可以同时修改
所以问题是:
- 如何避免修改建模为值对象[=65]的相同实体(
Student
)的无关信息的并发冲突=] (PersonalAddress
,AcademicInformation
)? - 是一个 "good approach"™,用于将
Student
聚合 拆分为两个或多个不同的 聚合根 ,正如我之前解释的那样? - 即使这种特殊情况可以通过其他方式解决(如果分享,我将不胜感激),这个问题是如何从更普遍的角度解决的?
我认为一切都取决于用户尝试并发修改信息的频率,并据此决定拆分聚合是否值得...但我不知道。
非常感谢。
1) 您可以通过其他方式使用乐观并发:基于旧值,而不是版本号。例如。改变学生的命令应该看起来像 new ChaneStudentCmd { StudentId = ...;旧地址街 = "xxx"; NewAddressStreet = "yyy" },实施应确保在更改之前当前的 strret 为 "xxx"。如果不是 "xxx" 应该抛出并发异常。
2) 我认为没有理由拆分聚合。
3) 一般方法可以使用更具体的更新命令,而不是简单的 "update all student properties"。业务层应该有关于那个确切的用户想要更新的信息。有了这些信息,考虑到并发性和其他需求,它将能够优雅地处理更新。
在我看来你可以保持原样,这是最简单的方法。但是,将不相关的属性或方法保留在一个 Aggregate/Entity/Class 中会违反 "high cohesion" 规则。
所以我想知道你的情况是否是 "smell" 未被发现的限界上下文。 PersonalAddress
和 AcademicInformation
属于同一个限界上下文吗?我不知道你的领域和用例,但你应该考虑一下。
回答您的问题:
广告 1. 通过将不相关的信息分离到不同的限界上下文或聚合中来避免冲突。但是,相关信息仍然可能存在并发冲突。
广告 2。"good approach" 正确地对限界上下文和聚合建模(尽管这并不容易;))。因此并发冲突和不相关的信息是 "smells",这让您知道您错过了模型中的某些内容。
广告 3。同样,正确的限界上下文和聚合建模。
我并不是说在任何情况下都有办法通过分离 BC 和聚合来避免您遇到的情况,但我确实认为这是可能的。我并不是说,在一个聚合中没有不相关的属性和方法总是正确的,但这是 "high cohesion" 规则引导我去的地方。