Sadly, a full-fledged solution is rather non-trivial. At least from what Axon Framework would optimally provide.
There is a means you could get started with this right now though, although it might be quite some custom code. Regardless, I will give some of our insights on the matter so that you can give it a head start.
To allow for correct dead-letter queuing, Axon requires two things:
- It needs to be able to catch the exception, in combination with knowing which event caused the exception.
- It needs to be able to understand in which “overall group” the event belongs.
I assume point one is evident, point two might require some explanation.
Point two argues that if an event has failed, this was very likely targeted towards a specific Query Model instance. To ensure event handling maintains the correct ordering for this model, any subsequent events targeted towards this exact model should not be handled at all. Within Axon Framework, the SequencingPolicy
decides where an event belongs.
Adding Events to the Dead Letter queue
Now, with that piece of information, we can take a look at the most reasonable spot to provide a custom solution for dead lettering. The one point within the framework which accounts for both these points, is the EventHandlerInvoker
. Most straightforward would be to create a custom implementation based on the SimpleEventHandlerInvoker
(e.g. DeadLetteringEventHandlerInvoker
). Overriding the handle(EventMessage<?>, Segment)
would be required. When handling an Exception
, it should be added to the dead letter queue. Prior to actually invoking the event handlers, you would have to validate, based on the SequencingPolicy
, if there are events with the same value dead-lettered and if they are the event should be added.
Maintaining the Dead Letter queue
Now that we’ve found a means to figure out if events belong or should belong to a/the dead letter queue, we need to think about where to store this information. Typically Axon’s TrackingToken
s would be the right position, but “messing” with this wouldn’t be feasible for a custom solution. I would thus recommend to introduce a table inside an RDBMS where you store the dead letter messages in.
You can either do this directly in the DeadLetteringEventHandlerInvoker
or you can create a DeadLetterStore
/DeadLetterQueue
component which encapsulates this storage behavior. It’s this component which would be invoked to validate if there are any events present already for the given sequencing value.
Reevaluating the Dead Letter queue
With the above in place, we can start thinking about when to reevaluate the dead letter queue. There are several clean approaches imaginable, like using a MultiStreamableMessageSource
or by looking into adjusting the TrackingEventProcessor
to periodically check for dead letter events. However, this is again to farfetched for a custom solution.
I would instead opt (again) to make the DeadLetteringEventHandlerInvoker
in charge of reevaluating the events. This should be done per sequencing value (as this dictates a set of events which are targeted towards a given model), and one event per time. Thus don’t handle the entire batch of events within the queue at once. Event Handling failed once, and it might be very likely it would again. Going over the set one event per time is simply the most granular approach to getting back to track here.
The trigger for a reevaluation can come from several angles really. You could have a scheduled task through an ExecutorService
which does this periodically. Or, you would introduce an endpoint you can invoke yourself based on the amount of dead-lettered events.
Conclusion
I hope this sheds some light on what your options are @Koen_Verwimp. It definitely sheds some light on our own thoughts to implement this inside the framework itself.
If you happen to have any further questions towards the custom/generic solution, definitely let us know in this thread.