事件溯源
事件溯源是构建业务逻辑和持久化聚合的另一种选择。
通过聚合一系列事件的方式持久化保存。每个事件代表聚合的一次状态变化。应用程序通过重放来重新创建聚合的当前状态。
模式:事件溯源
使用一系列表示状态更改的领域事件来持久化聚合。
1. 传统持久化技术的问题
- 对象和关系的“阻抗失调”
关系型数据的表格结构模式,与领域模型及其复杂关系的图状结构之间,存在基本概念不匹配的问题。 - 缺乏聚合的历史
传统持久化的另一个限制是,它只存储聚合的当前状态。聚合更新后,其先前的状态将会丢失。
如果应用程序必须保留聚合的历史,那么必须实现相应的业务逻辑。实现这个逻辑是非常耗时的一项工作,因为其中还有涉及到复制必须与业务逻辑保持一致的代码。 - 实现审计功能非常繁琐且容易出错
为了满足安全性或监管的要求,要实现审计功能以支持。
挑战在于,除了这个是一个耗时的工作之外,负责审计日志的业务代码,可能与业务逻辑发生偏离,导致各种错误。 - 事件发布凌驾于业务逻辑之上
传统持久化的另一个限制是,它通常不支持发布领域事件。
2. 什么是事件溯源
事件溯源是一种以事件为中心的技术,用于实现业务逻辑和聚合的持久化。
事件溯源通过事件来持久化聚合
通过设置事件存储库(event表),结构如下:
evenv_id | event_type | entity_type | entity_id | event_data |
---|---|---|---|---|
101 | OrderCreated | Order | 101 | {...} |
102 | OrderApproved | Order | 101 | {...} |
103 | OrderShipped | Order | 101 | {...} |
104 | OrderDelivered | Order | 101 | {...} |
... | ... | ... | ... | ... |
记录了Order变化的事件及所需的数据,通过加载事件存储库,可重放事件加载聚合。
- 一般来说,按照以下步骤:
- 加载聚合事件列表
- 使用默认构造方法创建聚合实例
- 调用聚合事件相对应的apply方法,重放事件
- 事件溯源常用实现如下:
- 使用process方法接受命令,返回事件列表
- 使用apply方法,接受事件,更新聚合
2.1 通过事件来持久化聚合
2.2 事件代表状态的改变
2.3 聚合方法都与事件相关
2.3.1 创建聚合的步骤如下:
- 使用聚合默认的构造函数实例化聚合根
- 调用process方法,生成事件列表1
- 遍历事件列表1,并调用apply方法更新聚合的状态
- 将事件列表保存至事件存储库
2.3.2 更新聚合的步骤如下:
- 从事件存储库加载事件列表1
- 使用其默认构造函数实例化聚合根
- 遍历加载的事件列表1,并在聚合根上调用apply方法
- 调用process方法以生成事件列表2
- 遍历事件列表2,并调用apply方法更新来聚合的状态
- 将新事件存储至事件存储库
2.4 并发更新
两个或多个请求同时更新同一聚合。
对于并发,避免问题出现的方式是,进行串行化的处理。
对事件存储库新增一列,is_publish
event_id is_publish 101 0 - 将事件存储至事件存储库(即时),is_publish为0
- 投递事件至消息代理
- 将事件标记为已发布,is_publish=1
事件执行反馈记录,这里不聊
2.5 领域事件的演化
事件溯源的结构分为三个层次:
- 由一个或多个聚合组成
- 定义每个聚合发出的事件
- 定义事件的结构
2.6 事件溯源的优劣
2.6.1 事件溯源的好处
可靠的发布领域事件
保留聚合的历史
最大程度的避免对象和关系的“阻抗失调”
为开发者提供一个时光机
2.6.2 事件溯源的弊端
编程模式有一定的学习曲线
基于消息传递的应用程序的复杂性
处理事件的演化有一定的难度
随着时间的推移,领域模型及结构都可能会有调整。
- 迁移旧结构,以支持当前最新的领域模型
- 代码中增加适配器,以兼容旧结构
删除数据有一定的难度 - 事件溯源本身是要保留聚合的历史,永久的保存数据,所以传统的做法是进行软删除
- 使用软删除使用多种数据,但是根据欧洲的数据保护和隐私法规(GDPR),应用程序必须彻底删除用户的个人信息。
做法是,通过用户设置UUID,根据UUID关联敏感数据。删除数据时,删除关联即可。 - 查询事件存储库非常有挑战性
需要在事件存储库,找到没有直接搜索条件的数据。
通过CQRS可实现该查询
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。