45

根据 Fowler(此处)的说法,存储库“在域和数据映射层之间进行调解,就像内存中的域对象集合一样”。因此,例如,在我的 Courier Service 应用程序中,当提交新的运行时,我的应用程序服务会创建一个新的 Run 聚合根对象,使用请求中的值填充它,然后在调用工作单元保存之前将其添加到 RunRepository对数据库的更改。当用户想要查看当前运行的列表时,我会查询同一个存储库并返回一个表示信息的非规范化 DTO。

但是,在查看 CQRS 时,查询不会访问同一个存储库。相反,它可能会直接违背数据存储并且总是被非规范化。我的命令端将演变为 NewRunCommand 和 Handler,它们将创建和填充 NewRun 域对象,然后将信息保存到数据存储中。

所以第一个问题是,如果我们不维护域对象的内存中集合(缓存,如果你愿意的话),存储库在哪里适合 CQRS 模型?

考虑这样一种情况,提交给我的应用程序服务的信息只包含一系列 ID 值,服务必须解析这些 ID 值才能构建域对象。例如,请求包含分配给运行的快递员的 ID #。服务必须根据 ID 值查找实际的 Courier 对象,并使用 AssignCourier 方法(验证 courier 并执行其他业务逻辑)将该对象分配给 NewRun。

另一个问题是,考虑到查询的分离和存储库的潜在缺失,应用程序服务如何执行查找以找到 Courier 域对象?

更新

根据丹尼斯评论后的一些额外阅读和思考,我将重新表述我的问题。

在我看来,CQRS 鼓励存储库只是数据访问和数据存储机制的门面。它们给出了集合的“外观”(如 Fowler 所描述的),但不管理内存中的实体(如 Dennis 指出的那样)。这意味着存储库上的每个操作都是直通的,是吗?

工作单元如何适应这种方法?通常,UoW 用于提交对存储库所做的更改(对吗?)但是如果存储库没有维护内存中的实体,那么 UoW 有什么作用?

关于“写入”操作,命令处理程序是否会引用相同的存储库、不同的存储库或者可能是 UoW 而不是存储库?

4

2 回答 2

14

我读过 CQRS 系统在命令端维护一个简单的键值存储来表示应用程序的状态,以及其他仅关联消息(使用某种 saga)并利用查询存储来表示应用程序状态的系统。无论哪种方式,这些方法无疑都会涉及持久性技术,但在这些情况下,存储库模式将是不必要的抽象。

不过,我在 CQRS 方面的经验只涉及事件溯源,我们重放过去的事件以重建封装并强制执行业务逻辑和不变量的聚合。在这种情况下,存储库模式是一种熟悉的抽象,它可以提供一种更简单的方法来检索这些聚合中的任何一个。

关于查询方面,我建议尽可能靠近数据存储,我的意思是避免您的 UI(无论是什么)和数据存储之间的任何存储库、服务或外观等。

查看这些方法的使用示例可能会有所帮助。也许看看以下项目:

在 NES 的情况下,存储库仅提供了一个熟悉的界面,用于直接在工作单元中添加和读取聚合。

更多可能有帮助的链接:

于 2012-04-18T00:50:58.203 回答
1

我不确定这是多么正统 - 但在当前项目中,我有一个用于聚合实体根的存储库。这个存储库只有两个方法,Get 和 ApplyEvents。

所有事件都为其类型实现了一个通用接口——订单有OrderEvents等。我个人把每个事件的业务逻辑都放到了一个多态方法中,这样添加新类型的事件就变得非常容易了。

对于 Get,存储库转到事件存储,并获取该类型范围内的所有事件(例如,单个存储位置订单)。然后,它对事件进行重播,以针对它给出的所有事件到达实体的当前状态。它也可以从快照中工作,因此您不必在每次加载时都重新创建每个事件。您还可以拥有一个通用事件存储库,甚至可以抽象出您存储事件的方式,并根据规范检索它们。

ApplyEvents 接收一个事件列表,然后根据这些改变实体的状态,并返回它。请注意,您为存储库提供了重新创建实体的选项,而不仅仅是更改它!这适用于函数式编程,但意味着最好在 C# 或 Java 中避免对象相等 (obj1 == obj2)。我认为只有ValueObjects,而不是Entity,无论如何都应该具有平等性。

以下是它在实践中的工作方式 (C#) - 我有订单,我想添加一个项目。currentOrder.Items 返回一个空列表。然后我做

Assert.IsFalse(newEvent.Items.Any())
IOrderEvent newEvent = eventFactory.CreateOrderItemEvent(myItemID);
currentOrder = orderRepository.ApplyEvents(currentOrder, newEvent);
Assert.IsTrue(newEvent.Items.Any())

我现在应该看到 currentOrder.Items 有一个条目。

这里的缺点是我所有的处理都是通过事件完成的,而不是在实体中拥有我的业务逻辑。但是在我的情况下,我的几乎所有对象都需要可序列化(基本上是 POCO)并在多个系统上工作,这实际上效果很好。

于 2014-06-13T17:57:54.053 回答