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.

Rodney
  • 53
  • 1
  • 5

4 Answers4

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.

Steven
  • 6,936
  • 1
  • 16
  • 31
  • 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)** – Rodney Oct 18 '17 at 14:24
  • 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. – Steven Oct 18 '17 at 15:15
  • 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? thanks – Pietro Feb 02 '21 at 07:53
  • 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: https://docs.axoniq.io/reference-guide/axon-framework/testing/commands-events – Steven Feb 03 '21 at 09:03
  • 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. – Steven Feb 03 '21 at 09:03
  • 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 https://github.com/gpietro/spring-boot-cqrs-demo/blob/master/src/test/java/com/fundev/adt/command/PatientTest.java#L60. All the tests pointing to the EpisodeOfCare (https://github.com/gpietro/spring-boot-cqrs-demo/blob/master/src/main/java/com/fundev/adt/command/EpisodeOfCare.java#L34) commandHandlers are not found. The logged error is: No handler was subscribed to command [com.fundev.adt.coreapi.CommandPatientAdmit] – Pietro Feb 04 '21 at 09:11
  • 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`. – Steven Feb 04 '21 at 14:13
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]

Allard
  • 2,640
  • 13
  • 13
  • 1
    A little bit confused because the [documentation](https://docs.axonframework.org/v/3.0/part3/spring-boot-autoconfig.html) 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. – Rodney Oct 18 '17 at 15:13
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)

Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
  • The tutorial you mention is using axon 2.4.1. I am using 3.0.6. In [axon's documentation](https://docs.axonframework.org/v/3.0/part3/spring-boot-autoconfig.html), 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.** – Rodney Oct 18 '17 at 13:25
  • 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.** – Rodney Oct 18 '17 at 13:27
  • In my project the command handler is within the aggregate. In axon 3.0.6 there is not CommandHandler interface I could inherit from. – Rodney Oct 18 '17 at 13:37
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.

shuaib
  • 71
  • 6