将所有实体作为聚合根的一部分加载完毕

huangapple go评论62阅读模式
英文:

Loading up all entities as part of an Aggregate Root

问题

根据我在网上的阅读,从数据库中加载的聚合必须处于完整状态。这意味着它必须能够访问其中的所有实体,这些实体也是从数据库加载的,以确保聚合永远不会处于无效/不完整的状态。

但如果聚合包含一个可能有数百万个实例的实体,例如 聚合A 包含 实体B实体C。最糟糕的情况是,聚合A 下可能有多达 100 个 实体B 实例,但是可能有数百万(甚至数十亿)个 实体C聚合A 下。


用例:

使用情况是,我想通过 Id 从 聚合A 中移除特定的 实体C 实例。按照 DDD 的方式,我首先必须从数据库中加载 聚合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));
    }
}

问题:

为了执行单个请求的任何“写操作(CQRS 中的命令)”,将数百万个实体加载到内存中似乎不是正确的方式。

我是否在这里漏掉了什么?

英文:

From what I've read online an Aggregate when loaded from the database must be in the complete state. Meaning it must have access to all the entities inside of it also loaded from the database so that the aggregate is never in an invalid/incomplete state.

What if the aggregate contains an entity of which there can be millions of for eg. Aggregate A contains Entity B and Entity C. Now worst case scenario there can be upto 100 Entity B instances under the Aggregate A but there can be millions (if not billions) of Entity C under Aggregate A.


The Use Case:

The use case would be that I want to remove one particular instance of Entity C from Aggregate A using an Id. To do this in the DDD fashion I would have to first load up the Aggregate A from the database and load all it's Entities into memory. and then remove the item maybe by using a method like below:

public class AggregateA extends AbstractAggregateRoot&lt;AggregateA&gt;{

     private String aggregateId;

     private Map&lt;String, EntityC&gt; entityC;

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

The question:

Loading up millions of entities into memory to perform any Write Operation (Command in CQRS) for a single request doesn't seem the right way.

Am I missing something here?

答案1

得分: 1

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

例如:我的操作(虽然是刻意构造的)是在拥有数百万条评论的博客条目中编辑特定的博客评论。我将从存储库中加载博客聚合和我要更新的单个评论。然后,我将让博客聚合更新评论。当然,另一个设计选择是仅加载评论(因为您可以通过其id识别它),但这将加载不完整的实体,因为它将缺少聚合根(这就是要求不这样做的内容)。

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

英文:

You misunderstand the "load the complete aggregate" mandate of DDD. Like you said, it's wholly impractical to do so. My recommendation is to load the complete parts of the aggregate that is needed.

For example: My action (albeit contrived) is to edit a specific blog comment in a blog entry with millions of comments. What I'll do is load the blog aggregate and the single comment I am trying to update from the repository. I'll then go ahead and have the blog aggregate update the comment. Ofcourse, another design choice would be to load just the comment (since you can identify it with it's id) but that would be loading an incomplete entity since it'd be missing the Aggregate root (that is what the mandate is telling not to do).

One issue here ofcourse is that it becomes in-efficient to do batch updates in DDD. That is a weakness by design and a problem solved outside of DDD.

答案2

得分: 0

我认为这将是重新考虑你的聚合边界的第一个信号。一个可能的解决方案是将实体C视为一个聚合,并通过id引用聚合A。这取决于你的业务领域,因此这只是一个笼统的建议。你可以在聚合A中或者在一个独立的领域服务中拥有一个工厂方法,用来生成聚合C的实例。通常情况下,你应该避免在一个聚合内加载数百个实体,因为它会在该聚合周围形成一个巨大的事务边界。

public class AggregateA {
    public AggregateC createAggregateC(...) {
        //创建AggregateC并将引用设置为AggregateA,然后返回它
        return new AggregateC(this.id, ...)
    }
}
英文:

I think this would be a first sign to rethink your aggregate boundaries. One possible solution would be to treat Entity C as an aggregate and have a reference by id to Aggregate A. It depends on your business domain, so this is only a vague recomendation. You could have a factory method in Aggregate A or in a seperate domain service which generates an instance of Aggregate C. In general you should avoid loading hundreds of Entities within an Aggregate, because it will span a huge transactional boundary arround that aggregate.

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

huangapple
  • 本文由 发表于 2020年8月19日 03:59:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/63475804.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定