事件溯源Event Sourcing

一. 什么是Event Sourcing?

一个对象从创建开始到消亡会经历很多事件,以前我们是在每次对象参与完一个业务动作后把对象的最新状态持久化保存到数据库中,也就是说我们的数据库中的数据是反映了对象的当前最新的状态。而事件溯源则相反,不是保存对象的最新状态,而是保存这个对象所经历的每个事件,所有的由对象产生的事件会按照时间先后顺序有序的存放在数据库中。可以看出,事件溯源的这种做法是更符合事实观的,因为它完整的描述了对象的整个生命周期过程中所经历的所有事件。
事件溯源是把应用程序的所有变动都保存在一个事件序列。我们不仅可以查询这些事件,还可以通过事件日志重新构建过去任何时候的状态,并自动调整状态来应对追溯过的变化。使用事件溯源后,最显而易见的成果就是我们掌握了所有活动的轨迹。事件溯源的关键是要确领域对象的所有变化都由事件对象触发。这样基于事件日志可以建立很多机制:
• 完全重建:可以完全不理会应用现在的状态,因为我们可以在空应用上运行事件日志中的事件来重现应用的当前状态。
• 时态查询:我们可以查看任何时间点的系统状态,可以通过从空白状态重新运行事件直到特定的时间点或事件来实现。进一步可以使用多个时间线(类似于版本控制中的分支)。
• 事件回放:如果我们发现过去某个事件有问题,我们可以回到这个事件点,然后重播正确事件和后续事件。(或者直接忽略系统状态,按顺序重放所有事件和正确事件。)类似的技术可以处理错误顺序的事件流,这是异步消息通信的系统常见的问题。
那么,事件到底如何影响一个领域对象的状态的呢?很简单,当我们在触发某个领域对象的某个行为时,该领域对象会先产生一个事件,然后该对象自己响应该事件并更新其自己的状态,同时我们还会持久化在该对象上所发生的每一个事件;这样当我们要重新得到该对象的最新状态时,只要先创建一个空的对象,然后将和该对象相关的所有事件按照事件发生先后顺序从先到后再全部应用一遍即可还原得到该对象的最新状态,这个过程就是所谓的事件溯源。
另一方面,因为是用事件来表示对象的状态,而事件是只会增加不会修改。这就能让数据库里的表示对象的数据非常稳定,不可能存在DELETE或UPDATE等操作。因为一个事件就是表示一个事实,事实是不能被磨灭或修改的。这种特性可以让领域模型非常稳定,在数据库级别不会产生并发更新同一条数据的问题。

二. Event Sourcing包含的内容

通过上面的例子,我们理解了Event Sourcing(事件溯源),下面我们再来看看Event Sourcing包含哪些部分:

聚合对象
每个聚合对象都有一个Id,用于唯一标识这个对象,所以系统中不同的模型就会有不同的对象。

Event Store
我们说了,在Event Sourcing模式当中,所有的事件都是要保存到数据库(或其他存储,下面就直接说数据库了)中的,这个存储就叫Event Store。
每个事件应该也包含一个它要处理的聚合对象的id,以及事件的顺序,查询的时候就是根据聚合对象的id从数据库中找到相关的事件,并按照生成的事件或序号排序。
Event Store除了提供事件数据的存储、查询功能以外,还可以提供事件的重现等功能。事件的重现,就是将截止某一个时间的所有事件取出来,调用他的处理函数,生成当时那个时间点的业务状态。所以在重现之前,如果我们的业务数据的状态,通过视图的形式保存到了数据库中,我们需要先清除相应的数据。正是由于Event Sourcing模式的这个以事件为源的特性,所以我们才有可能提供这样的历史重现的功能。

聚合资源库
一般情况下,我们的聚合对象的数据状态是不会保存在数据库当中的。每当系统要获得某一个账户的数据的时候,都是从Event Store当中取出所有相关聚合对象的事件,然后依次的调用这些事件的处理方法,“聚合”出该领域对象最新的数据状态。这个,就是聚合资源库需要提供的功能。

视图
上面我们也说了,如果每次都重新“聚合”出对象,获取当前的状态,会浪费很多资源。所以,我们可以在某个事件发生的时候,将这个聚合对象的最新数据状态,写到一个表中,这个表可以叫做物化视图。

