聚合根中覆盖的实体如何保存在 DDD 中?

Posted

技术标签:

【中文标题】聚合根中覆盖的实体如何保存在 DDD 中?【英文标题】:How entities covered with in an aggregate Root are saved in DDD? 【发布时间】:2020-06-11 20:33:32 【问题描述】:

阅读大量帖子后,我意识到如果某个概念/上下文存在聚合根,我们需要为整个概念/上下文创建一个存储库。

如果是这样,我认为内部实体不会有任何存储库。如果是,这些内部实体如何保存到数据库中?

我在聚合根目录下有许多内部实体。所以,想知道如果我需要在聚合根存储库下保存所有内部实体,它会变得臃肿。请建议在这种情况下可以做什么。

此外,我的内部实体将在持久性级别访问它们自己的每个表。如果不允许我以这种方式存储内部实体,请纠正我。

示例 考虑我有一家餐厅作为聚合根。它可以对名为 Review 的实体进行分组。评论存在于餐厅,没有评论就无法存在。

如果评论是一个内部实体,并且餐厅可以有许多评论,评论将保存在单独的表中。但由于餐厅聚合根只有一个餐厅存储库,因此如何/在哪里处理保存评论。

【问题讨论】:

【参考方案1】:

如果是这样,我认为内部实体不会有任何存储库。如果是,这些内部实体如何保存到数据库中?

聚合表示一致性边界。如果一个实体由一个聚合拥有并且需要确保一致性,那么它应该与聚合根一起持久化。

所以,想知道我是否需要将所有内部实体的保存都保存在聚合根存储库下,它会变得臃肿。

是的,会的。如果这是一个真正的问题,您可以考虑使用 ORM。 ORM 将在内存中维护以聚合根为根的图,并确保在必要时保留更改。

此外,我的内部实体将在持久性级别访问它们自己的每个表。如果不允许我以这种方式存储内部实体,请纠正我。

尝试将您的域与您的持久性策略分开考虑。您有一个映射到关系模式的域模型。关系模式不应驱动域的设计。

【讨论】:

确实,我们不应该与持久性策略混用。但是只有一个存储库来处理聚合根中所有实体的持久性。它是如何实际处理的?所述示例(餐厅/评论)的任何伪代码都会有所帮助。【参考方案2】:

我想补充 CPerson 的回答。

这种不深入上下文的讨论通常是毫无意义的。每个案例都是特定于上下文的,很少归结为持久性问题。

在上述情况下,我绝不会将 Review 建模为 Restaurant 聚合中的实体。这不是持久性问题,而是建模问题。

那里有几本 DDD 书籍,我相信阅读几篇博客文章不足以同时了解 DDD 的战略和战术模式。其中一种模式确实是聚合模式。简而言之,聚合是一致性边界。聚合始终是特定于上下文的(就像其他任何东西一样)。

如果您正在为餐厅管理系统或送餐系统建模,评论可能会在单独的上下文中。没有像“餐厅环境”这样的环境。这就是有界上下文模式的重点。在您的示例中,它可能是餐厅评论上下文。评论发生的情况与食物、营业时间和餐桌预订无关。

如果您正在为 TripAdviser 之类的东西建模,那么基本上您只有评论。在这种情况下,评论或多或少与正在评论的内容无关。那么,你的模型就完全不同了。

评论的数量不断增长,因此将所有评论作为实体进行汇总并没有多大意义。同样,聚合是一致性边界。如果另一条评论是一星,您会说无法发布评论吗?我不这么认为。关于评论,您试图在 Restaurant 聚合中保护的不变量是什么?您是否需要限制评论数量以根据这些评论更改餐厅的状态?我不认为是这样。因此,评论本身可以是一个汇总,因为所有评论都完全相互独立。

评论聚合中的餐厅可以是一个包含餐厅 ID 的简单值对象。在构建此值对象时,您将确保给定的餐厅确实存在并开放供评论。当餐厅消失时,您确实需要清除评论。但它也是特定于上下文的。餐厅可能会关门,但您仍然保留评论。

【讨论】:

