CQRS之旅——旅程6(我们系统的版本管理) (3)

在V2模型中,如果处理程序类抛出异常,EventProcessor实例将事件消息放回与该处理程序类关联的订阅。重试逻辑现在只会导致EventProcessor实例重试引发异常的处理程序,因此没有其他处理程序会重新处理消息。

集成事件的持久化

在V1版本中提出的一个问题是,系统如何持久化从会议管理限界上下文发送到订单和注册限界上下文的集成事件。这些事件包括关于会议创建和发布的信息,以及座位类型和配额更改的详细信息。

在V1版本中,订单和注册上下文中的ConferenceViewModelGenerator类通过更新视图模型并向SeatsAvailability聚合发送命令来处理这些事件,以告诉它更改座位配额值。

这种方法意味着订单和注册限界上下文不存储任何历史记录,这可能会导致问题。例如,其他视图从这里中查找座椅类型描述时,这里只包含座椅类型描述的最新值。因此,在其他地方重播一组事件可能会重新生成另一个包含不正确座椅类型描述的读取模型投影。

团队考虑了以下五个方法来纠正这种情况:

将所有事件保存在原始限界上下文中(会议管理限界上下文中),并使用共享的事件存储,订单和注册限界上下文中可以访问该存储来重播这些事件。接收限界上下文可以重放事件流,直到它需要查看的之前的座椅类型描述时为止。

当所有事件到达接收限界上下文(订单和注册限界上下文)时保存它们。

让视图模型生成器中的命令处理程序保存事件,只选择它需要的那些。

让视图模型生成器中的命令处理程序保存不同的事件,实际上就是为此视图模型使用事件源。

将来自所有限界上下文的所有命令和事件消息存储在消息日志中。

第一种选择并不总是可行的。在这种特殊情况下,它可以工作,因为同一个团队同时实现了限界上下文和基础设施,使得使用共享事件存储变得很容易。

CQRS之旅——旅程6(我们系统的版本管理)

Gary(CQRS专家)发言:

尽管从纯粹主义者的角度来看,第一个选项破坏了限界上下文之间的严格隔离,但在某些场景中,它可能是一个可接受的实用解决方案。

第三种选择可能存在的风险是,所需的事件集合可能在未来发生变化。如果我们现在不保存事件,它们将永远丢失。

尽管第五个选项存储了所有命令和事件,其中一些可能永远都不需要再次引用,但它确实提供了一个完整的日志,记录了系统中发生的所有事情。这对于故障诊断很有用,还可以帮助您满足尚未确定的需求。该团队选择了这个选项而不是选项二,因为它提供了一个更通用的机制,可能具有未来的好处。

持久化事件的目的是,当订单和注册上下文需要有关当前座位配额的信息时,可以回放这些事件,以便计算剩余座位的数量。要一致地计算这些数字,必须始终以相同的顺序回放事件。这种顺序有几种选择:

会议管理限界上下文发送事件的顺序。

订单和注册上下文接收事件的顺序。

订单和注册上下文处理事件的顺序。

大多数情况下,这些顺序是相同的。没有什么正确的顺序。你只需要选择一个和它保持一致就行了。因此,选择由简单性决定。在本例中,最简单的方法是按照订单和注册限界上下文中处理程序接收事件的顺序持久化事件(第二个选项)。

CQRS之旅——旅程6(我们系统的版本管理)

Markus(软件开发人员)发言:

这种选择通常不会出现在事件源中。每个聚合会都以固定的顺序创建事件,这就是系统用于持久存储事件的顺序。在此场景中,集成事件不是由单个聚合创建的。

为这些事件保存时间戳也有类似的问题。如果将来需要查看特定时间剩余的座位数量,那么时间戳可能会很有用。这里的选择是,当事件在会议管理限界上下文中创建时,还是在订单和注册限界上下文中接收时,应该创建时间戳?当会议管理限界上下文创建事件时,订单和注册限界上下文可能由于某种原因离线。因此,团队决定在会议管理有界上下文发布事件时创建时间戳。

消息排序

团队创建并运行来验证V1版本的验收测试,凸显出了消息排序的一个潜在问题:执行会议管理限界上下文的验收测试向订单和注册限界上下文发送了一系列命令,这些命令有时会出现顺序错误。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfpgw.html