我试图弄清楚如何正确使用存储库模式。聚合根的核心概念不断出现。在搜索 Web 和 Stack Overflow 以寻求有关聚合根是什么的帮助时,我一直在寻找关于它们的讨论以及指向应该包含基本定义的页面的死链接。
在存储库模式的上下文中,什么是聚合根?
我试图弄清楚如何正确使用存储库模式。聚合根的核心概念不断出现。在搜索 Web 和 Stack Overflow 以寻求有关聚合根是什么的帮助时,我一直在寻找关于它们的讨论以及指向应该包含基本定义的页面的死链接。
在存储库模式的上下文中,什么是聚合根?
在存储库模式的上下文中,聚合根是您的客户端代码从存储库加载的唯一对象。
存储库封装了对子对象的访问——从调用者的角度来看,它会自动加载它们,无论是在加载根目录的同时还是在实际需要它们时(与延迟加载一样)。
例如,您可能有一个Order
对象,它封装了对多个LineItem
对象的操作。您的客户端代码永远不会LineItem
直接加载对象,只会加载Order
包含它们的对象,这将是您域的那部分的聚合根。
来自埃文斯 DDD:
AGGREGATE 是一组关联对象,我们将其视为一个单元,用于数据更改。每个 AGGREGATE 都有一个根和一个边界。边界定义了 AGGREGATE 内部的内容。根是聚合中包含的单个特定实体。
和:
根是 AGGREGATE 的唯一成员,允许外部对象保存对 [.] 的引用
这意味着聚合根是唯一可以从存储库加载的对象。
一个示例是包含Customer
实体和Address
实体的模型。我们永远不会Address
直接从模型中访问实体,因为没有关联的上下文就没有意义Customer
。所以我们可以说Customer
和Address
一起形成一个聚合,这Customer
就是聚合根。
聚合根是一个简单想法的复杂名称。
精心设计的类图封装了它的内部。访问此结构的点称为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
。
聚合根封装了多个类。您只能通过主对象来操纵整个层次结构。
想象一下你有一个计算机实体,这个实体也不能没有它的软件实体和硬件实体。这些构成Computer
了域的计算机部分的聚合体、微型生态系统。
聚合根是聚合内的母体实体(在我们的例子中Computer
),通常的做法是让您的存储库仅与聚合根的实体一起使用,并且该实体负责初始化其他实体。
将聚合根视为聚合的入口点。
在 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(本身没有身份),仅将其视为示例。
如果您遵循数据库优先的方法,则聚合根通常是一对多关系的 1 侧的表。
最常见的例子是一个人。每个人都有许多地址、一张或多张工资单、发票、CRM 条目等。并非总是如此,但 9/10 倍如此。
我们目前正在开发一个电子商务平台,我们基本上有两个聚合根:
客户提供联系信息,我们为他们分配交易,交易获取订单项等。
卖家销售产品,有联系人,关于我们的页面,特价等。
这些分别由客户和卖方存储库处理。
聚合意味着收集某物。
root就像树的顶部节点,从那里我们可以访问<html>
网页文档中的节点等所有内容。
博客类比,一个用户可以有很多帖子,每个帖子可以有很多评论。因此,如果我们获取任何用户,那么它可以作为root访问所有相关帖子以及这些帖子的进一步评论。这些都被称为集合或聚合
聚合是您通过限制其访问思想聚合根来保护不变量并强制一致性的地方。不要忘记,聚合应该根据您的项目业务规则和不变量进行设计,而不是数据库关系。你不应该注入任何存储库,也不允许任何查询。
在另一个世界中,在事件溯源中,聚合(根)是一个不同的概念。事件溯源可能与 CQRS、DDD 等一起遇到。
在事件溯源中,聚合是一个对象,其状态(字段)未映射到数据库中的记录,正如我们在 SQL/JPA 世界中所认为的那样。
不是一组相关的实体。
它是一组相关的记录,就像在历史表中一样。
GiftCard.amount 是 GiftCard Aggregate 中的一个字段,但该字段映射到所有事件,例如曾经创建的卡兑现(从卡中取钱)。
因此,您的聚合的数据源不是数据库中的记录,而是为该特定聚合创建的完整事件列表。我们说我们事件来源聚合。
现在我们可以问自己它是如何完成的?谁在汇总这些事件,所以我们仍然使用一个字段,例如 GiftCard.amount?我们可能期望这个数量是一个集合而不是一个大十进制类型。
是事件溯源引擎,在做这项工作,他可能只是按创建顺序重播所有事件。但这超出了该线程的范围。
在 Erlang 中,无需区分聚合,一旦聚合由状态内部的数据结构组成,而不是 OO 组合。看一个例子:https ://github.com/bryanhunter/cqrs-with-erlang/tree/ndc-london