【问题标题】:Am I allowed to have "incomplete" aggregates in DDD?我可以在 DDD 中有“不完整”的聚合吗?
【发布时间】:2010-09-06 16:55:22
【问题描述】:

DDD 声明您应该只通过它们的聚合根访问实体。例如,假设您有一个聚合根 X,它可能有 lot 个子 Y 实体。现在,在某些情况下,您一次只真正关心这些 Y 实体的一个子集(也许您将它们显示在分页列表或其他内容中)。

那么是否可以实现一个存储库,以便在这种情况下它返回一个 incomplete 聚合? IE。一个 X 对象,它的 Ys 集合只包含我们感兴趣的 Y 实例,而不是 所有 个?例如,这可能会导致 X 上执行一些涉及 Ys 的计算的方法无法按预期运行。

这是否可能表明所讨论的 Y 实体应被视为提升为聚合根?

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

还有其他聪明的想法吗?

【问题讨论】:

    标签: domain-driven-design


    【解决方案1】:

    我认为具有 很多 个子实体的聚合根是代码异味,或者如果您愿意的话,是 DDD 异味。 :-) 通常我会考虑两个选项。

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

    【讨论】:

    • 这可能是代码异味,但也可能不是。我有同样的问题。我有一个包含地理航点的 Trace 类。现在这条轨迹可以任意长,虽然轨迹本身是一个实体,但航点不是。它们是价值对象。因此,如果我想更新纯 DDD 意义上的 Trace,我将不得不加载完整的聚合根 Trace 及其所有航点,添加一些航点,然后再次存储完整的 Trace。这显然不适用于大迹线。因此,由于性能问题,我可能会面临允许部分加载的聚合根...
    • 我意识到这个答案已经有 11 年历史了......但我相信应该根据领域专家的说法对他们的聚合进行建模。因此,如果有人误解了领域专家所说的话,应该将一个巨大的聚合体改造成更小的,以更好地匹配领域专家的愿景。以防万一,一个巨大的聚合与领域专家的心智模型一致——正式来说,DDD 要求它是巨大的。运气不好,我猜。实际上,Eric Evans 在一次演讲中提到了这个问题和可能的解决方案:youtu.be/lE6Hxz4yomA?t=1778
    【解决方案2】:

    Jimmy Nilsson 在他的书中暗示,您可以阅读其中一部分的快照,而不是阅读完整的汇总。但是您不应该能够将快照类中的更改保存到数据库中。

    Jimmy Nilsson's book 第 6 章:准备基础设施 - 查询。第 226 页。

    Snapshot pattern

    【讨论】:

      【解决方案3】:

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

      1. 您问题的标题和前半部分是哲学/理论问题。我认为仅通过其“聚合根”访问实体的原因是抽象出您所描述的各种实现细节。通过聚合根访问是一种通过拥有受信任的访问点来降低复杂性的方法。您正在通过遵守约定来消除摩擦/歧义/不确定性。不管它是如何在根目录中实现的,你只知道当你请求一个实体时它就会在那里。我不认为这种观点排除了您所描述的“过滤存储库”。但是要为开发人员提供一个pit of success,如果不明确说明其“过滤性”,就不可能实例化存储库;同样,如果对存储库实例的共享访问是可能的,那么在调用者中编码时“过滤性”应该是明确的。

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

      【讨论】:

        【解决方案4】:

        您是允许的,因为无论如何代码都会编译,但如果您要进行纯 DDD 设计,则不应有不完整的对象实例。

        如果你害怕加载一个巨大的对象,而你只会使用它的一小部分子实体,你应该研究一下 LazyLoading。

        LazyLoading 延迟加载您决定延迟加载的任何内容,直到它们被访问。一旦代码调用它们,它们就会使用回调来调用加载方法。

        【讨论】:

          【解决方案5】:

          那么是否可以实现一个存储库,以便在这种情况下它 返回一个不完整的聚合?

          一点也不。聚合是改变系统状态的跨国界线。永远不要使用聚合来查询数据。将系统拆分为写入端和读取端。 (阅读有关 CQR 和 CQRS 的信息)。当我们认为基于“CRUD”时,我们基于某些资源来实现我们的系统。假设您有“约会”聚合。认为“粗鲁”意味着我们应该实现用例 Create、Update、Delete、GetAll 约会。这意味着 Appointment[] 应该为 GetAll 返回。当您认为基于用例(HexagonalArchitecture)时,您的用例将是 ScheduleAppointment、RescheduleAppointment、CancelAppointment。但对于查询方面,它可以是:/myCalendar。我们在 ClientCalendar 对象中返回特定用户的所有约会。为查询端创建单独的 DTO。切勿为此目的使用聚合。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-09-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-19
            • 2019-07-22
            相关资源
            最近更新 更多