NoHandlerForCommandException with axon-spring-boot-starter
Asked Answered
I

4

5

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.

Indignity answered 18/10, 2017 at 1:20 Comment(3)
One important thing: events and commands should be immutable (no setters)Tammitammie
From doc: "CommandHandlers need to be subscribed to a CommandBusin order to receive command"Tammitammie
@ConstantinGalbenu, I removed the setters following your recommendation.Indignity
M
6

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.

Malefic answered 18/10, 2017 at 14:6 Comment(7)
Good catch. I tried that and now I am getting Command 'com.xxx.axontest.task.CreateTaskCommand' resulted in org.axonframework.commandhandling.model.AggregateNotFoundException(The aggregate was not found in the event store)Indignity
Adjusted the comment to point out that you should probably have the CreateTaskCommand be handled by a constructor rather than a regular function. And additionally, also a no-arg constructor, as Axon requires that.Malefic
I'm facing the same problem when running tests. There is an aggregate that works fine but another is not discovered... I used the same annotation @Aggragate, empty constructor and commandHandler constructor. It is registered when the application runs but not when running tests. Any idea? thanksOrmandy
What kind of tests are you pointing at @Pietro? For Aggregate tests, Axon provides dedicated test fixtures I would recommend you use. You can find more on them here: docs.axoniq.io/reference-guide/axon-framework/testing/…Malefic
If it's an integration test using Spring Boot, you will have to make sure the required Axon components are wired, as well as the components containing the command handling functions.Malefic
I'm using the fixture of Axon and the components are wired because the application runs without problems. Here is the test that is not working github.com/gpietro/spring-boot-cqrs-demo/blob/master/src/test/…. All the tests pointing to the EpisodeOfCare (github.com/gpietro/spring-boot-cqrs-demo/blob/master/src/main/…) commandHandlers are not found. The logged error is: No handler was subscribed to command [com.fundev.adt.coreapi.CommandPatientAdmit]Ormandy
Ah, well a Test Fixture only deals with a single Aggregate type, not with several. Your test however uses an AggregateTestFixture for the Patient aggregate type. If you want to test the EpisodeOfCare aggregate too, you will have to define a different AggregateTestFixture for that aggregate. I would recommend to add a distinct EpisodeOfCareTest class which creates the test fixture for the EpisodeOfCare, segregated from the PatientTest.Malefic
J
3

Based on the above code, a few remarks:

  • you do not need to provide a AnnotationCommandHandlerBeanPostProcessor. In fact, specifying one may interfere with the regular operation of Axon/Spring Boot autoconfiguration
  • The commands that creates a new Aggregate instance should be placed on a constructor. The is no instance to invoke the method on, yet. Note that you will (also) have to specify a no-arg constructor.
  • The taskId should be set by the @EventSourcingHandler. Getters and Setters do not belong in an (event sourced) Aggregate
  • In Events, you do not need to specify @TargetAggregateIdentifier. They are only means for commands.

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]

Jaguar answered 18/10, 2017 at 14:9 Comment(1)
A little bit confused because the documentation says: Axon will automatically register all the CommandHandler annotated methods with the Command Bus and set up a repository if none is present. without mentioning constructors. But using the @CommandHandler annotation in the constructor instead of a method, as you suggested, fixed the problem. Thank you very much.Indignity
T
1

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)

Tammitammie answered 18/10, 2017 at 5:57 Comment(3)
The tutorial you mention is using axon 2.4.1. I am using 3.0.6. In axon's documentation, section Aggregate Configuration, they say: Axon will automatically register all the @CommandHandler annotated methods with the Command Bus and set up a repository if none is present.Indignity
In the tutorial you mention they also say: if using Axon with Spring, use the AnnotationCommandHandlerBeanPostProcessor which allows us to have Spring beans that have methods annotated with @CommandHandler be turned into a command handler.Indignity
In my project the command handler is within the aggregate. In axon 3.0.6 there is not CommandHandler interface I could inherit from.Indignity
L
1

Bases on the comments

I will like to share the modified actual code.

@Aggregate
public class TaskAggregate {

    private Logger logger = LogManager.getLogger(TaskCreatedEvent.class);

    @AggregateIdentifier
    private int taskId;

    private String name;

    TaskAggregate(){ // default constructor needed for axon
    }

    @CommandHandler
    public void TaskAggregate(CreateTaskCommand createTaskCommand) {
        logger.info("Command received");
        AggregateLifecycle.apply(new TaskCreatedEvent());
    }

    @EventSourcingHandler
    public void onTaskCreatedEvent(TaskCreatedEvent taskCreatedEvent) {
        logger.info("Event received");
        this.name = taskCreatedEvent; // use this to set the aggregate meber than get and setter.
    }

}

@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.

Latoya answered 14/2, 2019 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.