Replaying projections with Axon and Spring Boot

Rolf Suurd
13 December 2019

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:

Image for post
Typical projections with Axon and Spring data

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:

 
Image for post
A new event handler to update our read model

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:

All we need to do is delete everything in the repository 🗑️

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:

Image for post
Shut down before resetting — then start!

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.

 
Image for post
Not very pretty, is it?

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.

 
Image for post
Demo!

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! 👏👏👏

Subscribe by Email

No Comments Yet

Let us know what you think