原文地址:http://geekabyte.blogspot.nl/2015/10/exploring-cqrs-with-axon-framework.html
在这篇文章中,我们来看看如何使用 Axon 框架来实现一个CQRS的应用中的 command handling 部分。
这是使用axon框架探索CQRS架构的系列文章中的一篇。
正如在介绍性帖子中所述,这些系列的帖子伴随着Github上的exploreCQRSwithAxon项目。 该项目是一个简单的应用程序,模拟两个银行帐户。 它将支持这两个账户之间的借记,贷记和转账。 它旨在帮助说明在讨论如何根据CQRS规定使用Axon Framework构建应用程序时所讨论的功能。
这个示例项目是基于Axon Framework and Spring Boot构建的。 使用的 Axon Framework 2.4.1 版本, Pom.xml 如下:
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-core</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-test</artifactId>
<version>2.4.1</version>
<scope>test</scope>
</dependency>
出于本系列的目的,我们只使用 axon-core 和 axon-test 模块.
以下是示例应用程序.
要在随附的应用程序中关注此帖子:
首先,克隆项目
git clone git@github.com:dadepo/exploringCQRSwithAxon.git
然后检查相应的提交哈希,让项目处于说明本文所涉及主题的状态:
git checkout 0529ada7987eeadb37d88c9db95673e5883bddc8.
这篇文章的目的
目标是设置应用程序的命令处理组件。 具体来说,我们将探索如何使用Axon的命令总线,如何创建命令并将其路由到命令处理程序。
让我们从命令开始看看如何实现这一目标。
Commands
命令是常规的,简单的,对象(通常称为POJO),用于捕获系统内需要发生的事件的意图以及所需的必要信息。
Axon Framework不需要命令来实现任何接口或扩展任何类。 如前所述,它们是POJO的。 但是,一般建议是使命令不可变。
关于命令的另一个一般建议是它们具有表达它们所体现的意图的名称。 因此,例如,如果命令传达应该取消约会,那么类的名称应该命名为CancelAppointmentCommand。
在随附的项目中,您将找到CreditAccount和DebitAccount类。 它们是命令,从其名称可以看出,表达了将帐户记入贷方或借记的意图。
public class CreditAccount {
private final String account;
private final Double amount;
public CreditAccount(String account, Double amount) {
this.account = account;
this.amount = amount;
}
public String getAccount() {
return account;
}
public Double getAmount() {
return amount;
}
}
和 DebitAccount
public class DebitAccount {
private final String account;
private final Double amount;
public DebitAccount(String account, Double amount) {
this.account = account;
this.amount = amount;
}
public String getAccount() {
return account;
}
public Double getAmount() {
return amount;
}
}
它们包含帐号和要贷记或借记的金额。
命令仅包含一个且只有一个收件人。 命令的接收者称为命令处理程序即command handler,我们将在下面查看。
Command Handlers
命令处理程序是接收命令并对其执行操作的组件。
命令本身就是“消息载体”。 命令处理程序是基于命令中捕获的意图(或消息)而动作的组件。
在Axon Framework中,您可以通过两种方式创建命令处理程序:
1. 通过让Object实现CommandHandler接口
2. 将Axon与Spring一起使用,则使用AnnotationCommandHandlerBeanPostProcessor,它允许我们将具有使用@CommandHandler注释的方法的Spring bean转换为命令处理程序。
在我们附带的应用程序中,DebitAccountHandler是一个命令处理组件,它通过实现CommandHandler接口实现,如下所示:
public class DebitAccountHandler implements CommandHandler {
@Override
public Object handle(CommandMessage commandMessage,
UnitOfWork unitOfWork) throws Throwable {
DebitAccount debitAccountCommand =
(DebitAccount) commandMessage.getPayload();
String account = debitAccountCommand.getAccount();
Double amount = debitAccountCommand.getAmount();
System.out.println("I can handle the debitAccount command: "
+ "Account to debit: " + account
+ " Amount to debit with: "+ amount);
return null;
}
}
而CreditAccountHandler使用注解:
@Component
public class CreditAccountHandler {
@CommandHandler
public void handle(CreditAccount creditAccountCommand){
System.out.println("I can handle the creditAccount command: "
+ "Account to credit: " + creditAccountCommand.getAccount()
+ " Amount to credit with: "+ creditAccountCommand.getAmount());
}
}
这需要在Spring Application上下文中配置AnnotationCommandHandlerBeanPostProcessor。
从上面的代码可以看出,这些命令处理程序现在要做的是将消息打印到控制台。
所以现在我们已经接触了命令和命令处理程序,接下来的问题是如何将命令传递给各自的命令处理程序?
Command bus
命令总线是将命令路由到各自的命令处理程序的组件。
Axon Framework,在版本2.4.1中,带有四种不同类型的命令总线,可用于将命令分派给命令处理程序:SimpleCommandBus,DisruptorCommandBus,AsynchronousCommandBus和
RecordingCommandBus。
它们都具有唯一的属性:
例如,AsynchronousCommandBus异步处理命令,而DisruptorCommandBus在分布式设置中最合适。
在我们的应用程序中,我们将使用SimpleCommandBus。
由于我们正在使用Spring Framework,因此要使用SimpleCommandBus,我们将其配置为Spring的应用程序上下文中的bean。 这是我们在AppConfiguration类中完成的。
@Configuration
public class AppConfiguration {
@Bean
public SimpleCommandBus commandBus() {
SimpleCommandBus simpleCommandBus = new SimpleCommandBus();
// This manually subscribes the command handler:
// DebitAccountHandler to the commandbus
simpleCommandBus.subscribe(DebitAccount.class.getName(),
new DebitAccountHandler());
return simpleCommandBus;
}
@Bean
AnnotationCommandHandlerBeanPostProcessor
annotationCommandHandlerBeanPostProcessor() {
/**
* The AnnotationCommandHandlerBeanPostProcessor
* finds all beans that has @CommandHandler
* and subscribed them to the commandBus with the
* first argument of the method being the
* the command type the method will be subscribed to.
*/
AnnotationCommandHandlerBeanPostProcessor handler =
new AnnotationCommandHandlerBeanPostProcessor();
handler.setCommandBus(commandBus());
return handler;
}
@Bean
public DefaultCommandGateway commandGateway() {
return new DefaultCommandGateway(commandBus());
}
}
我们配置的第一个bean是SimpleCommandBus。
请注意,与Axon Framework中的许多内容一样,您可以提供自定义实现,以替代Axon提供的几乎所有组件,例如,您可以轻松拥有自定义命令总线,所有这些都需要完成 是实现CommandBus接口。
对于命令总线能够将命令路由到命令处理程序,它需要知道命令并知道它们各自的命令处理程序。 这是通过将命令处理程序订阅到命令总线上的命令来实现的。
命令总线接口定义了subscribe()方法, 这是DebitAccountHandler命令处理程序在随附项目中订阅DebitAccount命令的方式。
例如,查看配置SimpleCommandBus的代码:
@Bean
public SimpleCommandBus commandBus() {
SimpleCommandBus simpleCommandBus =
new SimpleCommandBus();
// This manually subscribes the command handler:
// DebitAccountHandler to the commandbus
simpleCommandBus.subscribe(DebitAccount.class.getName(),
new DebitAccountHandler());
return simpleCommandBus;
}
正如您在代码中看到的,我们使用subscribe()方法通知SimpleCommandBus DebitAccount命令,并且它的命令处理程序是DebitAccountHandler。
我们还可以使用AnnotationCommandHandlerBeanPostProcessor来实现使用命令处理程序注册命令。
如果将Axon Framework与Spring一起使用,这是比较推荐的方法,否则必须手动将每个单独的命令处理程序订阅到应用程序中的命令,这很快就会变成一项枯燥乏味的任务。推荐的方法是使用自版本2.3以来添加的@AnnotationDriven注释,它可以避免必须显式声明AnnotationCommandHandlerBeanPostProcessor类型的bean。要使用它,只需使用@AnnotationDriven注释Spring @Configuration类,所有@ CommandHandler和@EventHandler将自动扫描并注册到各自的总线。随带的示例应用程序已更新(使用d6c9f18750f8f7d4c341c80a07bdf44c5a815783提交)以使用@AnnotationDriven。
AnnotationCommandHandlerBeanPostProcessor通过查找具有@CommandHandler注释的bean,在应用程序上下文中查找命令总线并将命令处理程序订阅到它打算处理的命令来扫描命令处理程序。
具有@CommandHandler的方法的第一个参数是命令处理程序要处理的命令。
下面的代码显示了我们如何连接AnnotationCommandHandlerBeanPostProcessor。
@Bean
AnnotationCommandHandlerBeanPostProcessor
annotationCommandHandlerBeanPostProcessor() {
/**
* The AnnotationCommandHandlerBeanPostProcessor finds all beans that has
* @CommandHandler
* and subscribed them to the commandBus with the first argument of
* the method being the the command type the method will be subscribed to.
*/
AnnotationCommandHandlerBeanPostProcessor handler =
new AnnotationCommandHandlerBeanPostProcessor();
handler.setCommandBus(commandBus());
return handler;
}
Command Gateway
尽管可以直接使用命令总线发送命令,但通常建议使用命令网关。 这是因为命令网关提供的API比必须直接使用命令总线“友好”。
它还允许我们执行某些功能,例如,使用命令网关,我们可以轻松地为失败的命令设置重试机制,拦截命令并在将它们分派到命令处理组件之前对其进行修改。
Axon Framework提供DefaultCommandGateway作为CommandGateway的实现。 我们将在我们的应用程序中使用它来发送命令而不是直接使用命令总线。 DefaultCommandGateway配置如下:
@Bean
public DefaultCommandGateway commandGateway() {
return new DefaultCommandGateway(commandBus());
}
如果您克隆了exploreCQRSwithAxon项目并检出了哈希码0529ada7987eeadb37d88c9db95673e5883bddc8,则可以通过切换到根目录并执行mvn spring-boot:run
来运行该应用程序。
应用程序启动后,转到 localhost:8080,您会看到以下页面:
如果选择Account one,在输入字段中输入10并单击借方帐户,您将在控制台中看到”I can handle the debitAccount command: Account to debit: acc-one Amount to debit with: 10.0” .
您将获得“我可以处理creditAccount命令:”Account to credit: acc-two Amount to credit with: 10.0”如果您选择账户2,输入10,并点击信用账户。
概述。
与UI输入交互将请求发送到IndexController,后者暴露 /debit 和 /credit 的endpoints.
当请求到达这些endpoints时,IndexController创建命令,抓取命令网关(已注入)并分派命令。
@Controller
public class IndexController {
@Autowired
private CommandGateway commandGateway;
@RequestMapping("/debit")
@ResponseBody
public void doDebit(@RequestParam("acc") String accountNumber,
@RequestParam("amount") double amount) {
DebitAccount debitAccountCommand =
new DebitAccount(accountNumber, amount);
commandGateway.send(debitAccountCommand);
}
@RequestMapping("/credit")
@ResponseBody
public void doCredit(@RequestParam("acc") String accountNumber,
@RequestParam("amount") double amount) {
CreditAccount creditAccountCommand =
new CreditAccount(accountNumber, amount);
commandGateway.send(creditAccountCommand);
}
}
由于我们已经使用命令总线配置命令网关,该命令总线知道如何路由已调度的命令,因此我们分别调用了DebitAccountHandler和CreditAccountHandler。
我们在本文中提到的Axon Framework的组件有更多功能。 (有些用于高级用例),但到目前为止我们的探索应该提供对这些组件的良好介绍以及如何使用它们。
我们现在将重点转向CQRS图中表示的其他组件,这将引导我们探索域驱动开发中的一些概念。
下一篇文章将介绍一些DDD概念:实体,聚合,聚合根和存储库。( Entity, Aggregate, Aggregate Root and Repository).