【问题标题】:Loading up all entities as part of an Aggregate Root加载所有实体作为聚合根的一部分
【发布时间】:2020-08-18 19:59:33
【问题描述】:

从我在线阅读的内容来看,从数​​据库加载时的聚合必须处于完整状态。这意味着它必须有权访问其中也从数据库加载的所有实体,以便聚合永远不会处于无效/不完整状态。

如果聚合包含一个可能有数百万个实体的实体,例如。 Aggregate A 包含 Entity BEntity C。现在最坏的情况是,Aggregate A 下最多可以有 100 个Entity B 实例,但Aggregate A 下可能有数百万(如果不是数十亿)Entity C


用例:

用例是我想使用 Id 从Aggregate A 中删除Entity C 的一个特定实例。要以DDD 方式执行此操作,我必须首先从数据库中加载Aggregate A 并将其所有实体加载到内存中。然后可以使用以下方法删除该项目:

public class AggregateA extends AbstractAggregateRoot<AggregateA>{

     private String aggregateId;

     private Map<String, EntityC> entityC;

     public void removeEntityC(String idToRemove) {
           this.entityC.remove(idToRemove);
           registerEvent(new EntityCRemoved(aggregateId, idToRemove));
     }
}

问题:

将数百万个实体加载到内存中以针对单个请求执行任何Write Operation (Command in CQRS) 似乎不是正确的方法。

我错过了什么吗?

【问题讨论】:

    标签: java domain-driven-design ddd-repositories


    【解决方案1】:

    您误解了 DDD 的“加载完整聚合”任务。就像你说的那样,这样做是完全不切实际的。我的建议是加载所需的聚合的完整部分。

    例如:我的操作(尽管是人为的)是编辑具有数百万 cmets 的博客条目中的特定博客评论。我要做的是加载博客聚合和我试图从存储库更新的单个评论。然后我会继续让博客聚合更新评论。当然,另一种设计选择是仅加载注释(因为您可以使用它的 id 来识别它),但这将加载一个不完整的实体,因为它会丢失聚合根(这是任务要求不要做的) )。

    这里的一个问题当然是在 DDD 中进行批量更新效率低下。这是设计上的弱点,也是在 DDD 之外解决的问题。

    【讨论】:

    • 谢谢,它确实比 brckner 的回答更有意义,但是如果假设您有一个域不变量,没有两个 cmets 可以相同,那么现在就不会有问题了(就像在继续你的回答)。现在,当您编辑博客评论时,假设它与另一条评论冲突。由于您只加载了一条评论,除非您依赖数据库进行唯一索引,否则您不会知道它是否与另一条评论冲突。但 DDD 的目标是将所有不变量保留为域的一部分,而不是依赖于基础设施来确保不变量。
    • 好问题。在您的扩展示例中:您的 UL 现在要求评论在博客下是唯一的。因此,您的评论内容现在是关键。当你为你的博客(你的聚合)补充水分时,你的 repo 需要获取密钥(内容)并同时补充它。之所以如此,是因为对于您尝试执行的功能,“完成”意味着使用现有评论键对博客和任何评论进行水合的 repo。因此,即使在运行 blog.add(comment) 之前,您也会从 repo 中获得一个带有单个评论的博客。其余的从那里开始是微不足道的。
    • 我不太明白hydrate 我错过了什么吗?你能指出我能理解的方向吗?
    • 水合物意味着当您将其从存储中取出时。因此,当您的博文超出存储空间(可能是数据库)添加评论时,还要拉取与新评论具有相同键的所有 cmets。
    【解决方案2】:

    我认为这将是重新考虑您的总体界限的第一个迹象。一种可能的解决方案是将实体 C 视为聚合,并通过 id 引用聚合 A。这取决于您的业务领域,因此这只是一个模糊的建议。您可以在聚合 A 中或在生成聚合 C 实例的单独域服务中使用工厂方法。一般而言,您应该避免在聚合中加载数百个实体,因为它将跨越该聚合周围的巨大事务边界。

    public class AggregateA {
        public AggregateC createAggregateC(...){
            //create AggregateC and return it with reference set to AggregateA
            return new AggregateC(this.id, ...)
        }
    }
    

    【讨论】:

    • 但是除了Aggregate A中的Entity C的数量之外,它是Entity的理想情况。其中一些是:1) 如果没有Aggregate AEntity C 就没有意义。 2)如果你删除Aggregate A,所有关联的Entity C也必须被删除。 3) 对Entity C 的任何更改必须始终通过Aggregate A 进行,因为Aggregate A 确保Entity C 上有一些不变量。老实说,将 Entity C 分解为自己的聚合是没有意义的
    • 这取决于你的业务逻辑。在一个聚合中加载数千个实体是没有意义的,因为您的事务边界会爆炸。 1)为什么不呢?你仍然会在他们之间有一个参考。 2/3)您可以通过域服务强制执行此类不变量。我不确定,但我认为 Vaughn Vernon 在这个博客系列中解决了您的一些问题。也许去那里看看。 dddcommunity.org/library/vernon_2011
    • 您在比例部分上是对的,这就是“加载与聚合关联的实体的一百万个实例”问题的重点没有意义。你能详细说明你的答案吗?因为我没有阅读任何地方,所以我找不到一些关于使聚合成为另一个聚合的一部分的资源
    猜你喜欢
    • 1970-01-01
    • 2011-11-19
    • 2015-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多