【问题标题】:Aggregate Root references other aggregate roots聚合根引用其他聚合根
【发布时间】:2011-02-07 09:28:04
【问题描述】:

我目前正在大量使用 DDD,但在从其他聚合根加载/操作聚合根时遇到问题。

对于我模型中的每个聚合根,我也有一个存储库。存储库负责处理根的持久性操作。

假设我有两个聚合根,有一些成员(实体和值对象)。

AggregateRoot1 和 AggregateRoot2。

AggregateRoot1 有一个引用 AggregateRoot2 的实体成员。

  1. 当我加载 AggregateRoot1 时,我是否也应该加载 AggregateRoot2?
  2. AggregateRoot2 的存储库应该对此负责吗?
  3. 如果是这样,AggregateRoot1 中的实体是否可以调用 AggregateRoot2 的存储库进行加载?

另外,当我在 AggregateRoot1 中的实体与 AggregateRoot2 之间创建关联时,应该通过实体还是通过 AggregateRoot2 的存储库来完成?

希望我的问题有意义。

[编辑]

当前解决方案

Twith2Sugars 的帮助下,我想出了以下解决方案:

如问题中所述,聚合根可以具有引用其他根的子级。将 root2 分配给 root1 的其中一个成员时,root1 的存储库将负责检测此更改,并将其委托给 root2 的存储库。

public void SomeMethod()
{
    AggregateRoot1 root1 = AggregateRoot1Repository.GetById("someIdentification");
    root1.EntityMember1.AggregateRoot2 = new AggregateRoot2();
    AggregateRoot1Repository.Update(root1);
}

public class AggregateRoot1Repository
{
    public static void Update(AggregateRoot1 root1)
    {
        //Implement some mechanism to detect changes to referenced roots
        AggregateRoot2Repository.HandleReference(root1.EntityMember1, root1.EntityMember1.AggregateRoot2)
    }
}

这只是一个简单的例子,不包括得墨忒耳法则或其他最佳原则/实践:-)

感谢更多cmets。

【问题讨论】:

  • 就我个人而言,我可以看到这种当前的方法变得混乱,我认为 DavidMasters84 的解决方案更像是一个优雅的解决方案。即将引用保持为 id 并将这种类型的域逻辑提取到域服务。
  • 凌乱是这种方法的一个很好的形容词。我可以这么说,因为我最初尝试以相同的方式实现这个问题,而我发现自己陷入了混乱:) 你可能还想在这里阅读关于类似问题的建议:stackoverflow.com/questions/2118088/…
  • 我听到了,但是那里没有管理聚合根的存储库,并且出于某种善意,根之间的关系。以及域服务来处理不适合单个实体的行为?在我看来,在阅读域服务的定义时,让域服务负责处理根之间的引用是错误的地方......我可能是错的,所以更支持的论点将不胜感激,谢谢。
  • 我对域服务的建议不是用来管理聚合之间的引用;它可以调用涉及多个聚合的功能,即“自然不适合单个实体的行为”。在大多数模型中,所有聚合都以某种形式在关系数据库中相互关联,聚合的目的是将这个依赖图分解为可管理的组。如果您维护模型中所有聚合之间的关系,它将违背聚合的意义。
  • 是的,这就是我的想法;你的问题是关于聚合根之间的运行操作,我认为这是域服务适合的地方。我想你很快就会问自己兔子洞有多深......

标签: domain-driven-design repository aggregate loading aggregateroot


【解决方案1】:

我自己也遇到过这种情况,得出的结论是,让子聚合以优雅的方式工作实在是太让人头疼了。相反,我会考虑您是否真的需要将第二个聚合引用为第一个聚合的子级。如果您只保留聚合 ID 的引用而不是实际聚合本身,这会使生活变得更加轻松。然后,如果存在涉及两个聚合的域逻辑,则可以将其提取到域服务中,如下所示:

public class DomainService
{
    private readonly IAggregate1Repository _aggregate1Repository;
    private readonly IAggregate2Repository _aggregate2Repository;

    public void DoSomething(Guid aggregateID)
    {
        Aggregate1 agg1 = _aggregate1Repository.Get(aggregateID);
        Aggregate2 agg2 = _aggregate2Repository.Get(agg1.Aggregate2ID);

        agg1.DoSomething(agg2);
    }
}

编辑:

真的推荐这些关于该主题的文章:https://vaughnvernon.co/?p=838

【讨论】:

  • + 1. 这也是 Vaughn Vernon 在他的“通过身份引用其他聚合”规则中所主张的。优点包括整体聚合大小导致更好的性能和更容易的分区。我猜的缺点是它有时会降低模型代码的表现力,所以它像往常一样需要权衡:dddcommunity.org/sites/default/files/pdf_articles/…
  • 使用这种方法,您不会将 Aggregate1 和 Aggregate2 之间的关系存储在数据库中,如果需要记住该关系,您会怎么做
  • @redzedi - 是的。在上面的示例中,Aggregate2 存储了其相关 Aggregate1 的 ID。
  • @DavidMasters ,好的,我现在看到了,但是在上面的代码行Aggregate2 agg2 = _aggregate2Repository.Get(agg1.Aggregate2ID); 中,你不让 db 保持 tat 关系我们不知道 'Aggregate2ID' 是否存在?跨度>
  • @DavidMasters 对不起,如果我不清楚,我的困惑是因为它不是一个完整的对象引用,它是否在 db 级别仍然具有 FK 关系,我在这里考虑更多的实现。所以我猜你真正想说的是让没有连接,但让表定义了一个 FK。应用层应该决定它是否需要来自 Id 的整个对象,在这种情况下它会使用 Id 查询存储库
【解决方案2】:

这种方法存在一些问题。首先,您应该为每个聚合拥有一个存储库并完成它。拥有一个调用另一个存储库的存储库是对这一规则的突破。其次,关于聚合关系的一个好的做法是一个根聚合应该通过它的 id 与另一个根聚合通信,而不是它的引用。这样做,您保持每个聚合独立于另一个聚合。仅在根聚合中保留组成相同聚合的类的引用。

【讨论】:

    【解决方案3】:

    也许 AggregateRoot1 存储库在构建 AggregateRoot1 实体时可以调用 AggregateRoot2 存储库。

    我认为这不会使 ddd 无效,因为存储库仍负责获取/创建自己的实体。

    【讨论】:

    • 我也考虑过这一点,但是如果 Aggregate2 有对​​ Aggregate3 的引用,而 aggregate3 又指向另一个等等。这可能是一个相当大的对象图。针对这些场景的建议策略是什么?
    • 没错,我知道您不应该过多担心 DDD 中的实现,但如果我认为图表太大,我会延迟加载聚合。
    • 好的,到目前为止一切顺利。 :-) 但是当我创建 AggregateRoot1.Entity1 --> AggregateRoot2 之间的关系时,应该通过 AggregateRootRepository1.AddRoot2ToEntity1(root1, root2) 还是通过 AggregateRepository2.AddRoot2ToRoot1(root1, root2) 或更直接的分配来完成: root2.Entity1.AddRoot2(root2)
    • 就我个人而言,我不会说它们。我将通过以下方式分配它:“entity1.Entity2 = entity2”,然后存储库应该能够检测到这种关系并执行它需要做的任何事情(即,如果那是底层存储,则更新 db 列)
    • 谢谢 TWith2Sugars,但我真的很想在结束这个问题之前得到更多反馈。感谢您的回答-
    猜你喜欢
    • 1970-01-01
    • 2011-01-16
    • 2018-08-12
    • 1970-01-01
    • 2014-10-26
    • 2013-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多