Axon数据校验
场景:
因为command的发送方和接收方是不同的服务,所以就考虑对command数据结构做数据校验。联想到Controller层使用Hibernate Validator基于注解的方式进行数据校验,就想Axon是不是也有同样的实现。
官方文档说明
翻阅官网可以看的如下内容:
Structural validation
There is no point in processing a query if it does not contain all required information in the correct format. In fact, a query that lacks information should be blocked as early as possible. Therefore, an interceptor should check all incoming queries for the availability of such information. This is called structural validation.
Axon Framework has support for JSR 303 Bean Validation based validation. This allows you to annotate the fields on queries with annotations like @NotEmpty and @Pattern. You need to include a JSR 303 implementation (such as Hibernate-Validator) on your classpath. Then, configure a BeanValidationInterceptor on your query bus, and it will automatically find and configure your validator implementation. While it uses sensible defaults, you can fine-tune it to your specific needs.
Interceptor Ordering Tip
You want to spend as few resources on an invalid queries as possible. Therefore, this interceptor is generally placed at the very front of the interceptor chain. In some cases, a logging or auditing interceptor might need to be placed first, with the validating interceptor immediately following it.
The BeanValidationInterceptor also implements MessageHandlerInterceptor, allowing you to configure it as a handler interceptor as well.
可以看的加粗的地方,Axon支持JSR 303,只需要引入JSR303的实现(例如Hibernate-Validator),并将BeanValidationInterceptor 注册到命令总线里,他会自动找到对应JSR303的实现并在CommandMessage执行对应MessageHandler之前进行拦截做校验。
实现
理论依据存在,下面就是看如何实现了
首先找到了commandBus 注入spring容器的代码,查找路径如下:
根据springboot自动注入的机制找到AxonAutoConfiguration,然后可以找到如下的代码
@ConditionalOnMissingBean(
ignoredType = {"org.axonframework.commandhandling.distributed.DistributedCommandBus", "org.axonframework.axonserver.connector.command.AxonServerCommandBus"},
value = {CommandBus.class}
)
@Qualifier("localSegment")
@Bean
public SimpleCommandBus commandBus(TransactionManager txManager, AxonConfiguration axonConfiguration, DuplicateCommandHandlerResolver duplicateCommandHandlerResolver) {
SimpleCommandBus commandBus = SimpleCommandBus.builder().transactionManager(txManager).duplicateCommandHandlerResolver(duplicateCommandHandlerResolver).messageMonitor(axonConfiguration.messageMonitor(CommandBus.class, "commandBus")).build();
commandBus.registerHandlerInterceptor(new CorrelationDataInterceptor(axonConfiguration.correlationDataProviders()));
return commandBus;
}
首先看看这个SimpleCommandBus内部有什么
看到这个handlerInterceptors, 是个List<MessageHandlerInterceptor<? super CommandMessage<?>>>,看之前官方文档可以知道
The BeanValidationInterceptor also implements MessageHandlerInterceptor, 那就是只要把BeanValidationInterceptor 加到这个list中就可以了。
那接着就是找SimpleCommandBus是不是有注册的方法,于是找到了下面的代码:
@Override
public Registration registerHandlerInterceptor(
MessageHandlerInterceptor<? super CommandMessage<?>> handlerInterceptor) {
handlerInterceptors.add(handlerInterceptor);
return () -> handlerInterceptors.remove(handlerInterceptor);
}
由此就有了下面的配置:
@ConditionalOnMissingBean(
ignoredType = {"org.axonframework.commandhandling.distributed.DistributedCommandBus"
, "org.axonframework.axonserver.connector.command.AxonServerCommandBus"},
value = {CommandBus.class}
)
@Qualifier("localSegment")
@Bean
public SimpleCommandBus commandBus(TransactionManager txManager, AxonConfiguration axonConfiguration,
DuplicateCommandHandlerResolver duplicateCommandHandlerResolver) {
SimpleCommandBus commandBus = SimpleCommandBus
.builder()
.transactionManager(txManager)
.duplicateCommandHandlerResolver(duplicateCommandHandlerResolver)
.messageMonitor(axonConfiguration.messageMonitor(CommandBus.class, "commandBus"))
.build();
commandBus.registerHandlerInterceptor(
new CorrelationDataInterceptor(axonConfiguration.correlationDataProviders()));
// 数据校验拦截器
commandBus.registerHandlerInterceptor(new BeanValidationInterceptor<>());
return commandBus;
}
配置好拦截器后,就是给自己的command加注解了,具体支持那些注解,网上文档很多这里就不详细说明了,可以看下我配置的一个例子:
public class ChangeOrderCommand {
@TargetAggregateIdentifier
@NotBlank
private String id;
@NotBlank
private String orderName;
@NotNull
private int number;
public ChangeOrderCommand() {
}
public ChangeOrderCommand(String id, String orderName, int number) {
this.id = id;
this.orderName = orderName;
this.number = number;
}
}