architecture - 在长期运行的事务中使用最小聚合的架构问题
问题描述
我正在尝试遵循关于聚合设计的Vaughn Vernon 指南。
我知道出于他的系列中提到的许多原因以及许多 DDD 从业者的建议,建议尽可能地减小聚合。
但这会产生处理聚合主机内不相关命令属性的问题。
我有一个传入的命令,其中包含系统完全执行交易所需的信息。命令中包含的信息不适合单个聚合,因为它们是最小的。
所以,我认为这是一个长期的交易。我正在使用 saga 模式控制整个事务流。
每个包装了这个长期运行事务中涉及的聚合的主机都会引发一个域事件。然后,该领域事件被 saga 拦截,该 saga 依次发出后续命令,依此类推,直到业务事务成功执行或完全失败。
如果我们想象一个场景,我们有这样的命令:
在对话 Z 中从参与者 Y 发送消息 X
例如,此命令将被消息聚合的主机截获。
该主机将重新水化消息聚合,告诉它验证事务的第一部分:
从参与者 Y 发送消息X。
拦截命令的同一主机将不得不引发域事件,例如
MessageCreated,这里的问题是 MessageCreated 事件需要携带其余的业务交易细节
在谈话 Z
为后续聚合执行剩余的事务。
由于消息聚合不关心ConversationId,它的主机需要保留该信息(从聚合的角度来看是不必要的)。
当聚合批准交易时,主机必须创建一个域事件,例如MessageCreated * 并将ConversationId附加到它。
我在这里担心的是,通过将执行业务事务所需的信息保留在主机内部,随着时间的推移,更多的业务需求会随着时间的推移而积累,最坏的情况可能会在主机之间产生逻辑耦合。
对于我的问题:
这是否被认为是处理长时间运行事务的安全方法,如果不是,那将是更好的选择吗?
解决方案
从您的问题中不清楚您如何以及为什么以这种方式设计聚合。你提到它们必须是最小的,但我认为这不是最重要的方面。聚合需要实现其目标。只有当它们太大或可能变得太大而无法使用时,尺寸才重要。例如,内部可能包含数年价值的消息的对话可能不是一个好的设计。但是可能会告诉你这不是一个好的设计不仅是它不断增长的性质,而且它很可能不需要所有消息来实现其目标(可能没有业务逻辑涉及对话中的所有消息)。也许您只需要最后几条消息或其他参与者尚未收到的消息。在那之后,
我想说的是,一个解决方案可能是重新设计您的聚合,以便您一开始就没有这个问题。
另一方面,您提到您使用了 Saga 模式,但根据解释不清楚您是如何做到的,因为聚合似乎是由事件而不是由 saga 协调的。
如果您需要将聚合与 saga 协调,命令将由 saga 处理。然后,您将面临以下两种情况之一:
- 要协调的操作不相互依赖
- 这些操作相互依赖,它们需要按一定的顺序执行(或依赖于先前操作的数据)
在第一种情况下,saga 将只处理命令并将“较小”的命令发送到所有需要协调的聚合。这些聚合将执行操作并发布事件或回复命令发送者,这将是一种设计选择。然后 saga 收集所有响应,当它收到所有响应时,操作已完成(显然,您需要处理某些操作失败或从未完成的情况)。
在第二种情况下,saga 将处理命令并在内部存储所有必要的信息。然后向第一个聚合发送命令并等待回复或事件。当接收到时,它将使用来自事件的信息加上在命令中接收到的信息在进程中发送以下命令,依此类推。
在这两种情况下,saga 应该能够检测到一个或多个步骤何时不成功,并发送其他命令来补偿或恢复成功的步骤。
推荐阅读
- excel - Workbook_BeforeClose 事件上的 Excel 自己的对话框,取消选项
- ios - React Native 0.60 升级:未知类型名称“MutexType”
- reactjs - 可能是比赛条件
- oracle - 如何在表被截断时创建将数据移动到另一个表的触发器
- java - Class.forName() 异常。java.lang.Class.forName0(本机方法)处的线程“AWT-EventQueue-0”java.lang.ExceptionInInitializerError 中的异常
- angular-calendar - 角度日历具有显示更多选项以在月视图的日单元格中加载更多事件
- python - h2o automl auc 系数低
- c# - 显示空的 XML 标记
- c++ - 从模板类继承:“成员”未在此范围内声明
- python-3.x - 将 while 和 if 函数与条件更改一起使用