【问题标题】:Can entities inside the aggregate be accessible or visible externally to the aggregate?聚合内部的实体可以在聚合外部访问或可见吗?
【发布时间】:2018-12-20 10:16:43
【问题描述】:

我是 DDD 的新手,我的问题对你们中的许多人来说可能是微不足道的。

考虑学生和课程的情况。

只有当学生的年龄高于注册该课程所需的最低年龄时,该学生才能注册该课程。

在我看来,Student 和 Course 可以被视为聚合,其中 Student 是根实体,Course 是子实体,age 是要尊重的不变量。

Student 应该有一个 Student.SubscribeTo(Course course) 方法,并且该方法应该强制执行不变量 Student.Age >= Course.MinAge,否则会生成异常。

这在 DDD 方法中是否正确?或者我应该只将 CourseId 传递给 SubscribeTo? Student.SubscribeTo(int CourseId)

从我的角度来看,如果没有办法打破不变量,则应该允许从外部访问 Course 到聚合。如果我在代码的其他一些地方更改 Course.MinAge,我不会破坏我的业务需求,因为我希望仅在订阅课程时尊重年龄,并且我不介意以后 Course.MinAge 是否更改。

如果业务需求状态不同:当 Course.MinAge 更改时,如果 Student.Age < Course.MinAge,则应将已注册课程的学生从课程中删除。

【问题讨论】:

  • 如果学生验证他的年龄,这很奇怪。在现实世界中,执行它的人不是学生本人。另外,如果你的根是student,没有人应该直接引用course,所有都应该通过根来完成,所以Student.SubscribeTo(Course course)可能不起作用。
  • 好的,所以你认为关系应该倒置?课程是根和studend孩子吗?
  • 好吧,老实说,我没有具体的答案。另外,我猜该课程需要执行一些任务,例如列出课程中的所有学生,并按照您的指定,在minAge 更改时删除学生。但是学生可能想要列出他所有的课程。也许聚合将是一些CourseStudentAggregator,这将是所有相关操作的业务逻辑。我不知道是不是ddd

标签: design-patterns architecture domain-driven-design software-design


【解决方案1】:

我认为您的汇总不正确。 Course 实体可以独立存在,它不是 Student 实体的子实体。课程有自己的生命周期:例如如果学生离开学校,课程将继续存在。课程 ID 不依赖于学生 ID。学生可以持有课程 ID,但它们是不同的聚合。

无论如何,对于您将课程 ID 传递给“student.subscribeTo”方法的问题(如果它们是一个聚合),答案是否定的,您不能将子实体的 ID 传递给聚合的操作,因为子实体在聚合之外没有已知的全局身份。他们在聚合中具有本地 ID。

更新:

由于 Course 和 Student 是两个聚合体,因此“学生的年龄必须高于注册课程所需的最低年龄”这一规则不是一成不变的。为什么?因为不变量是关于聚合状态的业务规则,所以它必须始终在事务上保持一致。聚合定义了事务一致性边界。

因此,该规则只是学生订阅课程时必须检查的验证规则(“student.subscribeTo”方法)。由于聚合不应使用存储库,因此您可以将域服务传递给该方法,并且学生聚合将双重分派到域服务以便从课程 ID 获取课程。

查看 Vaughn Vernon 的红皮书 IDDD 的聚合章节(第 361-363 页)或同一作者的文章:

http://www.informit.com/articles/article.aspx?p=2020371&seqNum=4

希望对你有帮助。

【讨论】:

  • 好的我同意学生不应该是聚合的根,可能是课程应该是根。我不明白您是否将两个实体分开以强制执行不变量。
  • 在我看来当然也不是根。学生本身也是一个实体。它不是 Course 聚合中的子项。如果您删除学生实体保持存在的课程(或课程完成),则学生生命周期不依赖于课程生命周期。学生和课程是两个不同的聚合。请参阅我更新的答案,其中我解释了您对不变量的不了解
【解决方案2】:

