什么是聚合根?

Posted

技术标签:

【中文标题】什么是聚合根?【英文标题】:What's an Aggregate Root? 【发布时间】:2010-12-29 20:09:35 【问题描述】:

我正在努力了解如何正确使用存储库模式。聚合根的核心概念不断出现。在搜索 Web 和 Stack Overflow 以寻求有关聚合根是什么的帮助时,我一直在寻找关于它们的讨论以及指向应该包含基本定义的页面的死链接。

在存储库模式的上下文中,

【问题讨论】:

考虑查看以下案例研究。有效的聚合设计第一部分:单个聚合建模dddcommunity.org/wp-content/uploads/files/pdf_articles/… 第二部分:使聚合协同工作dddcommunity.org/wp-content/uploads/files/pdf_articles/… 第三部分:通过发现获得洞察力dddcommunity.org/wp-content/uploads/files/pdf_articles/… 【参考方案1】:

来自DDD Step By Step(离线):

在聚合中存在聚合根。总根是 父实体到所有其他实体和值对象 聚合。

存储库在聚合根上运行。

更多信息也可以在here找到。

【讨论】:

谢谢。这绝对是我经常遇到的最常见和最令人沮丧的断开链接。 另外,措辞似乎倒退了。根如何在聚合中同时成为它的父级? 聚合根是根类。一个普通的聚合总是包含在一个聚合根中。使用上面的图表......客户是聚合根。客户可以拥有一辆或多辆汽车。汽车是与客户相关的聚合体。汽车有引擎。 Engine 是包含在 Car Aggregate 中的一个 Aggregate。使客户成为聚合根的原因是模型假设对汽车或其组件的访问始终通过拥有汽车的客户。【参考方案2】:

想象你有一个计算机实体,这个实体也不能没有它的软件实体和硬件实体。这些构成了Computer 聚合,即域的计算机部分的微型生态系统。

Aggregate Root 是聚合中的母体实体(在我们的例子中为 Computer),通常的做法是让您的存储库仅与作为 Aggregate Roots 的实体一起使用,并且该实体负责初始化其他实体.

将聚合根视为聚合的入口点。

在 C# 代码中:

public class Computer : IEntity, IAggregateRoot

    public Hardware Hardware  get; set; 
    public Software Software  get; set; 


public class Hardware : IEntity  
public class Software : IValueObject  

public class Repository<T> : IRepository<T> where T : IAggregateRoot 

请记住,Hardware 也可能是一个 ValueObject(本身没有标识),仅将其视为示例。

【讨论】:

where T : IAggregateRoot - 这让我很开心 我认为措辞有点矛盾,这也是我在尝试学习时感到困惑的地方。你是说计算机是聚合体,但是你说根是聚合体内部的母舰实体。那么在本例中,聚合中的“母舰”实体是哪一个? 来自未来的问候!。这家伙的意思是,计算机本身就是聚合根,而计算机及其内部的一切都是聚合。或者更清楚一点:case 本身就是聚合根,而整个计算机就是聚合(构成“计算机的所有内容的集合,例如 RGB 照明、硬件、电源、操作系统等)。 IAggregateRoot 技术出现在 Microsoft 的文档中:docs.microsoft.com/en-us/dotnet/architecture/microservices/…【参考方案3】:

在存储库模式的上下文中,聚合根是您的客户端代码从存储库加载的唯一对象。

存储库封装了对子对象的访问 - 从调用者的角度来看,它会自动加载它们,无论是在加载根目录的同时还是在实际需要它们时(与延迟加载一样)。

例如,您可能有一个 Order 对象,它封装了对多个 LineItem 对象的操作。您的客户端代码永远不会直接加载 LineItem 对象,只会加载包含它们的 Order,这将是您域中该部分的聚合根。

【讨论】:

