Although replaying events is already documented in theofficial documentation, I was struggling to have it integrated into my Spring Boot application. In this article, I would like to share with you how I trigger and keep track of a replay of my projections with Axon Framework’s Spring Boot auto configuration.
When doing event sourcing with Axon Framework, many developers opt to project these events to the database, depending on their use case.
For example, anOrderPlacedevent can add an entry to the customer’s order history, so when a customer logs into his account, they have an overview of their complete order history.
This was good enough for a long time. But after some time you find that multiple customers are requesting to also be able to see the order status in their order history. Luckily, you’ve been usingOrderStatusChangedevents already, so all you have to do is add astatusfield to yourOrderHistoryItementity and add an@EventHandlerto update that with the changed status:
And presto!✨All new status changes will update their associated history items accordingly.
But wait, what about allpreviousOrderHistoryItementries? They have no order status! What to do? This is why replaying a projection sometimes is needed: to replay all (or part) of the events so the the projections can be made up-to-date.
For most use cases, you will want to replay all events from the beginning of the event stream. For that, we can reset theEventProcessor. But, we also need to reset our projections: we will be adding history items again and we don’t want customers to end up with double entries. For this, Axon offers the@ResetHandler:
Now our projections will have a clean slate when we reset them. 🛀
Play it again, Sam
To perform a replay (and reset), we will need to obtain theTrackingEventProcessorfor our projections. These are available in theEventProcessingConfiguration, which, in our case, is available as a Spring managed bean. In order to obtain the processor, we will need to know it’s name. You can either specify it by annotating your projections with@ProcessingGroup, or use the default: the package of the projections. For example, this could becom.shop.projections.
Let’s create a@Componentthat is capable of replaying:
You can inject this component and trigger the replay either at startup of your application (for example by listening forApplicationReady), or in some sort of controller allowing you to trigger it using a REST-call.
Considering you may potentially be replaying millions of events, doing a replay can take quite some time. It would be nice to be able to keep track of the replay so we know when it is done, for example in order to check if all is well. TheTrackingEventProcessoruses tokens to keep track of what events to process. A replay uses a special kind of token called aReplayToken, which we can use to determine our position in the replay. These tokens can be obtained using theTokenStore, using the same event processor name as above. An added complication here is that we need to do it for multiple segments, so we will end up adding multiple replay tokens together and calculating the progress using that.
TheReplayTokencontains a reference to the token when the reset was triggered, so we can use that to compare against. If noReplayTokencan be found, the replay is completed. A token can be queried for its position. Note that this is anOptionalLong, further complicating things: it might be impossible for the token to determine its position. In that case, we also assume no replay is currently underway.
We return an optional object containing the accumulated current position of all segments, with the accumulated position of where to reset took place (where applicable). For example, it may indicate we are at position 130 out of 304, resulting in our rebuild being 42% complete! 📈
We can create a REST-call to return these results, so we can periodically poll our system to see how far along we are.
In the example project, we use axon’sReplayStatusin order to determine whether or not the event is being handled as part of a replay. This can come in handy sometimes if for example your handlers send emails or push notifications. You probably do not want to do stuff like that during a replay. In our case, we use theReplayStatusto introduce a little artificial delay in order to demo the progression a bit easier. 😴
Alternatively, you could annotate the handler withDisallowReplayto completely skip it.
I ended up interacting with myReplayControllerusingSlack slash commands. That way I can trigger a replay and know how far along it is without actually building an admin interface for my application. I might explain how i did that in a future article.🔮
For now, this is how I managed to trigger Axon replays and keeping track of them in my Spring Boot application. I have created an example project, which isavailable on GitHub.
I hope you found this article useful for you and your projects. If it is, please let me know in the comments, and clap those hands! 👏👏👏