How to handle Commands in other Services

Hey,

I’m currently trying to create a command message from Service A that should be handled in Service B. The documentation mentions that alternatively, Kafka, Spring Cloud, etc. can be used, but it also states that Axon Server supports this natively, so I would prefer to use Axon alone.

Both services are running in separate Docker containers and are connected to the Axon Server (which is also running in its own container). I can view them both in the Axon Dashboard, and everything else works fine (like creating aggregates, members etc.).

However, whenever I send a command to the CommandGateway, I always receive a NoHandlerForCommandException (so it is not recognizing the handler defined in Service B). And when I try to directly send an event via the EventBus as an alternative, it is listed in the Axon Server, but ignored in Service B, too.

In both services, I have created the commands/events in the same path, as I read that Axon uses the path for identification, but that did not change anything.

data class CreateSomethingCommand(  
  val message: String  
)  
  
data class SomethingCreatedEvent(
  val message: String  
)

Service A

@Service  
class ServiceA(  
  @Autowired private val commandGateway: CommandGateway,  
  @Autowired private val eventGateway: EventGateway  
) {  
  private val logger = KotlinLogging.logger {}  
  
  fun testCommandRouting(message: String): ResponseEntity<String> {  
    logger.info { "Test command routing: $message." }  
  
    val response = commandGateway.sendAndWait<String>(  
      CreateSomethingCommand(message)  
    )  
  
    return ResponseEntity.status(HttpStatus.OK).body(response)  
  }
  
  fun testEventRouting(message: String): ResponseEntity<String> {  
    logger.info { "Test event routing: $message." }  
  
    eventGateway.publish(SomethingCreatedEvent(message))  
  
    return ResponseEntity.status(HttpStatus.OK).build() 
  }
}

Service B

@Component  
class ServiceB {  
  private val logger = KotlinLogging.logger {}  
  
  @CommandHandler  
  fun handle(cmd: CreateSomethingCommand): String {  
    logger.info { "Create something..." }
    
    return "Something created..."
  }  
  
  @EventHandler  
  fun on(evt: SomethingCreatedEvent) {  
    logger.info { "Something created." }
  }  
}

What else could I do, or am I missing some additional configuration? It seems to me, that the command and handler do not correspont to each other at all.


I have now created an external library for the commands and events so that the paths are identical and included in both services accordingly. Now, Service B is at least responding to something, and the NoHandlerForCommandException in Service A is no longer occurring, but now the following error is being thrown in Service B:

Exception in thread "CommandProcessor-0" com.thoughtworks.xstream.security.ForbiddenClassException: commands.TestRoutingCmd
        at com.thoughtworks.xstream.security.NoTypePermission.allows(NoTypePermission.java:26)
        at com.thoughtworks.xstream.mapper.SecurityMapper.realClass(SecurityMapper.java:74)
        at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:125)
        at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:47)
        at org.axonframework.serialization.AbstractXStreamSerializer.classForType(AbstractXStreamSerializer.java:171)
        at org.axonframework.serialization.LazyDeserializingObject.<init>(LazyDeserializingObject.java:83)
        at org.axonframework.serialization.LazyDeserializingObject.<init>(LazyDeserializingObject.java:63)
        at org.axonframework.axonserver.connector.command.GrpcBackedCommandMessage.<init>(GrpcBackedCommandMessage.java:54)
        at org.axonframework.axonserver.connector.command.CommandSerializer.deserialize(CommandSerializer.java:146)
        at org.axonframework.axonserver.connector.command.AxonServerCommandBus$CommandProcessingTask.run(AxonServerCommandBus.java:304)
        at org.axonframework.axonserver.connector.PriorityRunnable.run(PriorityRunnable.java:58)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:833)

I’m not sure if that goes into the right direction, but if so, does anybody know how to fix these permissions? :sweat_smile:


With the help of this Thread it seems to work right now.

Here is my adjusted version:

@SpringBootApplication
@Import(AxonConfig::class)
class Application

fun main(args: Array<String>) {
  runApplication<Application>(*args)
}

@Configuration
class AxonConfig {
  @Bean
  fun xStream(): XStream {
    val xStream = XStream()
    xStream.allowTypesByWildcard(
      arrayOf(
        "com.myproject.**",
        "commands.**",
        "events.**"
      )
    )
    return xStream
  }
}

It seems like it’s not the same command in both services. As service A sends a CreateSomethingCommand, but service B handles CreateSomethingCmd. That could be a typo but is likely the issue. If you share the Command messages in a shared API module, it would be easier to be sure to use the ‘same’ commands on both sides.

Sorry, that was just a typo!

Is it generally recommended to put all commands and events into a shared api, or just the parts that need to be shared like in my example?

Just the shared ones would be better. Ideally, you probably also use those on a specific ‘integration’ context. Especially for events you need to be extra careful when making changes, even more so when they are used across services. But also for queries and commands you need to be careful, as they won’t all move to a new version at the same time.

1 Like