Dispatching a query from one microservice and handling it in another microservice

Hi there,

I am fooling around trying to understand fundamentals of Axon. I’m deeply impressed by the framework and I really want to use it.

So far I have two services: People and Gateway.
In the People service’s rest controller I dispatch a query called getit to get a person by id.
The People service provides a handler for the query.

This works perfectly fine.

Now, when I dispatch the same query from the controller of the second service called Gateway I get this error in the People service:

2024-01-26T10:26:23.819+01:00 WARN 27939 — [ueryProcessor-2] o.a.a.c.query.QueryProcessingTask : Query Processor had an exception when processing query [getit]

com.thoughtworks.xstream.converters.ConversionException: Cannot load java class com.ros.gateway.query.PersonEntity
---- Debugging information ----
message : Cannot load java class com.ros.gateway.query.PersonEntity
class : java.lang.Class
required-type : java.lang.Class
converter-type : com.thoughtworks.xstream.converters.SingleValueConverterWrapper
wrapped-converter : com.thoughtworks.xstream.converters.extended.JavaClassConverter
path : /org.axonframework.messaging.responsetypes.InstanceResponseType/expectedResponseType
line number : 1
class[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
required-type[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
version : 1.4.20

In the Gateway service I get:

2024-01-26T10:26:23.833+01:00 ERROR 28954 — [nio-7777-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path threw exception [Request processing failed: java.lang.RuntimeException: java.util.concurrent.ExecutionException: org.axonframework.queryhandling.QueryExecutionException: Cannot load java class com.ros.gateway.query.PersonEntity
---- Debugging information ----
message : Cannot load java class com.ros.gateway.query.PersonEntity
class : java.lang.Class
required-type : java.lang.Class
converter-type : com.thoughtworks.xstream.converters.SingleValueConverterWrapper
wrapped-converter : com.thoughtworks.xstream.converters.extended.JavaClassConverter
path : /org.axonframework.messaging.responsetypes.InstanceResponseType/expectedResponseType
line number : 1
class[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
required-type[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
version : 1.4.20
-------------------------------] with root cause

org.axonframework.axonserver.connector.query.AxonServerRemoteQueryHandlingException: An exception was thrown by the remote message handling component: Cannot load java class com.ros.gateway.query.PersonEntity
---- Debugging information ----
message : Cannot load java class com.ros.gateway.query.PersonEntity
class : java.lang.Class
required-type : java.lang.Class
converter-type : com.thoughtworks.xstream.converters.SingleValueConverterWrapper
wrapped-converter : com.thoughtworks.xstream.converters.extended.JavaClassConverter
path : /org.axonframework.messaging.responsetypes.InstanceResponseType/expectedResponseType
line number : 1
class[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
required-type[1] : org.axonframework.messaging.responsetypes.InstanceResponseType
converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
version : 1.4.20

Here’s the code from People:
Message Handler:

@Slf4j
@Component
@ProcessingGroup("person")
public class PersonQueryHandler {
    private PersonEntityRepository personEntityRepository;

    @QueryHandler(queryName = "getit")
    PersonEntity handle(GetPersonByIdQuery query) {
        log.info("Query getit " + this.personEntityRepository.getPersonById(query.getId().toString()));
        //System.out.println(query.getId().toString());
        return this.personEntityRepository.getPersonById(query.getId().toString());
    }
}

Query dispatching in Peoples controller (works fine):


    @GetMapping(value = "/test/{id}")
    public PersonEntity getTest(@PathVariable(value = "id") String id) {
        GetPersonByIdQuery getPersonByIdQuery = new GetPersonByIdQuery(UUID.fromString(id));
        CompletableFuture<PersonEntity> future = queryGateway.query("getit",getPersonByIdQuery, ResponseTypes.instanceOf(PersonEntity.class));
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

Query dispatching from different service (Gateway). Throws errors:


    @GetMapping("/p/{id}")
    public PersonEntity getPersonById(@PathVariable(value = "id") String id){

        GetPersonByIdQuery getPersonByIdQuery = new GetPersonByIdQuery(UUID.fromString(id));
        CompletableFuture<PersonEntity> future = queryGateway.query(
                "getit", getPersonByIdQuery, ResponseTypes.instanceOf(PersonEntity.class)
        );
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

To me it looks like theres a problem with the ResponseType class called PersonEntity.
It’s declared in the People service:

package com.ros.axonversuch.query;

public class PersonEntity {

    public String id;

    public String firstName;
    public String lastName;

    public PersonEntity() {}

    public PersonEntity(String id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

And it’s declared in the Gateway service as well:

package com.ros.gateway.query;

public class PersonEntity {

    public String id;

    public String firstName;
    public String lastName;

    public PersonEntity() {}

    public PersonEntity(String id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Dispatching the query from the Gateway service with:

    CompletableFuture<PersonEntity> future = queryGateway.query(
            "getit", getPersonByIdQuery, ResponseTypes.instanceOf(PersonEntity.class));

instanceOf(PersonEntity.class) refers to the class defned in it’s package:
com.ros.gateway.query.PersonEntity

But the handler in the People service defines it’s own class called PersonEntity in it own package: package com.ros.axonversuch.query.PersonEntity.

That may be the reason why xstream can’t properly de/serialize?
I have tried setting up axon using jackson instead of xstream without success.
I’ve also tried axon server EE. Same behavior.

I’ve been chewing on this one for two days. It would be great if one of you guys and gals could spread some light. It’s probably something more fundamental that I don’t get.

Thank you!

Lorenz