关于域驱动设计:我是否允许DDD中包含“不完整”的聚合?

关于域驱动设计:我是否允许DDD中包含“不完整”的聚合?

Am I allowed to have “incomplete” aggregates in DDD?

DDD指出,您只能通过实体的聚合根访问实体。举例来说,假设您有一个聚合根X,该根X可能有很多子Y实体。现在,对于某些情况,您一次只真正关心这些Y实体的子集(也许您正在将它们显示在页面列表中或其他内容中)。

然后可以实现存储库,这样在这种情况下它返回不完整的聚合吗?就是一个X对象,其Ys集合仅包含我们感兴趣的Y实例,而并非全部?例如,这可能导致X上执行某些涉及Y的计算的方法的行为不符合预期。

这是否表明有问题的Y实体应被视为提升为总根?

我目前的想法(在C#中)是利用LINQ的延迟执行,以便我的X对象具有一个IQueryable来表示其与Y的关系。这样,我就可以进行透明的延迟加载并进行过滤...但是要使它起作用使用ORM(在我的情况下为Linq to Sql)可能有点棘手。

还有其他聪明的主意吗?


我认为具有许多子实体的聚合根是代码气味,或者,如果可以的话,是DDD气味。 :-)通常,我看两个选项。

  • 将您的聚合拆分为许多较小的聚合。这意味着我的原始设计不是最佳的,我需要确定一些新实体。
  • 将您的域划分为多个有界上下文。这意味着,存在特定的场景组使用聚合中实体的公共子集,而其他场景组则使用不同的子集。

  • 吉米·尼尔森(Jimmy Nilsson)在他的书中暗示,您可以阅读其中一部分的快照,而不必阅读完整的汇总。但是,您不应将快照类中的更改保存到数据库。

    吉米·尼尔森(Jimmy Nilsson)的书第6章:为基础架构做准备-查询。 226。

    快照模式


    您实际上是在问两个重叠的问题。

  • 您的问题的标题和前半部分是哲学/理论上的。我认为仅通过实体的"聚合根"访问实体的原因是抽象化了您所描述的实现细节。通过聚合根进行访问是一种通过拥有受信任的访问点来降低复杂性的方法。您通过遵守约定来消除摩擦/歧义/不确定性。它在根中的实现方式无关紧要,您只知道当您请求一个实体时它就会存在。我不认为这种观点会排除您所描述的"过滤存储库"。但是要为开发人员提供成功的机会,应该在没有明确说明其"过滤性"的情况下实例化存储库。同样,如果可以对存储库实例进行共享访问,则在调用方进行编码时应明确"过滤"。

  • 问题的后半部分是关于在特定平台上的实现。不知道为什么要提到延迟执行,我认为这与过滤问题正交。使用LINQ进行过滤本身可能有些棘手。也许不是内联lambda,而是设置它们的集合,然后根据需要的过滤器选择一个。


  • Is it OK to implement a repository then, so that in such scenarios it
    returns an incomplete aggregate?

    一点也不。聚合是改变系统状态的跨国边界。切勿使用聚合查询数据。将系统分为写入和读取两面。 (了解有关CQR和CQRS的信息)。当我们认为基于" CRUD"时,我们会基于某些资源来实施我们的系统。假设您有"约会"合计。认为" Crudish"意味着我们应该实现用例Create,Update,Delete,GetAll约会。这意味着应该为GetAll返回Appointment []。当您认为基于用例(六角形体系结构)时,您的用例将是ScheduleAppointment,RescheduleAppointment,CancelAppointment。但对于查询方面,它可以是:/ myCalendar。我们返回ClientCalendar对象中特定用户的所有约会。为查询端创建单独的DTO。切勿为此目的使用聚合。


    允许这样做,因为代码仍然可以编译,但是如果要使用纯DDD设计,则不应有不完整的对象实例。

    如果您害怕加载一个庞大的对象,而您只会使用其子实体的一小部分,则应该研究LazyLoading。

    LazyLoading将您决定延迟加载的所有内容的加载延迟到访问它们的那一刻。一旦代码调用它们,它们便使用回调来调用加载方法。


    推荐阅读