Event-Sourcing模式使用仅附加存储来记录或描写叙述域中数据所採取的动作,从而记录完整的一系列系列事件,而不是仅存储实体的当前状态。由于存储包括全部的事件,能够用来具体化域对象。
Event-Sourcing模式能够简化复杂的域中的任务,避免了数据模型和业务领域的同步和引发的争用问题;增强性能,扩展性,以及响应;为事物数据提供一致性。保留全部的事件运行历史,能够跟踪和实现回滚之类的补偿操作。
问题
大多数的应用都会涉及到数据的处理,而通常的方法是由应用来保证数据的状态,当用户请求数据的时候。就会更新数据的状态。举个样例。在传统的CRUD模型中,典型的数据处理过程是,从数据仓库读取数据。做出一些改动。使用新的数据更新(通常通过事务来保护数据)当前数据的状态。CRUD的方式会有例如以下的一些限制:
- 其实,CRUD系统直接针对数据存储运行更新操作,可能会受到数据仓库的处理开销而影响系统的性能和响应时间,和限制的可扩展性。
- 在很多并发用户的协作域中,数据更新有非常大的可能发生冲突,由于更新操作都是发生在单个数据项上。
- 除非有一个额外的检查跟踪机制,它记录在一个单独的日志中的每一个操作的具体信息。否则,数据项的操作历史会丢失。
想要更深入的了解CRUD架构的一些限制,能够參考微软的CRUD, Only When You Can Afford It一文。
解决方式
Event-Sourcing模式定义了一种处理由一系列事件来驱动的数据操作的方法。每一事件都记录在一个仅能追加的存储中。应用将生成一系列事件来描写叙述每一个动作,每一个事件相应存储数据发生的变更,然后将这些事件持久化。每一个事件都表示一组数据的变化(比方在订单中添加数据项)。
事件能够持久化到事件存储仓库中。事件存储仓库作为对当前数据的状态的数据源或记录系统(数据源或信息的权威数据源)。事件存储一般会公布这些事件,以便能够通知消费者,并在消费者有须要的时候处理它们。比如。消费者能够初始化一些任务将事件中的操作应用于其它系统来运行,或运行完毕该操作所需的不论什么其它相关操作。
注意,生成事件的应用程序代码与订阅事件的系统是解耦的。
通常来说,事件存储所公布的事件是用来在应用程序对实体进行改动的时候,更新实体的Materialized视图,并与外部系统集成。比如,系统能够维护全部客户订单的Materialized视图。这些订单信息用于填充UI的部分。当应用程序加入新的订单,加入或删除项目的顺序,并加入航运信息,消费者将处理描写叙述这些变化的事件,并更Materialized视图。
參考Materialized-View模式来了解很多其它的具体信息。
此外,事件存储能够让应用在不论什么时候读取事件的历史,并通过对事件历史进行有效地“回放”和消耗有关该实体的全部事件来计算实体的当前状态。非常多时候,可能会由于client请求,或通过一个预定的定时任务,来又一次计算实体的当前状态。
当中。定时任务能够将计算的状态生成Materialized视图。并存储起来来提高查询效率。
图1展示了Event-Sourcing模式逻辑的概览图。当中也包括了使用事件流来创建一个Materialized视图,并与其它外部应用和系统集成,以及通过反复事件流来展示某些实体的当前状态。
图1. 关于Event-Sourcing模式的样例和概览
Event-Sourcing模式有非常多的长处,包括:
- 事件都是不可变对象,而且存储在一个仅支持追加的队列中。不论什么用户界面。workflow。或者程序触发了产生事件的行动,都能够以非堵塞任务继续运行,而且这些任务能够都在后台持续运行。
这一点,能够令结合事务运行过程中不会产生不论什么争用,能够极大地提高应用程序的性能和可扩展性,特别是对于用户界面的展示。
- 事件是描写叙述事件发生的简单对象,以及描写叙述事件所代表的动作所需的相关数据。事件不会直接更新数据存储区,它们仅仅是在适当的时候进行记录的生产者。这些方面都能够简化系统的实现和管理。
- 事件通常在某些领域都有各自的特殊含义,相对而言,对象可能仅仅是和数据库的表相相应的,是无法表示其在领域中的真实意义的。表仅仅能表示系统的当前状态,而表现不了发生的事件。
- Event-Sourcing能够有效降低并发更新所引起的冲突,由于Event-Sourcing避免了直接更新数据存储区中对象状态的要求。
当然,域模型仍然须要设计来保护其自身的一致性。
- 仅追加的时间存储能够提供Trace的线索,用来监測对数据存储区所採取的行动,并在不论什么时间计算当前实体状态的Materialized视图或运行事件的回放,协助測试和系统调试。
此外。使用补偿事件取消更改的要求提供了一个逆转状态的历史记录,假设模型仅仅存储当前状态,补偿回滚是无法实现的。事件列表也可用于分析应用程序性能和检測用户的行为趋势。以获得其它实用的业务信息。
- Event-Sourcing的事件存储解耦了其生产者(产生事件的任务)和消费者(生成Materialized视图的任务等)。为系统的提供了更好灵活性和可扩展性。比如。处理事件存储所公布的事件仅仅知道事件的性质和包括的数据。运行任务的方式与触发事件的操作是解耦的。此外。能够使用多个任务能够处理事件。这一特点使得事件仓库能够非常easy的配合其它服务和系统进行集成,仅仅须要侦听由事件存储引发的新事件就可以。然而,Event-Sourcing往往是非常低的level,假设有必要的话。能够对基础事件进行简单的聚合再来配合其它系统进行集成。
Event-Sourcing通常都是配合CQRS模式来运行数据管理任务的。
实现Event-Sourcing模式的问题和顾虑
当在考虑实现Event-Sourcing模式的时候,须要考虑例如以下一些问题:
- 整个系统在创建Materialized视图或通过回放事件数据生成预測的时候都是不一致的,仅仅能满足终于一致性的。应用程序将事件加入到事件存储区的过程与处理请求的结果、正在公布的事件以及处理它们的事件的消费者之间存在一些延迟。
在此期间,描写叙述实体的新的更新的新事件可能才到达事件存储区。
能够參考Data Consistency Primer来了解终于一致性方面的信息
- 事件存储是不可变的信息源,因此事件数据不应该被更新的。更新实体以撤消更改的唯一方法是向事件存储加入补偿事件。就像在交易系统中使用负事务一样。假设持久化事件的格式(而不是数据)须要更改,比方在迁移过程中。将存储中的现有事件与新版本号事件相结合非常难。可能须要遍历全部事件的变化使事件符合新的格式。考虑在事件结构中定义版本号,并对每一个版本号上使用版本号标记,以维护新旧事件的不同格式。
- 多线程应用程序和多个应用程序实例都可能在事件存储区中存储事件。事件存储中事件的一致性是至关重要的。由于这将影响特定实体的事件顺序(实体发生变化的顺序影响其当前状态)。
在每一个事件中都加入一个时间戳,能够帮助避免问题。
还有一个常见的做法是每一个产生的事件都标记一个增量标识符。假设两个操作试图同一时候为同一实体加入事件时。则事件存储能够拒绝与现有实体标识符和事件标识符相匹配的事件。
- 在遍历事件来获取信息这方面,是没有标准的方法或者一些诸如SQL查询的內建机制的。数据的提取仅仅能通过标识符,获取事件流的方式来。事件ID通常映射到单个实体。一个实体的当前状态。仅仅能通过回放全部涉及对该单位的原始状态的事件来计算出来。
- 事件流的长度也会影响管理和更新系统。假设事件流非常大,计算负载高。能够考虑每隔一定的时间间隔创建快照,比如指定的事件数。
这样,实体的当前状态能够从快照和重放那个时间点之后发生的不论什么事件中获得。
想了解针对数据建立快照方面很多其它的信息。能够參考Martin Fowler的Enterprise Application Architecture website以及Master-Subordinate Snapshot Replication on MSDN.
- 即使事件获取将数据冲突更新的可能性降到最低,应用程序仍然必须能够处理可能通过终于一致性和事务缺乏而出现的不一致。比如,表示库存降低的事件可能到达数据存储区,而该项目的还在进行下单,从而导致协调两个操作的出现冲突,就须要应用能够处理这样的相似的冲突。可能须要通知客户撤单或创建回订单。
- 事件公布可能发生多次,所以事件的消费操作必须是幂等的。
事件的消费者必须能够处理反复的通知操作。比如,假设一个实体的某一个属性由多个应用实例维护,比方订单总数,那么,当下订单的时候。必须仅仅有一个成功添加订单总数的事件的发生。尽管这不是Event-Sourcing的固有特性,可是通常来说。实现Event-Sourcing都要考虑这一点。
何时使用Event-Sourcing模式
Event-Sourcing模式非常适合下面场景:
- 当开发人员希望捕获数据中的“意图”、“目的”或“原因”时。非常适合使用Event-Sourcing。
比如,对客户实体的更改能够记录为一系列特定的事件,如搬家、关闭账户等。
- 当尽量降低更新数据的发生争用的时候。使用Event-Sourcing是非常合适的。
- 当开发人员希望记录发生的事件,并能够重放它们以恢复系统的状态;使用事件来回滚系统变更;或简单地将事件作为历史和审计日志等情况下,均适合使用Event-Sourcing模式。
比如,当任务涉及多个步骤时。您可能须要运行回滚某个更新的操作,然后又一次运行一些步骤,使数据又一次恢复到一致状态。
- 当使用事件是应用程序运行的一个自然特性,而且不须要太多的额外开发或实现工作的时候,使用Event-Sourcing模式非常合适。
- 当开发人员须要将输入或更新数据的过程与应用这些操作所需的任务解耦时,适合使用Event-Sourcing模式。
这样的方式也能够用来改善UI性能,或者将事件分发给其它监听器,如其它应用程序或服务,来进行集成。
- 当开发人员想能够依据需求灵活改变Materialized模型和实体数据的格式的情况下。当开发人员须要在使用CQRS模式的时候,适配读模型或者视图的时候。
- 当配合CQRS模式共同使用时,接受终于一致性的延迟,以及同意依据事件又一次计算读模型或者Materialized视图的状态的情况下。也能够使用Event-Sourcing模式
Event-Sourcing模式在例如以下的情况下不太适用:
- 领域模型非常小,或者业务较为简单,系统仅仅有非常少或没有业务逻辑,或非域系统。与CRUD架构就能非常好满足需求的情况下,不适合使用Event-Sourcing。
- 系统对一致性和实时更新的数据视图要求较高的时候,不适合使用Event-Sourcing模式。
- 系统不须要审计跟踪、历史和回滚和重放操作的功能的时候,由于复杂性的原因,不适合使用Event-Sourcing模式。
- 数据更新冲突非常少的情况下,并不适用于使用Event-Sourcing。比如,系统的主要工作是加入数据而不是更新数据的情况。
使用举例
某个会议管理系统须要跟踪完毕预订的会议,该系统能够在某个希望參加会议的人尝试申请參加的时候,检查是否还有空暇座位。该系统能够以至少两种方式来存储预定库存的总数:
- 该系统能够将预订库存总量的信息作为一个单独的实体在存储在数据库中。
能够预订或取消,系统能够适当的添加或降低这个数字。这样的方法在理论上是非常简单,可是假设大量的预定的人在非常多的时间内试图预订的座位的话,可能导致扩展性问题。比如,在预订期结束之前的最后一天内,可能有大量的预定请求。
- 该系统能够将预订和取消的事件存储到数据仓库中。库存总量能够通过遍历这些事件的来计算。
由于事件的不变性,这样的方法的可扩展性更好。
系统仅仅须要能够从事件存储区读取数据,或将数据加入到事件存储区。关于预订和取消事件的信息不会改动的。
图2显示了怎样使用Event-Sourcing来实现会议管理系统的座席预订子系统。
图2在会议管理系统中使用Event-Sourcing获取座位预订信息
保留两个座位的动作顺序例如以下:
1. 用户界面发出一个命令,为两位与会者预留座位。
该命令由一个单独的命令处理程序处理(一个与用户界面解耦的应用逻辑,负责处理作为命令发送的请求)。
2. 通过请求全部的预定和取消会议的事件,来获取一个包括全部预定信息的数据集合。
这个数据集合叫SeatAvailability
,并支持查询当中的库存信息。
能够使用快照来做一些优化(因此开发人员不须要查询和重放事件的完整列表以获取的
SeatAvailability
的当前状态),并在内存中维护聚合的缓存副本。3.
CommandHandler
调用由域模型的方法来预定或取消。4.
SeatAvailability
记录包括座位库存。下一次计算库存数时,将遍历全部的预订时间来计算还有多少库存。
5. 该系统添加的新事件会存储在事件仓库中。
假设用户希望取消座位,该系统遵循相似的过程,由命令处理程序产生一个取消座位的事件并将其加入到事件存储中。
除了提供了更好的扩展性外,事件仓库为全部的预定和取消操作都保留了操作历史。不管是为了跟踪。或者分析挖掘,都提供了良好的支持。由于事件仓库中记录的事件是真实的唯一来源。系统没有必要持久化其它的状态信息。由于能够非常easy地又一次遍历事件并计算不论什么时间点实体状态。
开发人员能够在Introducing Event Sourcing中更具体的了解这个样例。
相关的其它模式
当考虑实现Event-Sourcing模式的时候,也能够參考例如以下相关模式:
- CQRS模式.CQRS实现中所提供的不可变信息源的写存储通常就是基于Event-Sourcing模式的一种实现。
CQRS模式描写叙述了怎样将操作。读取数据和应用程序的操作,通过使用单独的更新数据的接口来分离职能。
- Materialized-View模式.在Event-Sourcing模式中所使用的数据仓库,通常来说是不利于查询的。
通常提高查询效率的方式,就是每隔一定的时间,依据数据仓库生成预填充的视图来提高查询效率。Materialized-View模式描写叙述了改功能是怎样实现的。
- Compensating-Transaction模式. 在实现Event-Sourcing模式中的数据仓库中的数据是不会运行更新操作的。通常,都会通过添加额外的事件来运行回滚等操作,来恢复实体的状态。Compensating-Transaction模式描写叙述怎样撤消由前一个操作运行的工作。
- Data Consistency Primer. 当使用Event-Sourcing时,读数据仓库或者Materialized视图是不会保证实时一致的。相对来说,他们会保证终于一致性。Data Consistency Primer中讲述了分布式数据保证一致性的诸多问题。
- Data Partitioning Guidance. 在使用Event-Sourcing的时候。为了提升扩展性,优化性能,降低争用。会考虑对事件存储进行分区。Data Partitioning Guidance中描写叙述了怎样将数据进行分区,以及分区中可能产生的问题等。