在一个事务中创建多个聚合
Create multiple aggregates in one transaction
我的限界上下文(库存)收到一个数量为 5 的事件。其中 5 是仓库中从卡车上卸下的托盘数量。现在在我的库存 BC 中,我想创建 5 个聚合(foreach pallet 1 聚合)。我怎样才能在我的应用层做到这一点?如果我只成功创建了其中的 3 个怎么办?我无法临时创建它们,因为我的持久存储是基于文件的。我该怎么办?
通常的答案是这样的——我们做的第一件事是将 事件 保存在 "TODO" 列表中。
我们对这个列表的 "subscription" 需要跟踪,也就是说 记下 它在待办事项列表中的进度。例如,我们可以将 TODO 列表视为仅追加的事件序列,并且订阅记下最后一个完全处理的事件的索引。
当订阅运行时,它会在列表中查找第一个未处理的事件,并根据您需要的事务数进行处理。完成所有这些工作后,它会更新自己的计数器(另一个事务),然后可以继续。
如果处理失败,那么我们永远不会到达"write it down"步;所以当我们重新启动时,它会尝试再次处理相同的事件。
为此,我们需要两个属性:一个是 运行 处理器再次为聚合生成 相同的 标识符列表,并且处理器知道聚合可能已经创建,并在这种情况下采取适当的行动。
所以在最坏的情况下:我们得到了 5 个托盘的事件。我们创建了所有 5 个新聚合,然后在它可以记录事件已完全处理之前崩溃。进程重新启动,并开始处理同一事件。它发现每个新聚合都已创建,因此这些步骤中的每一个都变成了空操作。最后,完成事件的所有处理后,它记录事件已完成。
换句话说,我们需要幂等处理。
您应该将接收事件的记录和五个托盘的生成视为两个单独的事务。
在您的案例中,更改跨越两个聚合,但理想情况下,您的应用程序服务应尽可能处理一个聚合。 域事件 是解决此类扩展事务问题的正确结构,无论是在单个 BC 中的聚合之间,还是跨 BC。
因此您的应用程序服务会将五个托盘的收据记录为交易,并冒出具有足够上下文和数据的域事件(比如 PalletsUnloaded
)。该事件将作为数据结构传递给消息代理,由为域事件注册的订阅者检索。
Pallet 聚合然后将通过特定于事件的订阅者捕获事件并以两种方式之一处理它:
- 您可以一次创建五个托盘。在交易方面,这种方法有点冒险,因为如果您将文件用作持久存储,则可能会出现故障,并且您可能没有细粒度的数据来确定确切的问题
- 您捕获该事件并创建五个单独的事件消息(比如
CreatePallet
),每个消息都被提交回消息代理。此事件的订阅者将挑选它们并一个一个地创建托盘记录。你会准确地知道哪一个失败了以及为什么
第二种方法也更安全,因为如果您使用可靠的消息代理(如 RabbitMQ)作为事件的传输机制,您可以将出错的事件发送到死信队列或设置稍后重试处理的机制。您还可以构建一个单独的错误处理 process/view 来处理和处理错误事件。
我的限界上下文(库存)收到一个数量为 5 的事件。其中 5 是仓库中从卡车上卸下的托盘数量。现在在我的库存 BC 中,我想创建 5 个聚合(foreach pallet 1 聚合)。我怎样才能在我的应用层做到这一点?如果我只成功创建了其中的 3 个怎么办?我无法临时创建它们,因为我的持久存储是基于文件的。我该怎么办?
通常的答案是这样的——我们做的第一件事是将 事件 保存在 "TODO" 列表中。
我们对这个列表的 "subscription" 需要跟踪,也就是说 记下 它在待办事项列表中的进度。例如,我们可以将 TODO 列表视为仅追加的事件序列,并且订阅记下最后一个完全处理的事件的索引。
当订阅运行时,它会在列表中查找第一个未处理的事件,并根据您需要的事务数进行处理。完成所有这些工作后,它会更新自己的计数器(另一个事务),然后可以继续。
如果处理失败,那么我们永远不会到达"write it down"步;所以当我们重新启动时,它会尝试再次处理相同的事件。
为此,我们需要两个属性:一个是 运行 处理器再次为聚合生成 相同的 标识符列表,并且处理器知道聚合可能已经创建,并在这种情况下采取适当的行动。
所以在最坏的情况下:我们得到了 5 个托盘的事件。我们创建了所有 5 个新聚合,然后在它可以记录事件已完全处理之前崩溃。进程重新启动,并开始处理同一事件。它发现每个新聚合都已创建,因此这些步骤中的每一个都变成了空操作。最后,完成事件的所有处理后,它记录事件已完成。
换句话说,我们需要幂等处理。
您应该将接收事件的记录和五个托盘的生成视为两个单独的事务。
在您的案例中,更改跨越两个聚合,但理想情况下,您的应用程序服务应尽可能处理一个聚合。 域事件 是解决此类扩展事务问题的正确结构,无论是在单个 BC 中的聚合之间,还是跨 BC。
因此您的应用程序服务会将五个托盘的收据记录为交易,并冒出具有足够上下文和数据的域事件(比如 PalletsUnloaded
)。该事件将作为数据结构传递给消息代理,由为域事件注册的订阅者检索。
Pallet 聚合然后将通过特定于事件的订阅者捕获事件并以两种方式之一处理它:
- 您可以一次创建五个托盘。在交易方面,这种方法有点冒险,因为如果您将文件用作持久存储,则可能会出现故障,并且您可能没有细粒度的数据来确定确切的问题
- 您捕获该事件并创建五个单独的事件消息(比如
CreatePallet
),每个消息都被提交回消息代理。此事件的订阅者将挑选它们并一个一个地创建托盘记录。你会准确地知道哪一个失败了以及为什么
第二种方法也更安全,因为如果您使用可靠的消息代理(如 RabbitMQ)作为事件的传输机制,您可以将出错的事件发送到死信队列或设置稍后重试处理的机制。您还可以构建一个单独的错误处理 process/view 来处理和处理错误事件。