Search code examples
javaspringspring-bootcqrsaxon

Handling exceptions in axon when listening to the same event multiple times in same spring boot application


We are in the process of integrating axon to our existing spring boot application. We are using Axon 4.1.2 and Axon Server currently.

For example in the registration process, we fire a RegisterCommand, which is read by the RegisterAggregate and which fires the RegistrationDoneEvent.

There are two EventHandlers listening to this RegistrationDoneEvent. Which are RegistrationNeo4jEventHandler and RegistrationSqlEventHandler.

Everything works fine when there are no exceptions. However, when there IS an exception, let's say the Neo4JEventHandler receives the event if there is an exception, then the SqlEventHandler sill gets called and it seems everything is still rolled back on the SqlEventHandler even though the SqlEventHandler ran successfully.

How can we make it so that the SqlEventHandler complete and commits BUT the Neo4JEventHandler retries?

Secondly, how do we stop the retrying of the event entirely upon failure? Lets say if we had four event handlers (HandlerA, HandlerB, HandlerC, HandlerD) listening to the SAME event. If HandlerC fails we want to trigger it to be retried when we lets say fixed the underlying issue BUT also make sure that the other handlers which listened to the SAME event are not rerun.

The following code snippets include the aggregate and the event handlers.

RegisterAggregate

@Aggregate
public class RegisterAggregate {

    ....

    @CommandHandler
    public RegisterAggregate(RegisterCommand command) {
        apply(new RegistrationDoneEvent(command));
    }
}

RegistrationSqlEventHandler

@Service
@Transactional
public class RegistrationSqlEventHandler { 

    @EventHandler
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void on(RegistrationDoneEvent event) {
        ....
    }
}

RegistrationNeo4jEventHandler

@Service
@Transactional
public class RegistrationNeo4jEventHandler { 

    @EventHandler
    public void on(RegistrationDoneEvent event) {
        ....
    }
}

Solution

  • I will break down my answer into two, as you have posed not one, but two questions. Firstly, let's talk about this question of yours:

    How can we make it so that the RegistrationSqlEventHandler complete and commits BUT the RegistrationNeo4jEventHandler retries?

    From the naming it is already very evident both Event Handling Components (i.e. the class you write containing @EventHandler annotated methods) serve an entirely different Query Model. The first is going towards are RDBMS, whilst the second serves the purpose of updating a Graph Model through Neo4j.

    As such I find it highly likely that you would eventually have differing non-functional requirements for both. To be able to allow differing configuration for these, you will have to use distinct Event Processor instances for both. The Event Processor, the component in charge of managing the technical aspect of providing the events to your event handlers, as the place to configure things like exception handling, threading numbers, batch sizes, etc.

    Note that Event Processors come in two flavors: the SubscribingEventProcessor and the TrackingEventProcessor. These can shortly be described as an event push and an event pull mechanism, with the latter being the default as it enforces further segregation.

    To configure distinct event processors for both, you can either use the Configuration API provided by Axon. For Event Processor, that means interacting with the EventProcessingConfigurer. With it you can define different event processors, and later on assign your event handling components to the right instance. A short hand which you can employ is to add the @ProcessingGroup annotation on both event handling components, with distinct names in it. Especially if you are in a Spring Boot environment, this will suffice as far as configuring goes.

    Doing this segregation will ensure that exception scenarios within the RegistrationNeo4jEventHandler cannot have any undesired impact on the RegistrationSqlEventHandler and vice versa.


    Secondly, let's move to your other question:

    Secondly, how do we stop the retrying of the event entirely upon failure?

    To this end you will have to adjust the exception handling of your event handling process. Axon derives two levels of exception handling when it comes to event handling components:

    1. The ListenerInvocationErrorHandler -> In charge of handling exception thrown by the @EventHandler annotated methods.
    2. The ErrorHandler -> In charge of handling exception thrown within the Event Processor.

    The default implementations of these will respectively log the errors (with the LoggingErrorHandler) and propagate the exception (with the PropagatingErrorHandler).

    FYI, the reference guide has this to say on error handling for Event Processors.

    In this question, you are further specifying your case with distinct event handling functions. Again, if you don't want to influence failures from one event handling to cause problems for another, you will likely want to segregate these concerns with distinct event processors.

    Mind you though, if these four event handlers your are exemplifying are just updating a query model, then a subsequent call should just perform the same operation without any side effects. If these event handlers however perform some other (external) activity, like sending an email, that definitely warrants segregation of the given Event Handling Component into a distinct Event Processor which you wouldn't want to retry/replay.