查询
由于我们提供了专门的视图表,将聚合对象的最新状态保存在数据库中,那我们在查询的时候,可以通过该物化视图去查询,而不是通过聚合对象的资源库去查询。

三. Event Sourcing与CQRS

CQRS,是 Command Query Responsibility Segregation的缩写,也就是通常所说的读写隔离。在上面,我们说,为了性能考虑,将聚合对象的数据状态用物化视图的形式保存,可以用于数据的查询操作,也就是我们把数据的更新与查询的流程隔离开来。我们通过事件来更新聚合对象的数据状态,同时由另一个处理器处理相同的事件,来更新物化视图的数据。
所以,Event Sourcing与CQRS有着天然的联系,所以也经常会有人把他们放在一起讨论。实际上,CQRS是在使用Event Sourcing模式以后,又使用了物化视图的情况下,所产生的额外的好处。
下图就是使用Event Sourcing好CQRS模式以后的一个简单的流程图:
事件溯源Event Sourcing_第1张图片
1. 对于Command类型的请求(需要修改数据),web层会走通过Event Sourcing更新聚合对象的流程,这时会有一个Event Handler的处理类监听相应事件,更新物化视图。
2. 对于Query类型的请求,web层会通过相应的DAO获取数据返回。

四. Event Sourcing的优点和缺点

优点:
1. 记录了数据完整变化过程,最详细的日志
2. 可以将系统还原到任何一个时间点的状态, 相当于重播事件日志和重新处理数据
3. Domain Event非常有业务价值,BI分析事件能预测业务未来发展情况
4. 可以有效解决线上的数据问题,线下重演一遍,就能知道哪里出问题
5. 不再需要用到ORM,所以没有O/R阻抗失衡的问题,领域模型的设计可以更OO
6. 将Command、Event串联起来,可以分析聚合根的整个变化过程,有助于排查分析问题
7. 自动并发冲突检测、命令的幂等处理
缺点:
1. 事件数量巨大,如何存储
2. 如果单个聚合根事件过多,则重演会造成性能问题
3. 领域模型重构被制约,事件的修改必须兼容以前的结构
4. 数据库订正不在有效
5. 架构实践门槛高,没有成熟框架支撑基本无望
6. 需要具备DDD领域建模的能力
7. 事件驱动状态的修改,思维转变难

五. Event Sourcing的使用场景

这种模式在以下几种场景中是最理想的解决方案:
• 当你想获得数据的“意图”,“目的”或者“原因”的时候。 例如,一个客户的实体改变可能用一系列的类似于”搬家“,”注销账户“或者”死亡“等事件类型。
• 并发更新数据时候非常需要减少或者完全避免冲突的时候。
• 当你需要保存已经发生的事件,并且能够重播他们来还原到某个状态、使用这些事件去回滚系统的某些变化或者仅仅是历史或者审查记录的时候。例如 ,当一个任务包括几个步骤,你可能需要执行一个撤销更新的操作然后重播过去的每个步骤来回到稳定的状态。
• 当使用事件是一些应用程序的某些操作的天然属性,并且需要很少的额外扩展或者实施的时候。
• 当你需要把插入,更新数据和需要执行这些操作的应用程序解耦开的时候。用这种模式可以提高UI的性能,或者把这些事件分发给其他的监听者,比如有些应用程序或系统,它们在一些事件发生的时候必须做出一些反应。例如,将一个工资系统和一个报销系统结合起来,这样的话当报销系统更新一个事件给事件数据库,数据库对此做出的相应事件就可以被报销系统和工资系统共享。
• 当要求变更或者——当和CQRS配合使用的时候——你需要适配一个读的模型或者视图来显示数据,而你想要更灵活地改变物化视图的格式和实体数据的时候。
• 当和CQRS配合使用的时候,并且当一个读模型被更新时能接受数据的最终一致性问题,或者说从一系列的事件序列中生成实体对性能的影响可以被接受。
这种模式在以下几种场景中可能并不适用:
• 小而简单的,业务逻辑简单或者根本没有业务逻辑,或者领域概念的,一般传统的增删改查(CURD)就能实现功能的业务领域,或者系统。
• 需要实时一致和实时更新数据的系统。
• 不需要审查,历史和回滚的系统。
• 并发更新数据可能性非常小的系统。例如,只增加数据不更新数据的系统。

你可能感兴趣的:(CQRS,领域驱动设计)