假设,如果客户端代码需要 LineItem 用于其他目的,是否会形成一个单独的聚合(假设会涉及与 Order 对象无关的其他对象)? @Ahmad,其他聚合可能将 LineItems 称为只读数据,它们只是无法更改它们。如果其他聚合可以更改它们,您将无法保护订单的不变量(也无法保护订单项)。 看看这个例如lostechies.com/blogs/jimmy_bogard/archive/2010/02/23/…。在示例中,Customer 是 Order 的不变量,对吧?但是,Customer 也可以是另一个聚合根吗?还是我在这里缺少一些基本的理解? 在该示例中,Customer 和 Order 可能都是聚合根,但我不认为作者建议您可以通过 Customer 对象更改 Orders(通过诸如 Customer.RemoveFirstItemFromOpenOrders() 之类的方法)或相反(例如,Order.UpdateCustomerBillingAddress())。 @Neil:我会使用任何可用的语言机制来强制执行它——例如,通过创建一个不可变的类来表示数据。【参考方案4】:

来自埃文斯 DDD:

AGGREGATE 是一组关联对象,我们将其视为一个单元,以便进行数据更改。每个 AGGREGATE 都有一个根和一个边界。边界定义了 AGGREGATE 内部的内容。根是包含在 AGGREGATE 中的单个特定实体。

还有:

根是 AGGREGATE 中唯一允许外部对象持有对 [.] 的引用的成员

这意味着聚合根是唯一可以从存储库加载的对象。

一个示例是包含Customer 实体和Address 实体的模型。我们永远不会直接从模型中访问Address 实体,因为没有关联的Customer 的上下文就没有意义。所以我们可以说CustomerAddress 一起形成一个聚合,而Customer 是一个聚合根。

【讨论】:

Update from Eric Evans:强调聚合根是事务/并发的一致性边界,并不再强调外部实体不能持有对其他聚合子实体的引用。 所以这种措辞总是让我感到困惑。 Each AGGREGATE has a rootThe root is the only *member* of the AGGREGATE——这个措辞暗示根是聚合的属性。但在所有示例中,情况正好相反:根包含聚合的属性。你能澄清一下吗? 只是为了让我的语言正确,Customer 类是否被视为聚合根,还是 Customer instances 一般来说,在客户订单行项目范式中,客户将是聚合根。客户的实例将是该聚合根的实例。当谈到称为客户的聚合根时,您正在讨论构成客户实例的客户的逻辑结构。客户的集合只是一个集合。 添加注释,地址在不同的域模型上可能有不同的含义,并且地址不能总是客户 BC。【参考方案5】:

如果您遵循数据库优先的方法,则聚合根通常是一对多关系的 1 侧的表。

最常见的例子是人。每个人都有许多地址、一张或多张工资单、发票、CRM 条目等。并非总是如此,但 9/10 倍如此。

我们目前正在开发一个电子商务平台,我们基本上有两个聚合根:

    客户 卖家

客户提供联系信息,我们为他们分配交易,交易获取订单项等。

卖家销售产品、有联系人、关于我们的页面、特价等。

这些由客户和卖方存储库分别负责。

【讨论】:

如果您遵循数据库优先的方法,那么您不是在实践领域驱动设计,而是在遵循数据驱动设计。 这是一个问答论坛,人们来这里解决问题和/或学习——那不是我在戳你。根据定义,DDD 是一种比其他任何东西都重要的思维方式,它让许多人感到困惑,所以我确保对那些正在学习 DDD 的人发表评论,以帮助减少任何潜在的设计方法混淆。【参考方案6】:

聚合根是一个简单想法的复杂名称。


总体思路

精心设计的类图封装了它的内部。访问此结构的点称为aggregate root

您的解决方案的内部可能非常复杂,但此层次结构的用户只会使用root.doSomethingWhichHasBusinessMeaning()


示例

检查这个简单的类层次结构

你想怎么骑你的车?选择更好的 API

选项 A(它只是以某种方式起作用):

car.ride();

选项 B(用户可以访问类内部):

if(car.getTires().getUsageLevel()< Car.ACCEPTABLE_TIRE_USAGE)
    for (Wheel w: car:getWheels())
        w.spin();
    

如果您认为选项 A 更好,那么恭喜您。你知道aggregate root背后的主要原因。


聚合根封装了多个类。您只能通过主对象来操作整个层次结构。

【讨论】:

