ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

(译)使用Spring Boot和Axon实现CQRS&Event Sourcing

(译)使用Spring Boot和Axon实现CQRS&Event Sourcing

上一篇中,我们讲述了CQRS和Event Sourcing的相关概念以及他们能解决什么问题。尽管可以在不适用任何其他框架或库的情况下实现CQRS/ES,但我们还是建议使用已有的一些工具。这些工具可以简化开发过程,同时运行开发人员专注于业务逻辑的处理,避免重复的造轮子。在本节中,我们将选择Axon框架来实现CQRS/ES。

什么是Axon?

Axon是一个轻量级的Java开源框架,可以帮助构建你构建基于CQRS模式的可伸缩、可扩展和库维护的Java应用程序。它还可以帮助你准备Event Sourcing所需要的环境。Axon提供了所有重要构建模块的实现,如聚合、存储库,命令和时间总线。Axon可以让开发人员的工作更为轻松。

为何选择Axon?

Axon让我们避免了负载的配置和对数据流的操作,我们可以专注于应用程序业务规则的定制,而不是创建样板代码。使用Axon可以获得如下的一些优势:

  • 优化事件处理:我们应该注意到,事件的发布具有先后顺序。Axon框架保证了事件被执行的先后顺序。
  • 内置测试环境:Axon提供了一个测试工具集,允许在特定的时间上对系统进行测试,这使得单元测试更为容易。
  • Spring Boot AutoConfiguration:在Spring Boot应用程序中配置Axon是一件非常简单的事情。只需要提供必要的依赖项,Axon将会自动配置一些基本组件。
  • 支持注解:Axon提供了注解支持,这使得我们的代码更清晰可读,我们可以使用这些注解轻松构建聚合和事件处理程序,而无需关心Axon特定的处理逻辑

Spring Boot应用程序中快速配置Axon

在默认情况下,Axon以经提供了对Spring Boot的集成支持。我们只需要通过一些简单的配置步骤就可以将Axon与Spring Boot整合在一起。

步骤1

第一步是使用合适的项目构建工具在项目中配置Axon依赖项。以下是使用Gradle来配置Axon的方法:

dependencies{
    compile("org.axonframework:axon-spring-boot-starter:3.2")
    compile("org.axonframework:axon-mongo:3.2")
    testCompile("org.axonframework:axon-test:32.")
}

第一个依赖项为我们提供了与Spring Boot集成的最基本的Axon所有必要组件,如命令中线,事件总线和聚合。第二个依赖项是为我们的聚合或事件配置库提供所需的基本环境。最后一个依赖项用于构建先关的测试环境。

步骤2

根据需要配置Axon所需要的一些Spring Bean。比如EventHandlerConfiguration(负责控制事件处理程序行为的组件),如果其中有一个事件执行失败,则终止处理后续的所有事件。当然这是非必须的,但是还是值得在应用程序中进行此配置,以防止系统中数据的不一致。配置代码如下:

@Configuration
public class AxonConfig {

private final EventHandlingConfiguration eventHandlingConfiguration;

@Autowired
public AxonConfig(EventHandlingConfiguration eventHandlingConfiguration) {
   this.eventHandlingConfiguration = eventHandlingConfiguration;
}

@PostConstruct
public void registerErrorHandling() {
   eventHandlingConfiguration.configureListenerInvocationErrorHandler(configuration -> (exception, event, listener) -> {
       String msg = String.format(
               "[EventHandling] Event handler failed when processing event with id %s. Aborting all further event handlers.",
               event.getIdentifier());
       log.error(msg, exception);
       throw exception;
   });
}}

这里的主要思想是创建一个额外的配置文件(使用@Configuration注释的类)。该类的构造函数注入由Spring自身所管理的EventHandlingConfiguration依赖项。由于绑定依赖,我们可以在此对象上调用configureListenerInvocationErrorHandler()并通过记录异常将异常传播到上层去处理错误。

步骤3

我们使用MongoDB来存储Event Store中所发生的所有事件。要实现此功能,可以通过如下的方法来实现:

@Bean
public EventStorageEngine eventStore(MongoTemplate mongoTemplate) {
   return new MongoEventStorageEngine(
           new JacksonSerializer(), null, mongoTemplate, new DocumentPerEventStorageStrategy());
}

这样,在事件总线上发布的所有事件都将自动保存到MongoDB数据库中。通过这种简单的配置,我们就可以在应用程序中使用MongoDB的数据源。

在Axon配置方面就是这样的简单。当然,我们还有其他很多的配置方式。但我们可以通过上述简单的配置,就可以使用Axon的功能了。

使用Axon实现CQRS

