由于须要用到,可是在网上对应的资料实在是不多,只有迎着头皮看官网文档并配合翻译器。若有误导多多包涵。java
Axon
什么是 Axon
Axon Framework 经过支持开发人员应用命令查询责任隔离(CQRS)架构模式来帮助构建可扩展和可维护的应用程序。它经过提供最重要的构建块(例如聚合,存储库和事件总线(事件的分发机制))的实现来实现。此外 Axon 提供注释支持,它使您能够构建聚合和事件侦听器,而无需将代码与 Axon 特定的逻辑绑定在一块儿。这使您能够专一于业务逻辑而不是管道,并可使您的代码更易于独立测试。数据库
并不是每一个应用程序都会从 Axon 中受益。不指望扩展的简单 CRUD 应用程序可能不会从 CQRS 或 Axon 中受益。然而,有各类各样的应用程序确实受益于 Axon。
可能受益于 CQRS 和 Axon 的应用:编程
- (系统功能须要频繁迭代新功能)应用程序极可能在很长一段时间内使用新功能进行扩展。例如,在线商店可能从订单模块进度的系统开始。在稍后阶段,这能够经过库存信息进行扩展,以确保库存在出售时获得更新。甚至在之后,会计能够要求记录销售的财务统计,等等。虽然很难预测软件项目在将来将如何发展,但大多数这类应用程序都是这样的。
- (频繁读写的应用)应用程序具备高的读写比。这意味着数据只写几回,并屡次读。因为查询的数据源与用于命令验证的数据源不一样,所以能够优化这些数据源以实现快速查询。重复数据再也不是问题,由于数据更改时会发布事件。
- 应用程序以多种格式显示数据。如今,许多应用程序在网页上显示信息时不会中止。例如,一些应用程序每个月发送电子邮件,通知用户可能发生的与其相关的更改。搜索引擎是另外一个例子。它们使用的数据与应用程序使用的数据相同,但以一种为快速搜索而优化的方式。报表工具将信息聚合到显示数据随时间演变的报表中。一样,这也是同一数据的不一样格式。使用 Axon,能够在实时或计划的基础上独立地更新每一个数据源。
- 当应用程序明显地将组件与不一样的对象分开时,它也能够从 Axon 中受益。这种应用程序的一个例子是在线商店。员工将在网站上更新产品信息和可用性,而客户则会发出订单并查询其订单状态。使用 Axon,这些组件能够部署在不一样的机器上,并使用不一样的策略进行缩放。它们使用事件保持最新状态,不管部署在哪台机器上,Axon 都会将这些事件分派给全部订阅的组件。
- 与其余应用程序集成多是一项繁琐的工做。使用命令和事件严格定义应用程序的 API 可使其更容易与外部应用程序集成。任何应用程序均可以发送命令或侦听应用程序生成的事件。
架构概述
CQRS 自己是很是简单的模式。它只规定处理命令的应用程序的组件应该与处理查询的组件分离。虽然这种分离自己很是简单,可是当与其它模式结合时,它提供了许多很是强大的特征。安全
axon 提供构建块,使得可以更容易地实现能够与 CQRS 组合使用的不一样模式。下图显示了基于CQRS的事件驱动架构的扩展布局的示例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ae30iIc6-1570778817749)
左侧显示的 UI 组件以两种方式与应用程序的其他部分交互:它向应用程序发送命令 Command(在顶部显示),并查询应用程序以获取信息(在底部显示)。架构
命令一般由简单和直接的对象表示,对象包含命令处理程序执行该命令所需的全部数据。命令以其名称表示其意图。在 Java 术语中,这意味着使用类名称来找出须要完成的操做,而且命令的字段提供执行该操做所需的信息。命令总线接收命令并将它们路由到命令处理程序。每一个命令处理程序响应特定类型的命令并基于命令的内容执行逻辑。可是,在某些状况下,您还但愿执行逻辑,而无论实际的命令类型,例如验证、日志记录或受权。命令处理程序从存储库中检索域对象(聚合),并对其执行方法以更改其状态。这些聚合一般包含实际的业务逻辑,所以负责保护它们本身的不变量。app
聚合的状态变化致使生成域事件。域事件和聚合都造成域模型。存储库负责提供对聚合的访问。一般,这些存储库被优化用于仅经过其惟一标识符来查找聚合。一些存储库将存储聚合自己的状态(例如,使用对象关系映射),而其余存储库存储聚合已在事件存储中经历的状态更改。存储库还负责持久存储在其备份存储中对聚合进行的更改。框架
Axon 为持久聚合的直接方式(例如使用对象关系映射)和事件来源提供支持。事件总线将事件分派给全部感兴趣的事件侦听器。这能够同步地或异步地完成。异步事件调度容许命令执行返回并将控制移交给用户,而在后台调度和处理事件。没必要等待事件处理才能完成应用程序的响应。另外一方面,同步事件处理更简单,是明智的默认处理。默认状况下,同步处理将处理一样处理该命令的同一事务中的事件侦听器。事件侦听器接收事件并处理它们。一些处理程序将更新用于查询的数据源,而另外一些处理程序将消息发送到外部系统。正如您可能注意到的,命令处理程序彻底不知道它们所作的更改感兴趣的组件。这意味着将应用扩展到新的功能是很是非侵入性的。您只需添加另外一个事件侦听器。这些事件将应用程序中的全部组件松散地耦合在一块儿。在某些状况下,事件处理须要向应用程序发送新命令。异步
这是当接收到订单时的示例。这可能意味着客户的帐户应借记购买的金额,而且必须告知装运准备装运所购买的货物。在许多应用中,逻辑会变得更加复杂:若是客户没有及时付款怎么办?您将当即发货,仍是先等待付款?Saga 是负责管理这些复杂业务交易的 CQRS 概念。用户接口和数据源之间的薄数据层提供了与所使用的实际查询实现的明肯定义的接口。此数据层一般将只读DTO 返回到包含查询结果的对象。这些 DTO 的内容一般由用户接口的须要来驱动。在大多数状况下,它们直接映射到 UI 中的特定视图(也称为表视图)。Axon 不能为该部分的应用提供任何构建块。主要缘由是这很是简单,与分层体系结构不一样。分布式
模块结构
AxonFramework 由多个模块组成,这些模块针对 CQRS 的特定问题区域。根据项目的确切需求,您须要包含一个或多个这些模块。模块化
- core: 模块包含 Axon 的核心组件。若是使用单节点设置,则此模块可能会提供所需的全部组件。全部其余 Axon 模块都依赖于这个模块,所以它必须始终在类路径上可用。
- test: 测试模块包含测试工具,能够用来测试基于 Axon 的组件,例如命令处理程序、Aggregates 和 Sagas。一般在运行时不须要这个模块,只须要在测试期间添加到类路径中。
- Distributed: 分布式 CommandBus 模块包含可用于在多个节点上分发命令的实现。它附带了用于链接这些节点的JGroups 和 SpringCloud 链接器。
- AMQP: AMQP 模块提供的组件容许您使用基于 AMQP 的消息代理做为分发机制来构建 EventBus。这容许保证交付,即便事件处理程序节点暂时不可用。
- Spring: Spring 模块容许在 SpringApplication 上下文中配置 Axon 组件.它还提供了许多特定于 Spring 框架的构建块实现,例如在 Spring 消息通道上发布和检索 Axon 事件的适配器。
- MongoDB: 是一个基于文档的 NoSQL 数据库.Mongo 模块提供事件和 Saga Store 实现,这些实现将事件流和 sagas 存储在 MongoDB 数据库中。 几个 Axon Framework 组件提供监控信息。度量模块提供了基于 Codehale 的基本实现来收集监控信息。
Spring 支持
Axon Framework 为 Spring 提供了普遍的支持,但不须要您使用 Spring 便可使用 Axon。 全部组件均可以经过编程方式配置,而且不须要在类路径上使用 Spring。 可是,若是确实使用 Spring,则经过使用 Spring 的注释支持能够简化许多配置。
消息传递
Axon 的核心概念之一是消息传递。组件之间的全部通讯都是使用消息对象完成的。这为这些组件提供了必要的位置透明性,以便在必要时可以扩展和分发这些组件。尽管全部这些消息都实现了消息接口,但不一样类型的消息与如何处理它们之间有着明显的区别。全部消息都包含有效负载、元数据和惟一标识符。消息的有效负载是对消息含义的功能描述。这个对象的类名和它所携带的数据的组合,描述了应用程序对消息的意义。元数据容许您描述发送消息的上下文。例如,您能够存储跟踪信息,以容许跟踪邮件的来源或缘由。您还能够存储信息来描述执行命令的安全上下文。
Command
Command 描述更改应用程序状态的意图。它们被实现为(最好是只读) POJO,它们使用一个 CommandMessage 实现包起来。 命令老是有一个目的地。虽然发送方不关心哪一个组件处理命令或该组件所在的位置,但了解它的结果可能会颇有趣。这就是为何经过命令总线发送的命令消息容许返回结果的缘由。
Event
Event 是描述应用程序中发生的事情的对象。事件的一个典型来源是聚合。当聚合中发生重要事件时,它将引起一个事件。在Axon 框架中,事件能够是任何对象。咱们很是鼓励您确保全部事件都是可序列化的。当事件被分派时,Axon 会将它们封装在EventMessage 中。实际使用的消息类型取决于事件的起源。当事件由聚合引起时,它被包装在 DomainEventMessage (扩展 EventMessage)中。全部其余事件都封装在 EventMessage 中。除了通用消息属性(如惟一标识符)外,EventMessage 还包含时间戳。DomainEventMessage 还包含引起事件的聚合的类型和标识符。它还包含聚合的事件流中事件的序列号,这容许复制事件的顺序。
Unit of Work
工做单元是 Axon 框架中的一个重要概念,尽管在大多数状况下,您不太可能直接与它交互。消息的处理被看做是一个单一的单元。工做单位的目的是协调在处理消息(命令或事件)期间所执行的行动。组件能够注册在工做单元的每一个阶段执行的操做。
例如 onPrepareCommit 或 onCleanup。你不太可能须要直接访问工做单元。它主要由 Axon 提供的构建块使用。若是您确实须要访问它,不管出于什么缘由,有几种方法能够得到它。
- Handler 经过 Handle 方法中的参数接收工做单元。若是使用注释支持,则能够将 UnitOfWork 类型的参数添加到带注释的方法中。
- 在其余位置,能够经过调用 CurrentUnitOfWork.get() 检索绑定到当前线程的工做单元。注意,若是没有绑定到当前线程的工做单元,此方法将引起异常。使用 CurrentUnitOfWork.isStarted() 查找是否可用。要求访问当前工做单元的缘由之一是,附加在消息处理过程当中须要屡次重复使用的资源,或者在工做单元完成时是否须要清理建立的资源。在这种状况下,unitOfWork.getOrComputeResource() 和生命周期回调方法(如 onRollback()、postCommit() 和onCleanup() 容许您注册资源并声明在此工做单元处理过程当中要采起的操做。
请注意,工做单元只是变动的缓冲,而不是事务的替代。虽然全部分阶段的更改都是在提交工做单元时才提交的,但它的提交并非原子的。这意味着,当提交失败时,一些更改可能会持久化,而其余更改则不会。最佳实践规定,命令不该包含多个操做。若是你坚持这种作法,一个工做单位将包含一个单一的行动,使它安全地使用原样。若是工做单元中有更多的操做,那么能够考虑将事务附加到 Work 的提交。 使用 unitOfWork.onCommit(…) 注册在提交工做单元时须要执行的操做。
处理程序可能会因为处理消息而引起异常。默认状况下,未经检查的异常将致使 UnitOfWork 回滚全部更改。Axon提供了一些开箱即用的回滚策略:
- RollbackConfigurationType.NEVER 将始终提交工做单元
- RollbackConfigurationType.ANY_THROWABLE 发生异常时将始终回滚
- RollbackConfigurationType.UNCHECKED_EXCEPTIONS 将回滚错误和运行时异常
- RollbackConfigurationType.RUNTIME_EXCEPTION 将回滚运行时异常可是不包括 Error
当使用 Axon 组件处理消息时,将自动管理工做单元的生命周期。若是选择不使用这些组件,而是本身实现处理,则须要以编程方式启动和提交(或回滚)一个工做单元。在大多数状况下,DefaultUnitOfWork 将为您提供所需的功能。它指望在单个线程中进行处理。要在工做单元的上下文中执行任务,只需在新的 DefaultUnitOfWork 上调用UnitOfWork.ExecutWithResult(可调用)。工做单元将在任务完成时启动和提交,或者在任务失败时回滚。若是须要更多的控制,也能够选择手动启动、提交或回滚工做单元。示例以下:
UnitOfWork uow = DefaultUnitOfWork.startAndGet(message); // then, either use the autocommit approach: uow.executeWithResult(() -> ... logic here); // or manually commit or rollback: try { // business logic comes here uow.commit(); } catch (Exception e) { uow.rollback(e); // maybe rethrow... }
一个工做单元包含几个阶段。 每次进入另外一个阶段时,都会通知 UnitOfWork 侦听器。
- Active phase: 工做单位已启动。工做单位一般在此阶段(经过 CurrentUnitOfWork.set(UNITOFWork))注册。随后,该消息一般由这个阶段中的消息处理器处理。
- Commit phase: 在完成消息处理以后,但在提交工做单元以前,将调用 onPrepareCommit 侦听器。若是工做单元绑定到事务,则调用 onCommit 侦听器来提交任何支持事务。当提交成功时,将调用 postCommit 侦听器。若是提交或以前的任何步骤失败,则调用 onRollback 侦听器。若是可用,消息处理程序结果包含在工做单元的 ExecutionResult中。
- Cleanup phase: 这是该单位所持有的任何资源(如锁)将被释放的阶段。若是嵌套了多个工做单位,则清理阶段将被推迟,直到工做的外部单元准备好清理。
消息处理过程能够被认为是原子过程;它要么彻底被处理,要么根本不被处理。Axon 框架使用工做单位跟踪消息处理程序执行的操做。在处理程序完成后,Axon 将尝试提交与工做单位注册的操做。能够将事务绑定到工做单位。许多组件(例如CommandBus 实现和全部异步处理事件处理器)容许您配置事务管理器。而后,该事务管理器将被用于建立与用于管理消息处理的工做单元绑定的事务。当应用程序组件在消息处理的不一样阶段须要资源时,例如数据库链接或 EntityManager,这些资源能够附加到工做单位。unitOfWork.GetResources() 方法容许您访问链接到当前工做单元的资源。在工做单元上直接提供了几种辅助方法,以便更容易地与资源一块儿工做。当嵌套的工做单位须要可以访问资源时,建议将其注册到可使用unitOfWork.root() 访问的工做的根单元上。若是工做单位是聚合根,它将简单地返回本身。
快速开始
下面就让咱们快速开始吧,首先咱们能够从 Maven、Gradle 中来获取依赖.
<!-- Maven 依赖 --> <dependency> <groupId>org.axonframework</groupId> <artifactId>axon-core</artifactId> <version>${axon.version}</version> </dependency>
AxonFramework 是在 Java 8 上构建和测试的。因为 Axon 自己不建立任何链接或线程,因此在 ApplicationServer 上运行是安全的。Axon 经过使用执行器抽象全部异步行为,这意味着您能够轻松地传递一个容器托管线程池。若是您不使用完整的 ApplicationServer (例如 Tomcat、Jetty 或独立应用程序),您可使用 Executors 类或SpringFramework 来建立和配置线程池。
以上一个简单的 axon 框架环境就搭建好了.是否是很简单.
Command Model
在基于 CQRS 的应用程序中,域模型能够是一种很是强大的机制,能够利用状态更改的验证和执行所涉及的复杂性。虽然典型的领域模型有大量的构建块,但其中一个模块在 CQRS 中的命令处理中起着主导做用:聚合。应用程序中的状态更改以命令开始。命令是表示意图(它描述了您想要作的事情)以及基于该意图进行操做所需的信息的组合。命令模型用于处理传入的命令,验证它并定义结果。在此模型中,命令处理程序负责处理特定类型的命令,并根据其中包含的信息采起行动。
Aggregate
聚合是一个实体或一组实体,老是保持一致的状态。聚合根是聚合树顶部的对象,负责保持这种一致的状态。这使得聚合成为在任何基于 CQRS 的应用程序中实现命令模型的主要构建块。
例如,“Contact(联系人)”聚合能够包含两个实体:联系人和地址。为了使整个聚合保持一致状态,应经过联系人实体为联系人添加地址。在这种状况下,联系人实体是指定的聚合根。
在 Axon 中,聚合由聚合标识符标识。这多是任何对象,可是对于标识符的良好实现有一些准则。标识符必须:
- 实现 eques 和 hashCode,以确保与其余实例进行良好的平等比较
- 实现一个提供一致结果的 toString 方法
- 实现 Serializable 成为可序列化的
当聚合使用不兼容的标识符时,测试模块将验证这些条件并经过测试。字符串、UUID 和数字类型的标识符始终适用。不要使用原始类型做为标识符,由于它们不容许延迟初始化。在某些状况下,Axon 可能会错误地将图元的默认值假定为标识符的值。
聚合老是经过一个名为聚合根的实体访问。一般,该实体的名称与聚合的名称彻底相同。例如,订单聚合可能由一个 Order 实体组成,该实体引用多个 Orderline 实体。有序与有序相结合,造成集合。聚合是一个常规对象,它包含状态和改变该状态的方法。虽然根据 CQRS 原则并不彻底正确,但也能够经过访问器方法公开聚合的状态。聚合根必须声明包含聚合标识符的字段。必须最迟在发布第一个事件时初始化此标识符。此标识符字段必须由 @AggregateIdentifier 注释进行注释。若是您使用JPA 并在聚合上有 JPA 注释,Axon 也可使用 JPA 提供的 @ID 注释。聚合可使用 AggregateLifecycle.Apply() 方法注册事件以供发布。与 EventBus 不一样,EventBus 须要将消息包装在EventMessage 中,Apply() 容许您直接传递有效负载对象。
@Entity // 标记聚合是一个 jpa 实体 public class MyAggregate { @Id // 使用 JPA @Id 进行注释时,不须要 @AggregateIdentifier 注释 private String id; // fields containing state... @CommandHandler public MyAggregate(CreateMyAggregateCommand command) { // ... update state apply(new MyAggregateCreatedEvent(...)); } // constructor needed by JPA protected MyAggregate() { } }
聚合中的实体能够经过定义 @EventHandler 注释方法来侦听聚合发布的事件。这些方法将在 EventMessage 发布时调用(在任何外部处理程序发布以前)。
Event sourced aggregates
除了存储聚合的当前状态外,还能够根据过去发布的事件从新构建聚合的状态。要使此操做正常,全部状态更改都必须由一个事件表示。对于主要部分,事件源聚合相似于“常规”聚合:它们必须声明一个标识符,而且可使用 Apply 方法发布事件。可是,事件源集合中的状态更改(即字段值的任何更改)必须在 @EventSourcingHandler 注释的方法中独占执行。这包括设置聚合标识符。注意,聚合标识符必须在聚合发布的第一个事件的 @EventSourcingHandler 中设置。这一般是建立事件。事件源聚合的聚合根还必须包含无 Arg 构造函数。AxonFramework 使用此构造函数在使用过去的事件初始化聚合实例以前建立一个空聚合实例。未能提供此构造函数将致使加载聚合时出现异常。
public class MyAggregateRoot { @AggregateIdentifier private String aggregateIdentifier; // fields containing state... @CommandHandler public MyAggregateRoot(CreateMyAggregate cmd) { apply(new MyAggregateCreatedEvent(cmd.getId())); } // constructor needed for reconstruction protected MyAggregateRoot() { } @EventSourcingHandler private void handleMyAggregateCreatedEvent(MyAggregateCreatedEvent event) { // 确保标识符始终正确初始化 this.aggregateIdentifier = event.getMyAggregateIdentifier(); // ... update state } }
在某些状况下,特别是当聚合结构超过了几个实体时,对在同一聚合的其余实体中发布的事件做出反应是比较干净的。可是,因为在重构聚合状态时也会调用事件处理程序方法,所以必须采起特殊的预防措施。能够在事件 SourcingHandler 方法中Apply() 新事件。这使得实体 B 可以将事件应用于实体 A 所作的事情。当重播历史事件时,Axon 将忽略 Apply() 调用。请注意,在本例中,内部 Apply() 调用的事件仅在全部实体接收到第一个事件以后才发布到实体。若是须要发布更多事件,则根据应用内部事件后实体的状态,使用 Apply(.).和 ThenApply(.) 您还可使用静态的AggregateLifecycle.isLive() 方法来检查聚合是不是“live”。基本上,若是一个集合已经完成了历史事件的重播,它就被认为是实时的。在重播这些事件时,isLive() 将返回 false。使用此 isLive() 方法,您能够执行仅在处理新生成的事件时才应该执行的活动。
Complex Aggregate structures
复杂的业务逻辑一般所须要的不仅是仅具备聚合根的聚合所能提供的。在这种状况下,重要的是将复杂性分布在聚合中的多个实体上。在使用事件源时,不只聚合根须要使用事件来触发状态转换,并且聚合中的每一个实体也须要使用事件来触发状态转换。
注意聚合不该公开状态的规则的一个常见误解是,任何实体都不该该包含任何属性访问器方法。 不是这种状况。 实际上,若是聚合中的实体向同一聚合中的其余实体公开状态,则聚合可能会受益不浅。 可是,建议不要将状态暴露在聚合外部。
Axon 为复杂聚合结构中的事件源提供支持。就像聚合根同样,实体是简单的对象。声明子实体的字段必须使用@AggregateMember 注释。 此注释告诉 Axon,带注释的字段包含应检查命令和事件处理程序的类。
当一个实体(包括聚合根)应用一个事件时,它首先由聚合根处理,而后经过全部 @AggregateMember 注释字段冒泡直至其子实体。包含子实体的字段必须使用 @AggregateMember 注释。此批注可用于多种字段类型:
- 在字段中直接引用的实体类型
- 包含 Iterable(包含全部集合,例如 Set,List 等)的内部字段
- 在包含 java.util.Map 的字段的值中
Handling commands in an Aggregate
建议直接在包含处理该命令状态的 Aggregate 中定义命令处理程序,由于命令处理程序不太可能须要该 Aggregate 的状态来完成其工做。
若要在聚合中定义命令处理程序,只需使用 @CommandHandler 注释命令处理方法。@CommandHandler 注释方法的规则与任何处理程序方法相同。然而,命令不只是由它们的有效负载路由的。命令消息带有一个名称,默认为 Command 对象的彻底限定类名。
默认状况下,@CommandHandler 注释方法容许如下参数类型:
- 第一参数是命令消息的有效载荷。若是 @CommandHandler 注释明肯定义了处理程序能够处理的命令的名称,则它也能够是类型消息或 CommandMessage。缺省状况下,命令名称是命令的有效负载的彻底限定的类名称。
- 用 @MetadataValue 注释的参数将以注释所示的键解析为元数据值。若是须要为 false(默认),则在元数据值不存在时传递 NULL。若是须要,则解析器将不匹配,而且在元数据值不存在时阻止该方法被调用。
- MetaData 类型的参数将注入 CommandMessage 的整个 MetaData。
- 类型为 UnitOfWork 的参数获取当前注入的工做单位。这使命令处理程序能够注册要在工做单元的特定阶段执行的操做,或者访问对其注册的资源。
- 类型为 Message 或 CommandMessage 的参数将获取完整的消息以及有效负载和元数据。若是方法须要几个元数据字段或包装的 Message 的其余属性,则此方法颇有用。
为了使 Axon 知道哪一个 Aggregate 类型的实例应处理命令消息,必须在 @CommandAggregateIdentifier 处注释Command 对象中带有 AggregateIdentifier 的属性。 注释能够放在字段或访问器方法(例如 getter)上。
建立聚合实例的命令无需标识目标聚合标识符,尽管建议也对它们进行注释。若是您更喜欢使用另外一种机制来路由命令,则能够经过提供自定义 CommandTargetResolver 来覆盖该行为。该类应该根据给定的命令返回聚合标识符和预期版本(若是有的话)。
当 @CommandHandler 注释放置在聚合 Aggregate 构造函数上时,相应的命令将建立该聚合的新实例并将其添加到存储库中。这些命令不须要针对特定的聚合实例。所以,这些命令不须要任何 @TargetAggregateIdentifier 或@TargetAggregateVersion 注释,也不会为这些命令调用自定义 CommandTargetResolver。 当命令建立聚合实例时,当命令成功执行时,该命令的回调将接收聚合标识符。
public class MyAggregate { @AggregateIdentifier private String id; @AggregateMember private MyEntity entity; @CommandHandler public MyAggregate(CreateMyAggregateCommand command) { apply(new MyAggregateCreatedEvent(...); } // no-arg constructor for Axon MyAggregate() { } @CommandHandler public void doSomething(DoSomethingCommand command) { // do something... } } public class MyEntity { @CommandHandler public void handleSomeCommand(DoSomethingInEntityCommand command) { // do something } }
@CommandHandler 注释不限于聚合根。将全部命令处理程序放置在根中有时会致使聚合根的大量方法,而其中许多方法简单地将调用转发到底层实体之一。若是是这种状况,则能够将 @CommandHandler 注释放置在下面的实体之一“方法”上。对于Axon 找到这些注释的方法,必须用 @AggregateMember 标记在聚合根中声明实体的字段。请注意,只有声明类型的注释字段被检查为命令处理程序。若是在传入命令到达该实体时,字段值为 NULL,则会引起异常。
请注意,每一个命令在聚合中必须有一个处理程序。这意味着您不能用使用 @CommandHandler 注释处理相同命令类型的多个实体(不管是根节点仍是不是根节点)。若是须要有条件地将命令路由到实体,则这些实体的父实体应该处理该命令,并根据适用的条件转发命令。字段的运行时类型没必要彻底是声明的类型。可是,对于 @CommandHandler 方法,只检查@AggregateMenger 注释字段的声明类型。
也可使用 @AggregateMember 注释包含实体的 Collections 和 Maps。在后一种状况下,映射的值应包含实体,而键包含的值将用做其引用。
因为须要将命令路由到正确的实例,所以必须正确标识这些实例。他们的“id”字段必须用 @EntityId 注释。将用于查找消息应该路由到的实体的命令上的属性,默认为已注释的字段的名称。例如,在注释字段“myEntityId”时,命令必须定义一个名称相同的属性。这意味着必须存在 getMyEntityId 或 myEntityId() 方法。若是字段的名称和路由属性不一样,您可使用 @EntityId(routingKey=“customRoutingProperty”) 显式地提供一个值。
若是在带注释的 Collection 或 Map 中找不到任何 Entity,则 Axon 会引起 IllegalStateException;不然,它将抛出 IllegalStateException。 显然,聚合不能在该时间点处理该命令。Collection 或 Map 的字段声明应包含适当的泛型,以容许 Axon 标识 Collection 或 Map 中包含的实体的类型。 若是没法在声明中添加泛型(例如,由于您使用的是已经定义了泛型类型的自定义实现),则必须在 @AggregateMember 批注中指定 entityType 属性中使用的实体类型。
External Command Handlers
在某些状况下,不可能或但愿将命令直接路由到 Aggregate 实例。在这种状况下,能够注册命令处理程序对象。
Command Handler 对象是一个简单的(常规)对象,具备 @CommandHandler 注释方法。与聚合不一样,命令处理程序对象只有一个实例,该对象处理在其方法中声明的全部类型的命令。
public class MyAnnotatedHandler { @CommandHandler public void handleSomeCommand(SomeCommand command, @MetaDataValue("userId") String userId) { // whatever logic here } @CommandHandler(commandName = "myCustomCommand") public void handleCustomCommand(SomeCommand command) { // handling logic here } } // 要将注释的处理程序注册到命令总线,请执行如下操做 Configurer configurer = ... configurer.registerCommandHandler(c -> new MyAnnotatedHandler());
Event Handling
Event listeners 是做用于传入事件的组件。它们一般基于由命令模型做出的决定来执行逻辑。一般,这包括更新视图模型或将更新转发到其余组件,例如第三方集成。在某些状况下,事件处理程序将根据收到的事件(模式)抛出事件,或者甚至发送命令来触发进一步的更改。
Defining Event Handlers
在 Axon 中,对象能够经过用 @eventHandler 注释它们来声明多个事件处理程序方法。该方法的声明参数定义了它将接收的事件.
Axon 为如下参数类型提供开箱即用的支持:
- 第一个参数始终是事件消息的有效负载。若是事件处理程序不须要访问消息的有效负载,则能够在 @EventHandler 注释中指定预期的有效负载类型。当指定第一个参数时,将使用下面指定的规则解析第一个参数。若是但愿将有效负载做为参数传递,请不要在注释上配置有效负载类型。
- 用 @MetadataValue 注释的参数将以注释所示的键解析为元数据值。若是须要为 false(默认),则在元数据值不存在时传递 NULL。若是须要,则解析器将不匹配,而且在元数据值不存在时阻止该方法被调用。
- MetaData 类型的参数将注入 EventMessage 的整个 MetaData。
- 用 @Timestamp 注释而且类型为 java.time.Instant(或 java.time.temporal.Temporal)的参数将解析为EventMessage 的时间戳。 这是生成事件的时间。
- 用 @SequenceNumber 注释而且类型为 java.lang.Long 或 long 的参数将解析为 DomainEventMessage 的sequenceNumber。这提供了事件生成的顺序(在其起源的聚合范围内)。
- 可分配给 Message 的参数将注入整个 EventMessage(若是消息可分配给该参数)。若是第一个参数的类型为message,则它有效地匹配任何类型的事件,即便通用参数会另外建议也是如此。因为类型擦除,Axon 没法检测到指望的参数。在这种状况下,最好声明有效负载类型的参数,而后声明消息类型的参数。
- 使用 Spring 并激活 Axon 配置时(经过包括 Axon Spring Boot Starter 模块,或者经过在 @Configuration文件中指定 @EnableAxon),若是在控制台中只有一个可注入的候选对象,则任何其余参数都将解析为自动装配的 bean。应用程序上下文。这使您能够将资源直接注入到 @EventHandler 带注释的方法中。
您能够经过实现 ParamResolerFactory 接口并建立名为 /META-INF/Service/org.axonframework.common.annotation.ParameterResolverFactory 的文件来配置其余参数解析器,其中包含实现类的彻底限定名称。
在全部状况下,每一个监听器实例最多调用一个事件处理程序方法。Axon 将使用如下规则搜索要调用的最特定方法:
- 在类层次结构的实际实例级别上(由this.getClass())返回),将评估全部带注释的方法。
- 若是找到一个或多个方法能够将全部参数解析为一个值,则选择并调用具备最特定类型的方法。
- 若是在该类层次结构的此级别上没有找到任何方法,则计算父类的方法是否相同。
- 当到达层次结构的顶层,而没有找到合适的事件处理程序时,该事件将被忽略。
public class TopListener { @EventHandler public void handle(EventA event) { } @EventHandler public void handle(EventC event) { } } public class SubListener extends TopListener { @EventHandler public void handle(EventB event) { } }
在上面的示例中,SubListener 将接收 EventB 和 EventC(扩展EventB) 的全部实例。换句话说,TopListener 根本不会接收 EventC 的任何调用。因为 Eventa 不能分配给 EventB(它是它的超类),这些类将由 TopListener 处理。
Registering Event Handlers
事件处理组件使用 EventHandlingConfiguration 类定义,该类被注册为带有全局 Axon 配置程序的模块。一般,应用程序将定义单个 EventHandlingConfiguration,但更大的模块化应用程序能够选择每一个模块定义一个。
若要使用 @EventHandler 方法注册对象,请在 EventHandlingConfiguration 上使用 RegistrerEventHandler方法:
// 定义EventHandlingConfiguration EventHandlingConfiguration ehConfiguration = new EventHandlingConfiguration() .registerEventHandler(conf -> new MyEventHandlerClass()); // 该模块须要在 Axon 配置中注册。 Configurer axonConfigurer = DefaultConfigurer.defaultConfiguration().registerModule(ehConfiguration);