现在,每个开发人员都很熟悉MVC标准体系结构设计模式。大多数的应用程序都是基于这种体系结构进行创建的。它允许我们创建可扩展的大型企业应用程序,但近期我们还听到了另外的一些有关于CQRS/ES的相关信息。这些方法应该被放在MVC中一起使用吗?他们可以解决什么问题?现在,让我们一起来看看CQRS/ES是什么,以及他们都有哪些优点和缺点。
CQRS — 模式介绍
CQRS(Command Query Responsibility Segregation)是一种简单的设计模式。它衍生与CQS,即命令和查询分离,CQS是由Bertrand Meyer所设计。按照这一设计概念,系统中的方法应该分为两种:改变状态的命令和返回值的查询。Greg young将引入了这个设计概念,并将其应用于对象或者组件当中,这就是今天所要将的CQRS。它背后的主要思想是应用程序更改对象或组件状态(Command)应该与获取对象或者组件信息(Query)分开。
下面,将通一张图来说明应用程序中有关CQRS部分的组成结构:
Commands(命令)—表示用户的操作意图。它们包含了与用户将要对系统执行操作的所有必要信息。
- Command Bus(命令总线):是一种接收命令并将命令传递给命令处理程序的队列。
- Command Handler(命令处理程序):包含实际的业务逻辑,用于验证和处理命令中接收到的数据。Command handler负责生成和传播域事件(Event)到事件总线(Event Bus)。
- Event Bus(事件总线):将事件发布给订阅特定事件类型的事件处理程序。如果存在连续的事件依赖,事件总线可以使用异步或者同步的方式将事件发布出去。
- Event Handler(事件处理程序):负责处理特定类型的事件。它们的职责是将应用程序的最新状态保存到读库中,并执行终端的相关操作,如发送电子邮件,存储文件等。
Query(查询):表示用户实际可用的应用程序状态。获取UI的数据应该通过这些对象完成。
下面我们将介绍有关CQRS的诸多优点,它们是:
- 我们可以给处理业务逻辑部分和处理查询部分的开发人员分别分配任务,但需要小心的是,这种模式可能会破坏信息的完整性。
- 通过在多个不同的服务器上扩展Commands和Query,我们可以进一步提升应用程序的读/写性能。
- 使用两个不同的数据库(读库/写库)进行同步,可以实现自动备份,无需额外的干预工作。
- 读取数据时不会涉及到写库的操作,因此在使用事件源是读数据操作会更快。
- 我们可以直接为视图层构建数据,而无需考虑域逻辑,这可以简化视图层的工作并提高性能。
尽管使用CQRS模式具有上述诸多的优点,但是在使用前还需要慎重考虑。对于只具有简单域的简单项目,其UI模型与域模型紧密联系的,使用CQRS反而会增加项目的复杂度和冗余度,这无疑是过度的设计项目。此外,对于数据量较少或者性能要求较低的项目实施CQRS模式不会带来显著的性能提升。
Event Sourcing — 案例研究
有这样一个案例,我们想要检索任何一个域对象的历史状态数据,而且在任何时间都可以生成统计数据。我们想要检查上个月、上个季度或者过去任何时间的状态汇总。想要解决这个问题并不容易。我们可以在特定的时间范围内将额外的数据保存在数据库中,但这种方法也存在一些缺点。我们不知道范围应该是什么样子,以及未来统计数据需要哪些数据项。为了避免这些问题,我们可以每天为所有聚合创建快照,但它们同样会产生大量的冗余数据。
Event Sourcing(ES)似乎是目前解决这些问题的最佳方案。Event Sourcing允许我们将Aggregate(聚合)状态的每一个更改事件保存在Event Store的事件存储库中。通过Command Handler将事件写入到事件存储库中,并处理相关的逻辑。要创建Aggregate(聚合)对象的当前状态,我们需要运行创建预期域对象的所有事件并对其执行所有的更改。下面我们将通过一张图来说明这一架构设计方式:
下面我们将列举一些使用ES的优点:
- 时间穿梭机:可以及时重建特定聚合的状态。每个事件都包含一个时间戳。根据这些时间戳可以在特定的时间内运行事件或者停止事件。
- 自动审计:我们不需要额外的工作就可以检查出在特定的时间范围内谁做了什么以及改变了什么。这和可以显示更改历史记录的系统日志不同,事件可以告知我们每次更改背后所对应的操作意图。
- 易于引入纠正措施:当数据库中的数据发生错误时,我们可以将应用程序的状态回退到特定的时间点上,并重建当时的应用程序状态。
- 易于调试:如果应用程序出现问题,我们可以将特定事件内的所有事件取出,并逐条的重建应用状态,以检查应用程序可能出现问题的地方。这样我们可以更快的找到问题,缩短调试所需的时间。
Aggregates
Aggregate(聚合)一词在本文中多次被提及,那它到底是什么意思?Aggregate(聚合)来自于领域驱动设计(DDD)的一个概念,它指的是始终保持一致状态的实体或者相关实体组。我们可以简单的理解为接收和处理Command(包含Command Handler)的一个边界,然后根据当前状态生成事件。在通常情况下,Aggregate root(聚合根)由一个域对象构成,但它可以由多个对象组成。我们还需要注意整个应用程序可以包含多个Aggregate(聚合),并且所有事件都存储在同一个存储库中。
总结
CQRS/ES可以作为特定问题的解决方案。它可以在标准N层架构设计的应用程序的某些层中进行引入,它可以解决非标准问题,常规架构中我们所拿到的是最终状态,在很多情况下,固然当前状态很重要,但我们还需要知道当前状态是如何产生的。CQRS和ES两种概念应该一起使用吗?事实表明,并没有。我们想要统计任何时间范文内的域对象状态,而写库只能存储当前状态。引入CQRS并没能帮助我们解决这一问题。在下一章节中,我们将引入Axon框架,Axon框架时间了CQRS/ES,用于解决某些域对象的一些特定问题,尤其是收集历史统计数据。我们将阐述如何使用Axon框架实现CQRS/ES并实现与Spring Boot应用程的整合。