The aggregate was not found in the event store

I am trying to implement a 404 response for a RestController in Spring Boot that runs an Axon-Command.

When I execute the Command on a non-existing aggregate ID I get the following exception from the CommandGateway:

Root:
org.axonframework.commandhandling.CommandExecutionException: The aggregate was not found in the event store

Cause:
AxonServerRemoteCommandHandlingException{message=An exception was thrown by the remote message handling component: The aggregate was not found in the event store, errorCode='AXONIQ-4002', server='21604@gl65'}

Is there any way to decide that the cause of a CommandExecutionException is a non-existing aggregate ID? The only way I see is to:

  1. match the cause.errorCode which I guess only matches, when I use the AxonServer and the errorhandler would not detect “aggregate not found” using other backends like JPA, etc.
  2. run a regex on the message and check for the “not found” phrase. Which seems very dirty and also unlikely to work with other backends.

I don’t like those options, so I wonder if there is a way to check if an aggregate id exists just from the exception (without doing an extra query to check it).

I got the same issue this morning to.

Hi,

exception handling is always a nice topic. In the case of Axon, while the abstractions make it very easy to do remote calls, we should still realize these are remote calls. When doing HTTP calls, you don’t get a stacktrace back either. At best, you get a String representation of that stacktrace. But that’s an anti-pattern, since it exposes your application’s internals, which may pose a security risk.

What you would do with HTTP calls, it catch any exceptions on the server side, and translate them into meaningful response structures (e.g. JSON).

In Axon, you can (and should) do exactly the same thing. On the handling side, you can either throw a HandlerExecutionException (or one of it’s subclasses) with a details object as a parameter to the constructor, or just cleanly return an object with details.

In both cases, the details object is an application-specific data structure that allows you to “inform” the sender that something went wrong, what, and what needs to happen to fix it. In the case of the Exception, the transaction is rolled back. If you return a details object “cleanly”, the handler transaction is committed.

There are different places where you can configure this. On way is using @ExceptionHandler annotated methods in the Aggregate. However, this only works when the exception is thrown from within the aggregate. With an AggregateNotFoundException, it never actually reached the aggregate.
For that case, you can use a MessageHandlerInterceptor, which you configure on the Command Bus. Simply proceed() all calls in a try/catch block and wrap the exceptions that you’re interested in.

1 Like

On way is using @ExceptionHandler annotated methods in the Aggregate. However, this only works when the exception is thrown from within the aggregate. With an AggregateNotFoundException, it never actually reached the aggregate.

This is the main issue I have, the exception handler where I could introduce my own exceptions never ran it this case. I will try the MessageHandlerInterceptor soon and share the results here.

I tried the 2 available kinds of Interceptors, both don’t really solve the problem as I described:

org.axonframework.messaging.MessageDispatchInterceptor

  • This intercepts EVERY command, so you need logic to select the correct commands where the exception should be thrown
  • It is not possible to throw checked exceptions, because the interface wants you to return a java.util.function.BiFunction. But the RuntimeException is not wrapped when using the CommandGateway so you can catch it normally and create the 404 response
  • there is no way to check if the aggregate Id exists in the CommandMessage in the Interceptor implementation, so an extra query is required that would run on every single command

org.axonframework.messaging.MessageHandlerInterceptor

You can catch the org.axonframework.modelling.command.AggregateNotFoundException here (GREAT!).

But could not find a way to communicate that back at the time you use the CommandGateway, you get the same CommandExecutionException with Cause AxonServerRemoteCommandHandlingException

But it seems like at least in the AxonServerRemoteCommandHandlingException the exception message of exceptions thrown in the Interceptor is uses, so at least the matching is easier. I don’t know if that is also true for other CommadBus implementations.

So seems like the only way to catch your own exceptions in the commandGateway is the MessageDispatchInterceptor with RuntimeExceptions. But in any case, there is a query necessary to check if the aggregate Id exists, which is very bad in a interceptor in my opinion.

So the Interceptors don’t work for me, I am back at the 2 ways I described in the first message :frowning:


For those that, like me, just did a tutorial using all defaults, I want to also share the code I used to register the handlers. The documented way wants you to provide your own org.axonframework.commandhandling.CommandBus bean, which is very inconvenient because the auto-configured org.axonframework.axonserver.connector.command.AxonServerCommandBus needs 8 dependencies to build and the axon starter doesn’t provide a “configurer” to register interceptors.

So I recommend to annotate the MessageXInterceptors with org.springframework.stereotype.Component and then register them using a org.springframework.context.ApplicationListener

@Component
public static class InjectInterceptor implements ApplicationListener<ApplicationReadyEvent> {

    private final CommandBus commandBus;
    private final List<MessageDispatchInterceptor> dispatchInterceptors;
    private final List<MessageHandlerInterceptor> handlerInterceptors;
    
    public InjectInterceptor(
        CommandBus commandBus,
        List<MessageDispatchInterceptor> dispatchInterceptors,
        List<MessageHandlerInterceptor> handlerInterceptors
    ) {
        this.commandBus = commandBus;
        this.dispatchInterceptors = dispatchInterceptors;
        this.handlerInterceptors = handlerInterceptors;
    }
    
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        dispatchInterceptors.forEach(commandBus::registerDispatchInterceptor);
        handlerInterceptors.forEach(commandBus::registerHandlerInterceptor);
    }
    
}

I think the key you’re missing when it comes to sharing a working exception is the required reuse of the CommandExecutionException.

It’s the exception that allows you to provide a details Object. This object will be serialized by the framework and be serialized back to whatever object you expect it to be on the dispatching end.

I’ve actually done a live coding session where I show this. Assuming it might prove useful to iron out some of the problems - Steven van Beelen - Live coding session: Axon 4.4 in practice - YouTube

1 Like