根据上图,创建命令,将命令传递给命令总线然后创建事件并将事件放在事件总线上还不是CQRS。我们必须记住改变写入存储库的状态并从读取数据库中读取当前状态,这才是CQRS模式的关键点。

配置此流程也并不复杂。在将命令传递给命令网关时,Spring将名利类型作为参数以搜索带有@CommandHandler 注释的方法。

@Value
class SubmitApplicationCommand {
   private String appId;
   private String category;
}

@AllArgsConstructor
public class ApplicationService {
   private final CommandGateway commandGateway;

   public CompletableFuture<Void> createForm(String appId) {
       return CompletableFuture.supplyAsync(() -> new SubmitExpertsFormCommand(appId, "Android"))
               .thenCompose(commandGateway::send);
   }
}

除其他事项外,命令处理程序负责将创建的事件发送到事件总线。它将事件对象放置到AggregateLifecycle静态导入的apply()方法中。然后调度该事件以查找预期的处理程序,并且由于我们配置了事件存储库,所有事件都自动保存在数据库中。

@Value
class ApplicationSubmittedEvent {
   private String appId;
   private String category;
}

@Aggregate
@NoArgsConstructor
public class ApplicationAggregate {
   @AggregateIdentifier
   private String id;

   @CommandHandler
   public ApplicationAggregate(SubmitApplicationCommand command) {
      //some validation
       this.id = command.getAppId;
       apply(new ApplicationSubmittedEvent(command.getAppId(), command.getCategory()));
   }
}

要更改写库的状态,我们需要提供一个使用@EventHandler注释的方法。该应用程序可以包含多个事件处理程序。他们每个人都应该执行一个特定的任务,如发送电子邮件,记录或保存在数据库中。

@RequiredArgsConstructor
@Order(1)
public class ProjectingEventHandler {
   private final IApplicationSubmittedProjection projection;

   @EventHandler
   public CompletableFuture<Void> onApplicationSubmitted(ExpertsFormSubmittedEvent event) {
       return projection.submitApplication(event.getApplicationId(), event.getCategory());
   }

如果我们想确定所有事件处理程序的处理顺序,我们可以用@Order注释一个类并设置一个序列号。submitApplication()方法负责进行所有必要的更改并将新的数据存储到写库中。

这些都是使我们的应用程序采用CQRS模式原则的关键点。当然,这些原则只能应用于我们应用程序的某些部分,具体取决于业务需求。Event Sourcing不适合我们正在构建的每个应用程序或模块。在实现此模式时也要谨慎,因为更复杂的应用程序可能难以维护。

总结

使用Axon框架,CQRS和Event Sourcing的实现变得简单化。有关高级配置的更多详细信息,请访问Axon的网站https://docs.axonframework.org/

原文作者:ŁukaszKucik

原文地址:https://www.nexocode.com/blog/posts/smooth-implementation-cqrs-es-with-sping-boot-and-axon/

译 者:谭朝红

译文地址:https://www.ramostear.com/articles/impl_cqrs_es_by_spring_boot_and_axon.html

(译)CQRS & Event Sourcing — 解决检索应用程序状态问题的一剂良方

(译)CQRS & Event Sourcing — 解决检索应用程序状态问题的一剂良方

CQRS & Event Sourcing — 解决检索应用程序状态问题的一剂良方

现在,每个开发人员都很熟悉MVC标准体系结构设计模式。大多数的应用程序都是基于这种体系结构进行创建的。它允许我们创建可扩展的大型企业应用程序,但近期我们还听到了另外的一些有关于CQRS/ES的相关信息。这些方法应该被放在MVC中一起使用吗?他们可以解决什么问题?现在,让我们一起来看看CQRS/ES是什么,以及他们都有哪些优点和缺点。

CQRS — 模式介绍

CQRS(Command Query Responsibility Segregation)是一种简单的设计模式。它衍生与CQS,即命令和查询分离,CQS是由Bertrand Meyer所设计。按照这一设计概念,系统中的方法应该分为两种:改变状态的命令和返回值的查询。Greg young将引入了这个设计概念,并将其应用于对象或者组件当中,这就是今天所要将的CQRS。它背后的主要思想是应用程序更改对象或组件状态(Command)应该与获取对象或者组件信息(Query)分开。

下面,将通一张图来说明应用程序中有关CQRS部分的组成结构:

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(聚合)对象的当前状态,我们需要运行创建预期域对象的所有事件并对其执行所有的更改。下面我们将通过一张图来说明这一架构设计方式:

event-sourcing

下面我们将列举一些使用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应用程的整合。

作者:LukaszKucik ,译:谭朝红,原文:CQRS and Event Sourcing as an antidote for problems with retrieving application states