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) {
....
}
}
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 theRegistrationNeo4jEventHandler
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:
ListenerInvocationErrorHandler
-> In charge of handling exception thrown by the @EventHandler
annotated methods.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.