Replaying projections with Axon and Spring Boot
Introduction
Although replaying events is already documented in the official 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.
Background
When doing event sourcing with Axon Framework, many developers opt to project these events to the database, depending on their use case.
For example, an OrderPlaced
event 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.
When implementing this using spring-data-jpa
it almost looks trivial:
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 using OrderStatusChanged
events already, so all you have to do is add a status
field to your OrderHistoryItem
entity and add an @EventHandler
to update that with the changed status:
And presto!✨All new status changes will update their associated history items accordingly.
But wait, what about all previous OrderHistoryItem
entries? 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.
Rewind selecta
For most use cases, you will want to replay all events from the beginning of the event stream. For that, we can reset the EventProcessor
. 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 the TrackingEventProcessor
for our projections. These are available in the EventProcessingConfiguration
, 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 be com.shop.projections
.
Let’s create a @Component
that is capable of replaying:
You can inject this component and trigger the replay either at startup of your application (for example by listening for ApplicationReady
), or in some sort of controller allowing you to trigger it using a REST-call.
Keeping track
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.
The TrackingEventProcessor
uses tokens to keep track of what events to process. A replay uses a special kind of token called a ReplayToken
, which we can use to determine our position in the replay. These tokens can be obtained using the TokenStore, 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.
The ReplayToken
contains a reference to the token when the reset was triggered, so we can use that to compare against. If no ReplayToken
can be found, the replay is completed. A token can be queried for its position. Note that this is an OptionalLong
, 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.
ReplayStatus
In the example project, we use axon’s ReplayStatus
in 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 the ReplayStatus
to introduce a little artificial delay in order to demo the progression a bit easier. 😴
Alternatively, you could annotate the handler with DisallowReplay
to completely skip it.
In conclusion
I ended up interacting with my ReplayController
using Slack 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 is available 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! 👏👏👏