事件溯源是构建业务逻辑和持久化聚合的另一种选择,它将聚合以一系列的方式持久化保存,每个事件代表聚合的一次状态变化。应用通过重放事件来重新创建聚合的当前状态。
好处:
弊端:
对象与关系的阻抗失调
关系数据库的表格结构模式与领域模型及其复杂关系的图状结构之间,存在基本的概念不匹配问题。
缺乏聚合的历史
只存储聚合的当前状态,聚合更新后先前的状态丢失
实现审计功能将非常繁琐且容易出错
这是项耗时的工作,记录审计的代码可能会和业务逻辑代码偏离
事件发布是凌驾于业务逻辑之上
不支持发布领域事件,开发人员必须自己处理事件生成的逻辑。
事件溯源通过事件来持久化聚合
事件溯源采用基于领域事件的概念来实现聚合的持久化,将每个聚合持久化为数据库中的一系列事件。
应用程序从事件存储中检索并重放事件来加载聚合。
1、加载聚合的事件
2、使用其默认的构造函数创建聚合实例
3、调用apply()方法遍历事件
事件代表状态的改变
事件必须包含执行状态更改所需要的数据
聚合方法都和事件相关
业务逻辑通过调用聚合根上的命令方法来处理对聚合的更新请求。命令方法通常会验证其参数,而后更新一个或多个聚合字段。
基于事件溯源的应用程序的命令方法则会生成一系列事件,并应用于聚合以更新其状态。
乐观锁通常使用版本列来检测聚合自读取以来是否已更改。只有当前版本和应用程序读取聚合时版本一致,此UPDATE语句才会成功。
可以将事件溯源作为可靠的事件发布机制。将这些持久化保存的事件传递给所有感兴趣的消费者。
使用轮询发布事件
关于确定新事件,让事件发送方记录它已处理的最后一个eventId,使用select语句查询新事件,问题在于事务可以按照与生成事件不同的顺序提交,事件发布方可能意外跳过事件,解决方案是向EVENTS表添加一个列,以跟踪事件是否已发布。
使用事务日志拖尾技术来可靠地发布事件
长生命周期的聚合可能有大量事件,可定期持久保存聚合状态的快照。应用通过加载最新快照以及仅加载快照后发生的事件来快速恢复聚合状态。
基于关系型数据库事件存储库的幂等消息处理
将message ID插入PROCESSED_MESSAGES表,作为插入EVENTS表的事件的事务的一部分,以检测和丢弃重复消息。
基于非关系数据库事件存储库的幂等消息处理
NOSQL的事件存储库事务模型功能有限,简单的解决方案是消息的ID存储在处理它时生成的事件中,通过验证聚合的所有事件中是否有包含该消息的ID来做重复检测。
事件的结构经常随着时间的推移而变化,应用程序可能需要处理多个事件版本。
事件结构的演化
服务的领域模型随着时间的推移而发展,向事件添加字段,不大可能影响接收方。但更改字段名词等操作不向后兼容。
通过向上转换来管理结构的变化
事件溯源应用可以使用类似Flyway的方法处理向后兼容的更改。从事件存储库加载事件时,将各个事件从旧版本更新为新版本。
使用事件溯源的程序将事件存储在事件存储库,事件存储库是数据库和消息代理功能的组合。
一些专用事件存储库:如Event Store、Lagom、Axon、Eventuate。
如Eventuate Local包含一个存储事件的事件数据库(MySQL),一个向订阅者传递事件的事件代理(Kafka),以及一个将事件数据库中存储的事件发布到消息代理的事件中继。
事件溯源的事件驱动属性使得实现基于协同式的Saga非常简单,当聚合被更新,它会发出一个事件。不同聚合的事件处理程序可以接受事件,并更新聚合。事件溯源代码提供了Saga所需的机制,包括消息传递的进程间通信、消息去重等。
但问题在于,事件体现处理双重目的,使用事件来表示状态更改,但是使用事件实现Saga协同,需要聚合即使在没有状态更改也必须发出事件。
最好使用编排式来实现复杂的Saga。
基于事件溯源的业务逻辑与基于编排的Saga相结合更具挑战性。
当关系型数据库作为事件存储库时,应该如何创建Saga编排器
它可以在同一个ACID事务中更新事件存储库并创建Saga编排器。
当非关系型数据库作为事件存储库时,应该如何创建Saga编排器
使用基于NOSQL的事件存储库的服务很可能无法以原子方式更新事件存储库并创建Saga编排器。服务必须具有一个事件处理程序,该事件处理程序将创建Saga编排器来响应聚合发出的领域事件,它必须处理重复事件,至少一次消息传递意味着可以多次调用创建Saga的事件处理程序。
命令式消息的幂等处理
Saga参与方在处理消息时生成的事件中记录消息ID。在更新聚合之前,Saga参与方通过在事件中查找消息ID来验证它之前是否处理过该消息
以原子方式发送回复事件
Saga编排器可以订阅聚合发出的事件,但这方法存在两个问题。
1、Saga命令可能不会实际改变聚合的状态,聚合不会发出事件
2、需要Saga编排器区别处理使用事件溯源的Saga参与方与不使用事件溯源的Saga参与方。
更好的方法是让Saga参与方继续向Saga编排器的回复通道发送回复消息。
使用事件溯源持久化Saga编排器
可使用以下事件持久化Saga:
可靠地发送命令式消息
Saga编排器使用两步命令:
1、Saga编排器发送SagaCommandEvent,这些事件存储在事件存储库中
2、事件处理程序处理SagaCommandEvents并将命令式消息发送到目标消息通道。
这两步法可确保命令至少发送一次。
保证唯一的SagaCommandEvent的ID被用作命令式消息的ID,重复的消息具有相同的ID,接收重复命令式消息的Saga参与者将使用前述机制丢弃它。
确保只处理一次回复消息
Saga编排器还需要检测并丢弃重复的回复消息,可以将回复消息的ID存储在处理回复时发出的事件中,然后它可以确定消息是否重复。