DistributedCommandBus not working with AxonServer

Hello,

I want to send commands between two bounded contexts. I am using Axon Server as an event store and message handler. From what I read so far, Axon Server should automatically register as a distributed command bus. However, I get a NoCommandHandlerFoundException whenever I send a command using the command gateway.

In each service, I have identical classes for the command. I only use String and UUID as attribute data types. The packages in which the classes are located are identical as well.

Am I missing some configuration?

Hello @danstoofox, do the classes share the same package? The (default) command name is the fully qualified class name of the command.

Yes, the packages are the same.

One additional aspect I haven’t mentioned yet is, that the two services are connected to separate contexts in Axon Server

These are the command classes from both services:

package de.tailord.workdutyplanning.api.command;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.axonframework.commandhandling.RoutingKey;

import java.util.UUID;

@Getter
@AllArgsConstructor
public class ApiCreateQualification {
    @RoutingKey
    private final UUID id;
    private final String title;
    private final String abbreviation;
}
package de.tailord.workdutyplanning.api.command;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.axonframework.commandhandling.RoutingKey;

import java.util.UUID;

@Getter
@AllArgsConstructor
public class ApiCreateQualification {
    @RoutingKey
    private final UUID id;
    private final String title;
    private final String abbreviation;
}

I publish the event in service A:

@EventHandler
    public void on(QualificationCreated event) {
        commandGateway.send(new ApiCreateQualification(
                event.getId().getValue(),
                event.getTitle(),
                event.getAbbreviation()
        ), LoggingCallback.INSTANCE);
    }

and receive the command in service B like this:

@Component
public class QualificationCommandHandler {

    private final CommandGateway commandGateway;

    public QualificationCommandHandler(CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    @CommandHandler
    public void handle(ApiCreateQualification command) {
        commandGateway.send(new CreateQualification(
                new QualificationId(command.getId()),
                command.getTitle(),
                command.getAbbreviation()
        ), LoggingCallback.INSTANCE);
    }
}

Alright that clears it up a lot more for me, thanks!

The AxonServerCommandBus, used by the CommandGateway to send commands, is currently only connected to one context (let’s call it A). However, you want the command to be handled in context B.

You can supply a TargetContextResolver to the AxonServerCommandBus to resolve the context it has to send commands to. By default this resolves to context A. Writing one that resolves this command to context B should do the trick for you.

It should look like this:

@Configuration
public class AxonServerConfiguration {
    @Bean
    public TargetContextResolver<CommandMessage<?>> targetContextResolver() {
        return message -> {
            if(message.getCommandName().startsWith("de.tailord.workdutyplanning.api")) {
                return "Context B";
            }
            return "Context A";
        };
    }
}

This bean is automatically picked up by the AxonServerAutoConfiguration

Thank you!

I had to rename the configuration class because it conflicted with a bean in the axon starter package. I also needed to add a TargetContextResolver for query messages.

@Bean
public TargetContextResolver<QueryMessage<?, ?>> queryTargetContextResolver() {
    return message -> "Context A";
}

After this, my bounded contexts can finally talk to each other :sweat_smile:

What is the reasoning behind the target context resolver? I imagine, that an additional target context parameter for the command gateway would be more intuitive. I don’t like that every command has to be resolved now with this redundant check 99% of the time. The only argument I can think of is location transparency but to me, this is also violated with this custom resolver that looks at the package and sets the context. I would like it to be more explicit when dispatching the command and not in some configuration bean.

2 Likes

Great to hear you got it working.

As for the reasoning behind this, I think it has to do with legacy. Axon Framework has been around longer than Axon Server, and multi-context is something that came into being much later. It’s actually implementation-specific for the DistributedCommandBus, so that might also be the reason it’s not part of the generic CommandBus or CommandGateway interfaces.

Having said that, we are always looking for improvements and we will start working on Axon 5 in the near future. It would be great if you could suggest this here: Axon Framework 5 suggestions - #11 by Gerard

2 Likes