通过多个聚合从域事件重建查询 [英] Rebuild queries from domain events by multiple aggregates

查看:49
本文介绍了通过多个聚合从域事件重建查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用DDD/CQRS/ES方法,并且对建模聚合和查询存在一些疑问.作为示例,请考虑以下情形:

I'm using a DDD/CQRS/ES approach and I have some questions about modeling my aggregate(s) and queries. As an example consider the following scenario:

用户可以创建工作项,更改其标题并将其他用户关联到该工作项.WorkItem具有参与者(关联的用户),并且参与者可以将操作添加到WorkItem.参与者可以执行动作.

A User can create a WorkItem, change its title and associate other users to it. A WorkItem has participants (associated users) and a participant can add Actions to a WorkItem. Participants can execute Actions.

让我们假设已经创建了Users,而我只需要userIds.

Let's just assume that Users are already created and I only need userIds.

我有以下WorkItem命令:

I have the following WorkItem commands:

  • CreateWorkItem
  • ChangeTitle
  • AddParticipant
  • AddAction
  • ExecuteAction

这些命令必须是幂等的,因此我不能将同一用户或操作添加两次.

These commands must be idempotent, so I cant add twice the same user or action.

以及以下查询:

  • WorkItemDetails(工作项的所有信息)

查询由处理程序更新,这些处理程序处理由WorkItem集合引发的域事件(将它们保留在EventStore中之后).所有这些事件都包含WorkItemId.我希望能够通过加载所有相关事件并按顺序处理它们来动态重建查询(如果需要).这是因为我的用户通常不会访问一年前创建的WorkItem,因此我不需要处理这些查询.因此,当我获取一个不存在的查询时,可以重建它并将其存储在具有TTL的键/值存储中.

