DDD:聚合中的延迟加载

Nan*_*ndo 5 c# domain-driven-design entity-framework-core

这几天我一直在学习 DDD,并努力理解聚合根的一些核心概念。也许有人可以给我推动正确的方向,并详细说明在这种情况下的最佳实践:

为了使这个示例不那么复杂,假设我们有一个包含两个实体的域:餐厅和营业时间。餐厅被定义为聚合根。

文本

根据我在所有在线示例中的理解(如果我错了,请纠正我),每次我需要聚合根的实例时,聚合根都会急切地加载所有子实体。因此,每当我想调用餐厅的方法时,都会加载所有营业时间(无论是否使用)。

在此示例中,我想验证当向该餐厅添加新时间时,营业时间不存在交集。在这种情况下,每隔一个开放时间的急切加载是有意义的,因为我需要将它们与现有的进行比较。

但是:这是一种限制,因为我知道每次我想添加另一个集合(比如餐厅图像)时,SQL 负载会越来越重,尽管大多数方法只需要其中一个集合。

我可以想到两种可能的解决方案:

延迟加载

通过实体框架属性代理延迟加载打开时间/子实体。因此,聚合根可以存在而无需急切加载它们,但只要需要它们就可以访问它们。然而,在我到处寻找答案时,我都认为聚合根中的延迟加载被认为是不好的做法。也许有人可以解释原因。

较小的聚合根

当然,我可以将开放时间本身定义为聚合根,但随后我需要将业务逻辑(在本例中为交叉点验证)置于模型之外。

在上面的所有示例中,我仅讨论命令端(而不是查询或序列化)。

也许我缺少一些基本的想法。在这个例子中应该如何组织聚合根以及为什么延迟加载被认为是不好的做法?

编辑 不知道为什么这个问题因为“基于意见”而被关闭。我要求最佳实践以及为什么延迟加载不在这种情况下。

pla*_*alx 6

该问题属于集合验证问题,根据领域需求有一些潜在的解决方案。

一致性强

  1. 创建 AR 来保护整个集合。在上面的示例中,如果此类集合具有合理的长度,您可以创建专用的RestaurantScheduleAR。您甚至可以将此类 AR 进一步划分为RestaurantWeekSchedule每周 1 个 AR。当您想要添加/删除开放日时,它将创建/加载给定周的 AR。另一种选择可能是调整 ORM 以仅加载集合的子集,例如schedule = scheduleRepo.loadForWeek(openingTime.week()); schedule.add(openingTime);。乐观锁定仍然允许检测冲突。

  2. 在数据库中强制执行规则。例如,如果您有一个关系数据库,您可能会将其OpeningTime作为 AR,然后使用唯一约束来防止违规。该规则最终会存在于域之外,但它可能是可以接受的。唯一性规则并不那么有趣。

最终一致性

处理集合验证问题的一种非常常见的方法是避免试图避免违规,并在事后通过手动或自动补偿操作来重点检测和纠正违规行为。OpeningTimeAdded这可以像显示重叠条目的异常报告一样简单,也可以更复杂,例如检查冲突并标记此类条目以进行更正的事件的单线程侦听器。


Far*_*sim 4

然而,在我到处寻找答案时,我都认为聚合根中的延迟加载被认为是不好的做法。

事实并非如此。实际的限制是延迟加载其他聚合,因为在聚合根处直接引用;建议改为引用其他聚合及其标识符。这个限制背后有很好的理由。

对聚合的引用不必要地增加了应用程序的内存占用(检索不会在事务中使用的实体);在高度并发的用例中,锁有效,会降低应用程序性能(不必要地锁定实体);妨碍数据分区(两个聚合需要在同一数据节点中处理)。

每个聚合根定义了自己的一致性边界;每笔交易都是为了确保一个聚合的一致性。通过域事件进行通信的跨聚合的更新操作(或事务)应该是最终一致的

如果您直接引用另一个聚合,从而需要延迟加载,并对它们执行更新,您应该重新考虑您的设计。

与往常一样,您的场景中的选择取决于业务上下文或其处理的领域。如果您认为OpeninigTime是一个单独的聚合,具有自己的一致性边界,则应仅保留其 id 并发布包含 id 的域事件,OpeningTime聚合将通过检索适当的聚合来处理这些事件。然而,如果情况并非如此(这似乎更有可能),您可能会保留引用、延迟加载并对其执行更新。