我也在学习 DDD,前几天我问了一个类似的问题,你可以找到 here

我了解到,唯一真正的答案是:这取决于本身没有正确或错误的方法,一切都必须为业务问题及其解决方案服务。虽然,有一些指导方针和经验法则可能非常有用,例如:

  • 不要模拟现实。领域模型是一种抽象,旨在解决特定问题。
  • 不要基于数据关系建模。每个关联都必须在那里执行规则或不变量,而不是因为这些对象在现实生活中是相关的。根据行为开始建模。
  • 更喜欢小聚合体。
  • 首选同时修改单个聚合(用例/事务),并使用最终一致性来更新其他聚合。
  • 仅按身份在聚合之间建立关联。

我认为您的方案中的问题是缺少很多内容。谁拥有该协会,为什么?是否有任何其他用例同时涵盖学生和课程?为什么你用student.SubscribeTo(course) 而不是course.enroll(student)?请记住,DDD 的目标是处理复杂的域逻辑,因此当接近模型的 写入端 时它会发光,读取端 可以通过多种不同的方式完成,而无需 put模型中有很多关联。

正如您所说,仅验证年龄可能不是不变的,需要进行大量聚合:

如果我在代码的其他一些地方更改Course.MinAge,我不会破坏我的业务需求,因为我希望仅在订阅课程时才尊重年龄,并且我不介意以后Course.MinAge变化。

那么没有理由强制StudentCourse 始终保持一致(在这个特定的上下文/场景中),也没有必要让它们成为同一聚合的一部分。如果您需要执行的唯一规则是Student.Age >= Course.MinAge,您可以坚持一个简单的:

Student.SubscribeTo(Course course)

其中StudentCourse 不是同一聚合的一部分。没有什么反对加载两个不同的聚合并在同一个事务中使用它们,只要你只修改一个。 (好吧,也没有什么反对在同一个事务中修改两个聚合,这只是一个经验法则,但在这种情况下你可能不需要破坏它。

这里Student.SubscribeTo 将执行有关年龄的规则。我不得不说,让Student 验证自己的年龄听起来很“奇怪”,但也许它在你的模型中恰到好处(记住,不要模拟现实,模拟解决方案)。结果,Student 将拥有一个新的状态来保存课程的身份,Course 将保持不变。

如果业务需求说明不同情况:当 Course.MinAge 更改时,如果 Student.Age < Course.MinAge,则应从课程中删除已注册该课程的学生。

在这里,您必须先回答一些问题(在领域专家的帮助下):为什么要删除它们?是否应该立即删除?如果他们当时正在上课怎么办?学生被移除意味着什么?

可能在域中没有真正需要在更改 MinAge 的同时删除学生(当操作仅在所有发生时才被认为成功,如果没有,没发生什么事)。学生可能需要进入一个最终可以解决的新状态。如果是这种情况,那么您也不需要使它们成为同一聚合的一部分。

不出所料地回答标题中的问题:这取决于。拥有聚合的全部原因是为了保护一组以某种方式相关的实体的不变量。聚合不是HAS-A 关系(不一定)。如果必须保护跨越多个实体的不变量,可以将它们设为聚合并选择一个实体作为聚合根;该根是修改聚合的唯一访问点,因此始终强制执行每个不变量。允许直接引用聚合内的实体会破坏这种保护:现在您可以在不知道根的情况下修改该实体。由于无法从外部访问聚合内部的实体,因此这些实体只有本地标识,并且它们作为没有对根的引用的独立对象没有意义。不过,可以向根询问聚合内的实体。

可以有时,将对内部实体的引用传递给另一个聚合,只要它是临时引用并且没有人在聚合根之外修改该引用。然而,这使模型变得混乱,边界开始变得模糊。更好的方法是传递该实体的副本,或者更好的事件,传递该实体的不可变视图(可能是值对象),因此无法破坏不变量。如果您认为通过传递对内部实体的引用不会破坏不变量,那么也许没有理由从聚合开始。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-13
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多