我还不清楚,但我需要再次查看您的答案。请考虑在这个例子中,评论是除了餐厅之外没有外部依赖的实体。这可能是一个不好的例子,但请考虑这个约束。根据您的回答,这是否意味着聚合从不包含其他实体而只包含值对象?如果不是,谁负责存储这些实体?我的问题是我们是否也需要使用代码重载聚合根存储库以保存这些内部实体? 如果您想要另一个示例,请考虑标准示例。订单和行项目。这里考虑到 Line Items 的数量可能很大。 Line Item 将有一个唯一的 Id 并且不是一个值对象,而是它自己的一个实体。现在保存此订单项的代码应该在哪里?我是否需要使用订单项保存代码重载订单存储库? 这意味着聚合从不包含其他实体,而只包含值对象:不,我的意思是在那种特殊情况下它可能相反。评论 评论是一个汇总,你有很多。每条评论都可以将餐厅的引用作为价值对象。餐厅本身就是另一个集合体。 如果您想要另一个示例,请考虑标准示例。订单和行项目。在这里考虑 Line Items 的数量可能很大。:再次,这是一个理论示例,DDD 的第一条规则是为特定业务领域制定模型。每个企业都是不同的,因此模型也不同。聚合的规则是相同的。聚合是事务一致性边界。没有外部参考,它总是有价值的。而已。持久性在这里没有任何作用(当然交易除外)。 好的,为了方便起见,你能给我举一个 Aggregate 可以有实体的例子吗?可能我错过了一些东西。根据您的文字,它看起来像“如果一个实体将成为一个大列表,它总是最终成为一个单独的聚合根。”。但我在想,如果该特定实体单独没有任何意义,但仅在另一个实体组(聚合)下有价值。【参考方案3】:

我同意其他答案中提出的一些观点,即您可能不想那样建模,但我认为您的问题本身尚未得到回答。举个例子,它完全没问题。

所以这里是:

您不会为实体创建存储库(如在 DDD 存储库中),只为聚合根创建。 所以在你的例子中,应用层可能是这样的:

Restaurant restaurant = restaurantRepository.findById(23)
restaurant.addReview(review)
restaurantRepository.save(restaurant)

还要注意,操作是在聚合根上完成的。 所以一般来说,你的应用层中的命令通常是:加载聚合,对其执行操作,保存它

聚合被保存为一个整体和实体。 存储库背后发生的事情取决于您的基础架构。当然,由于存储库属于域,我们不关心那里的基础设施。

那么基础设施层中的实现(这些只是可能的一些示例):

    在 documentDB 中,您将拥有一个存储库实现,它只是将文档作为一个整体保存到 DB 中。 (文档存储和 DDD 在这里非常匹配,因为聚合 = 文档) 如果您要在一个简单的文本文件中将聚合序列化到磁盘,您可能有一个存储库实现,每个实体都有一个序列化器,值对象来执行序列化。

    如果您有一个 RDBMS,您确实希望将实体存储在它们自己的表中。在这里你可以使用:

    一个ORM框架,只保存根实体,让框架自动保存/删除/更新子实体 使用 DAO 模式并为每个表/实体创建一个 DAO ...

也看看这个question

此外,在某些情况下,您可能会仅出于技术原因拆分聚合。如果您要拥有一个包含大量实体的聚合,并且处理它变得很重,那么您可能希望纯粹出于技术原因将其拆分为两个聚合。但这是你尽量避免的事情,并且只在真正必要时才这样做。

【讨论】:

这就是我所期待的!感谢您理解所述问题。所以在这里,我可以将 addReview 添加到 Restaurant 存储库,并且在内部我有一个 DAO 可以将评论写入单独的表/集合(NOSQL),对吗? addReview 不是存储库方法,它是对餐厅聚合的操作。操作之后,通过存储库上的 save 方法再次保存整个聚合餐厅。在那个界面后面,你确实可以有一个特定的 DAO 来保存评论对象本身

以上是关于聚合根中覆盖的实体如何保存在 DDD 中?的主要内容,如果未能解决你的问题,请参考以下文章

使用实体框架从聚合根中删除子记录

EF 代码优先:如何在遵循 DDD 时从实体的集合中删除一行?

从聚合根中删除子

DDD中聚合聚合根的含义以及作用

DDD 在一个聚合中的实体之间以一对多而不是一对多的聚合边界

领域驱动设计 DDD的一些基础概念