Queries are updated by handlers that handle domain events raised by WorkItem aggregate(s) (after they're persisted in the EventStore). All these events contain the WorkItemId. I would like to be able to rebuild the queries on the fly, if needed, by loading all the relevant events and processing them in sequence. This is because my users usually won't access WorkItems created one year ago, so I don't need to have these queries processed. So when I fetch a query that doesn't exist, I could rebuild it and store it in a key/value store with a TTL.

域事件具有一个aggregateId(用作事件streamId和shard键)和一个sequenceId(用作事件流中的eventId).

Domain events have an aggregateId (used as the event streamId and shard key) and a sequenceId (used as the eventId within an event stream).

因此,我的第一个尝试是创建一个名为WorkItem的大型聚合,其中包含参与者和操作集合.参与者和动作是仅存在于WorkItem中的实体.参与者引用一个userId,而动作引用一个参与者ID.他们可以获取更多信息,但这与本练习无关.使用此解决方案,我的大型WorkItem集合可以确保命令是幂等的,因为我可以验证自己没有添加重复的参与者或动作,并且如果我想重建WorkItemDetails查询,则只需加载/处理给定事件的所有事件即可.WorkItemId.

So my first attempt was to create a large Aggregate called WorkItem that had a collection of participants and a collection of actions. Participant and Actions are entities that live only within a WorkItem. A participant references a userId and an action references a participantId. They can have more information, but it's not relevant for this exercise. With this solution my large WorkItem aggregate can ensure that the commands are idempotent because I can validate that I don't add duplicate participants or actions, and if I want to rebuild the WorkItemDetails query, I just load/process all the events for a given WorkItemId.

这很好用,因为因为我只有一个聚合,所以WorkItemId可以是gregationId,所以当我重建查询时,我只会加载给定WorkItemId的所有事件.但是,此解决方案存在大量聚合的性能问题(为什么要加载所有参与者和操作来处理ChangeTitle命令?).

This works fine because since I only have one aggregate, the WorkItemId can be the aggregateId, so when I rebuild the query I just load all events for a given WorkItemId. However, this solution has the performance issues of a large Aggregate (why load all participants and actions to process a ChangeTitle command?).

因此,我的下一个尝试是使用不同的聚合,所有聚合都具有相同的WorkItemId作为属性,但是只有WorkItem聚合才将其作为AggregationId.这解决了性能问题,我可以更新查询,因为所有事件都包含WorkItemId,但是现在我的问题是我无法从头开始重建它,因为我不知道其他聚合的aggregateId,因此无法加载他们的事件流并对其进行处理.它们具有WorkItemId属性,但这不是其真正的aggregateId.另外,我不能保证我按顺序处理事件,因为每个聚合都有自己的事件流,但是我不确定这是否是一个真正的问题.

So my next attempt is to have different aggregates, all with the same WorkItemId as a property but only the WorkItem aggregate has it as an aggregateId. This fixes the performance issues, I can update the query because all events contain the WorkItemId but now my problem is that I can't rebuild it from scratch because I don't know the aggregateIds for the other aggregates, so I can't load their event streams and process them. They have a WorkItemId property but that's not their real aggregateId. Also I can't guarantee that I process events sequentially, because each aggregate will have its own event stream, but I'm not sure if that's a real problem.

我可以想到的另一种解决方案是拥有专用的事件流,以合并由多个聚合引发的所有WorkItem事件.因此,我可以使用事件处理程序,将事件和操作触发的事件简单地附加到事件流中,该事件流的ID类似于"{workItemId}:allevents".这将仅用于重建WorkItemDetails查询.这听起来像是黑客..基本上,我正在创建没有业务操作的聚合".

Another solution I can think of is to have a dedicated event stream to consolidate all WorkItem events raised by the multiple aggregates. So I could have event handlers that simply append the events fired by the Participant and Actions to an event stream whose id would be something like "{workItemId}:allevents". This would be used only to rebuild the WorkItemDetails query. This sounds like an hack.. basically I'm creating an "aggregate" that has no business operations.

我还有什么其他解决方案?快速重建查询是不常见的吗?当使用多个聚合(多个事件流)的事件来构建相同的查询时,可以完成吗?我已经搜索了这种情况,但没有发现任何有用的信息.我觉得我想念的东西应该很明显,但是我还没弄清楚.

What other solutions do I have? Is it uncommon to rebuild queries on the fly? Can it be done when events for multiple aggregates (multiple event streams) are used to build the same query? I've searched for this scenario and haven't found anything useful. I feel like I'm missing something that should be very obvious, but I haven't figured what.

对此非常感谢.

谢谢

推荐答案

我认为您在设计汇总时不应考虑查询问题.Read端就是为此而设计的.

I don't think you should design your aggregates with querying concerns in mind. The Read side is here for that.

在域方面,重点关注一致性问题(聚合可以有多小,并且域在单个事务中仍保持一致),并发性(它有多大并且不会遭受并发的访问问题/竞争条件?)和性能(我们是否会为了执行一个简单的命令而在内存中加载数千个对象?—正是您要的内容).

On the domain side, focus on consistency concerns (how small can the aggregate be and the domain still remain consistent in a single transaction), concurrency (how big can it be and not suffer concurrent access problems / race conditions ?) and performance (would we load thousands of objects in memory just to perform a simple command ? -- exactly what you were asking).

按需读取模型没有发现任何问题.它与从实时流中读取基本上相同,不同之处在于您可以在需要时重新创建流.但是,这可能是很多工作,而不是获得非凡的收益,因为在大多数情况下,实体是在修改后立即查询的.如果按需变为基本上每次实体更改",那么您也可以订阅实时更改.对于旧"视图,旧"的定义是不再对其进行修改,因此无论您是按需系统还是连续系统,都无需重新计算它们.

I don't see anything wrong with on-demand read models. It's basically the same as reading from a live stream, except you re-create the stream when you need it. However, this might be quite a lot of work for not an extraordinary gain, because most of the time, entities are queried just after they are modified. If on-demand becomes "basically every time the entity changes", you might as well subscribe to live changes. As for "old" views, the definition of "old" is that they are not modified any more, so they don't need to be recalculated anyways, regardless of if you have an on-demand or continuous system.

如果您走了多个小型集合路线,并且您的读取模型需要来自多个来源的信息来进行自我更新,则有两种选择:

If you go the multiple small aggregates route and your Read Model needs information from several sources to update itself, you have a couple of options :

  • 使用其他数据丰富发出的事件

  • Enrich emitted events with additional data

从多个事件流读取并合并其数据以构建读取模型.这里没有魔术,读方需要知道特定投影中涉及哪些聚合.如果您知道其他读取模型是最新的,并且仅会提供所需的数据,那么您也可以查询它们.

Read from multiple event streams and consolidate their data to build the read model. No magic here, the Read side needs to know which aggregates are involved in a particular projection. You could also query other Read Models if you know they are up-to-date and will give you just the data you need.

请参见> CQRS事件不包含详细信息更新读取模型所需的

这篇关于通过多个聚合从域事件重建查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