I am creating a simple app using Axon + Spring Boot, just to make sure I understand the basic components in Axon framework before I use it in a real project. There is a method annotated with @CommandHandler within the class TaskAggregate that is supposed to be called when I send a command through the CommandGateway, but after running the app I am getting the exception:
Exception in thread "main" org.axonframework.commandhandling.NoHandlerForCommandException: No handler was subscribed to command [com.xxx.axontest.task.CreateTaskCommand]
As per the documentation, the @CommandHandler annotation should be enough to subscribe the command hander to the command bus. I guess I must be missing something. Could you take a look to below code and point me to the right direction?.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxx</groupId>
<artifactId>axon-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<axon.version>3.0.6</axon.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
<version>${axon.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
App.java
package com.xxx.axontest;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import com.xxx.axontest.task.CreateTaskCommand;
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);
CommandGateway commandGateway = configurableApplicationContext.getBean(CommandGateway.class);
commandGateway.send(new CreateTaskCommand(123, "asd"));
}
@Bean
public EventStorageEngine eventStorageEngine() {
return new InMemoryEventStorageEngine();
}
@Bean
public AnnotationCommandHandlerBeanPostProcessor
annotationCommandHandlerBeanPostProcessor() {
return new AnnotationCommandHandlerBeanPostProcessor();
}
}
CreateTaskCommand.java
package com.xxx.axontest.task;
import org.axonframework.commandhandling.TargetAggregateIdentifier;
public class CreateTaskCommand {
@TargetAggregateIdentifier
private int taskId;
private String name;
public CreateTaskCommand(int taskId, String name) {
this.taskId = taskId;
this.name = name;
}
public int getTaskId() {
return taskId;
}
public String getName() {
return name;
}
}
TaskCreatedEvent.java
package com.xxx.axontest.task;
import org.axonframework.commandhandling.TargetAggregateIdentifier;
public class TaskCreatedEvent {
@TargetAggregateIdentifier
private int taskId;
private String name;
public int getTaskId() {
return taskId;
}
public String getName() {
return name;
}
}
TaskAggregate.java
package com.xxx.axontest.task;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.commandhandling.model.AggregateIdentifier;
import org.axonframework.commandhandling.model.AggregateLifecycle;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.spring.stereotype.Aggregate;
@AggregateRoot
public class TaskAggregate {
private Logger logger = LogManager.getLogger(TaskCreatedEvent.class);
@AggregateIdentifier
private int taskId;
private String name;
@CommandHandler
public void handleCommand(CreateTaskCommand createTaskCommand) {
logger.info("Command received");
AggregateLifecycle.apply(new TaskCreatedEvent());
}
@EventSourcingHandler
public void onTaskCreatedEvent(TaskCreatedEvent taskCreatedEvent) {
logger.info("Event received");
}
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Thanks in advance.
I think you need to annotate your aggregate with @Aggregate rather than @AggregateRoot
.
@Aggregate
as an annotation is both the @AggregateRoot
annotation, but is also used by the Spring Boot Axon Starter module to signal that for that class an Aggregate factory and Repository
has to be created.
Additionally, that'll mean the @CommandHandler
annotated functions on your aggregate will also be found and registered to the CommandBus
, probably solving the exception you caught.
Otherwise, the webinars on YouTube from Allard for starting an Axon application in version 3 could give you some insight.
But, in short, try switching the @AggregateRoot
annotation for @Aggregate
:-)
Additionally however, you should be adjusting the @CommandHandler
annotated function for the CreateTaskCommand
to be a constructor for the TaskAggregate
.
Lastly, Axon requires you to have a no-arg constructor for you aggregate, so also add a public TaskAggregate() { }
constructor in there.
Based on the above code, a few remarks:
I can't explain this exception given the code you provide, but there is a chance that the explicitly defined AnnotationCommandHandlerBeanPostProcessor is in the way.
[Edited] Steven correctly noted the @AggregateRoot annotation. It should be @Aggregate. Above comments are still valid, but not directly related to the exception [/Edited]
You also need to register your handler to the command bus. I found this tutorial that should help you. A quick highlight from there:
@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;
}
}
P.S. One important thing: events and commands should be immutable (no setters)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With