我喜欢这个例子,但我很难找到客户应该引用 Engine 的场景。似乎 Engine 应该封装在 Car 后面。你能详细说明一下吗? 在我看来,发动机本身必须安装在特定车型中,例如配备 3000cc 发动机的 BMW 系列 5。通过这种建模,发动机是汽车的一个组件。 @ParamaDharmika 当然,你可以这样建模。这取决于您的客户对汽车的“先进”程度。在基本模型中,他应该有权访问car 聚合根。您也可以允许像图纸上这样的情况。正确的解决方案取决于应用程序的业务模型。在每种情况下可能会有所不同。 @MarcinSzymczak 正确,完全同意解决方案取决于域模型本身 那么现在你的汽车对象负责返回对象?您的汽车现在是一个存储库 - 例如car.GetTyres()?这有什么好处?【参考方案7】:

在 Erlang 中没有必要区分聚合,一旦聚合由状态内部的数据结构组成,而不是 OO 组合。看一个例子:https://github.com/bryanhunter/cqrs-with-erlang/tree/ndc-london

【讨论】:

【参考方案8】:

Aggregate 表示某物的集合。root 就像树的顶部节点,从那里我们可以访问网页文档中的 &lt;html&gt; 节点等所有内容。 博客类比,一个用户可以有很多帖子,每个帖子可以有很多 cmets。因此,如果我们获取任何用户,那么它可以作为 root 访问所有相关帖子以及这些帖子的进一步 cmets。这些统称为集合或聚合

【讨论】:

【参考方案9】:

黛娜:

在存储库的上下文中,聚合根是没有父实体的实体。它包含零个、一个或多个子实体,其存在取决于父实体的身份。这是存储库中的一对多关系。这些子实体是普通的聚合。

【讨论】:

那么,如果您是汽车销售商,那么 Car 本身就是聚合根吗?因为您可以拥有许多还没有客户的汽车 @JorgeeFG 真正的答案是没有人有任何线索。周围有很多相互矛盾的信息。 子实体不是聚合,它们只是恰好是聚合根控制的聚合的成员的实体。 “聚合”是实体的逻辑分组。 @JorgeeFG 这真的取决于您正在设计的有界上下文。如果您是汽车销售商,那么像 Carshop 之类的东西会成为聚合根,然后在它下面跟随 Cars...【参考方案10】:

聚合是您保护不变量并通过限制其访问思想聚合根来强制一致性的地方。不要忘记,聚合应该根据您的项目业务规则和不变量进行设计,而不是数据库关系。你不应该注入任何存储库,也不允许任何查询。

【讨论】:

【参考方案11】:

在另一个世界中,在事件溯源中,聚合(根)是一个不同的概念。 事件溯源可能会与 CQRS、DDD 等一起遇到。

在事件溯源中,聚合是一个对象,其状态(字段)未映射到数据库中的记录,正如我们在 SQL/JPA 世界中所认为的那样。

不是一组相关的实体。

它是一组相关的记录,就像在历史表中。

GiftCard.amount 是 GiftCard Aggregate 中的一个字段,但此字段映射到所有事件,例如曾经创建的卡兑现(从卡中取钱)。

因此,您的聚合的数据源不是数据库中的记录,而是曾经为该特定聚合创建的事件的完整列表。我们说我们事件来源聚合。

现在我们可以问自己它是如何完成的?谁在汇总这些事件,所以我们仍然使用一个字段(例如 GiftCard.amount)进行操作?我们可能期望该数量是一个集合,而不是一个大十进制类型。

是事件溯源引擎,在做这项工作,它可能只是按创建顺序重放所有事件。但这超出了这个线程的范围。

【讨论】:

每次访问一个字段时似乎需要做很多工作。随着时间的推移,工作呈指数增长。有什么技巧被应用吗?快照可能吗?

以上是关于什么是聚合根?的主要内容,如果未能解决你的问题,请参考以下文章

聚合根可以引用另一个根吗?

领域驱动设计中实体和聚合之间的区别

领域模型:聚合聚合根详解

基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

DDD中聚合聚合根的含义以及作用

一系列简单的聚合根问题(领域驱动设计)