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.