Event-Sourcing和CQRS实战案例
任何试图实现一个完全符合标准的ACID系统的人都知道:你必须顾及很多方面。需要确保数据库实体在事务ACID前提下可以自由创建、修改和删除,而不会有错误风险,在大多数情况下,这种围绕数据库的CRUD总是会有性能方面的损失。这里介绍另外一个方法,设计的基础是基于一系列事件而不是基于数据库这样的可变状态系统。这种方式通常被称为事件溯源Event Sourcing。
本文介绍使用基于Speedment快速开发一个可扩展的Event-Sourced数据库应用,完整代码见这里
什么是事件溯源?
在一个典型的关系数据库系统中,您将实体的状态存储为数据库中的一行。当状态更改时,应用程序使用更新或删除语句修改该行。这种方法的问题是,它增加了对数据库技术的很多要求,要确保没有记录行被改变到一个非法的状态。
在一个事件来源event-sourced系统中,我们采取了一种不同的方法。不是存储实体状态在数据库中,存储的是一系列改变状态的操作,即事件,事件是状态改变的原因。一个事件一旦被创建是不可变的,这意味着你只需要实现两个操作,CREAT创建和读取READ。如果一个实体被更新或删除,那是创建一个“UPDATE更新”或“REMOVE删除”事件来实现。
一个事件源系统可以很容易地扩展提高性能,因为任何一个节点都可以简单地下载事件日志和重播当前状态。你也得到更好的性能,由于这样的事实,写作和查询是由不同的机器完成,这被称为cqrs(命令查询职责分离)。
案例:预订桑拿房
为了展示建立事件源系统的工作流程,我们将创建一个小应用程序来处理一个共享桑拿房的预订。我们有多个租户感兴趣预订桑拿浴室,但我们需要保证,害羞的住户从来不会意外地预订两次;我们也希望在同一系统中支持多个桑拿。
用数据库来简化通信,我们将使用speedment工具包。speedment是java的工具,使我们能够从数据库中生成一个完整的领域模型,并可以很容易地使用java 8流实现数据库查询优化。
第一步:定义数据库Schema
第一步是要确定我们的数据库(MySQL)。我们只有一个表称为“booking”,存储预订桑拿相关的事件。请注意,预订是一个事件,而不是一个实体。如果我们想取消预订或更改它,我们将不得不发布新的事件作为新的记录插入,而不是修改现有记录行。我们不允许修改或删除已插入的记录行。
|
“id”列是一个不断增加的整数,每次都会自动分配一个新的事件被发布到日志中。“booking_id”指的是预订。如果两个事件共享相同的预订标识,他们将指向同一个实体。我们也有一个枚举类型称为“event_type”描述我们想做的哪种操作。之后列是有关预订的信息。如果一列是null,我们会认为是与以前的值相比没有修改过。
第二步:使用Speedment产生代码
下一步是使用speedment项目生成代码。简单的创建一个新的Maven项目,将下面的代码添加到pom.xml-file。
|
如果你构建build这个项目,一个新的Maven目标称为speedment:tool应该出现在IDE中。运行它,加载speedment推出的用户界面。在那里,连接到Sauna数据库,并使用默认设置生成代码。该项目现在应该存在了自动生成的源文件。
第三步:创建物化视图Materialized View
物化视图是定期轮询数据库的一个组件,以查看是否已添加了新的记录行,如果有的话,下载并将它们以正确的顺序合并到视图中。由于轮询有时需要花很多时间,我们希望这个进程在一个单独的线程中运行。我们可以用一个java Timer和TimerTask实现。
轮询数据库?真的?嗯,要考虑的一个重要的事情是,它只是轮询数据库的服务器,而不是客户端。这给我们提供了很好的可扩展性,因为我们可以有几个服务器轮询数据库,反过来又为成千上万的租户提供服务。
见下: