Access Global Event Sequence from Event Handlers

Hello there,

we are developing a CQRS application using Axon Server 4.2 with two Java microservices running the command and query sides. To back the persistent query model, we use a relational database.

Currently we are working on implementing cursor-based pagination for the query side (https://phauer.com/2018/web-api-pagination-timestamp-id-continuation-token/#continuation-token-approaches) and thus need to ensure a stable natural ordering of the entities in our tables.
Currently we’re using a single instance of our query-side application that uses a single TrackingEventProcessor with a SequentialPolicy to ensure that events are always processed in the same order. This however is starting to cause considerable read-model latency as complexity and load on our application grows. Thus we would like to switch to a TrackingEventProcessor with the default SequentialPerAggregatePolicy to speed up things and gain scalability for our query-side application.

Having a single instance and the SequentialPolicy in place made it easy for us to determine a stable, sequential identifier to be added to each database row when a new entity was created since the events are guaranteed to be processed strictly sequential in the order in which they appear in the event store and we could use a simple auto-increment field to have a sequence that is stable even when we reset the database and replay all events.

When switching to a SequentialPerAggregatePolicy this is not true anymore, since events will be processed in paralell and we cannot guarantee that they are processed in the same order when we reset the database and replay all events. We would need to rely on some sort of global index that is already persistent in the underlying event stream and thus will not change, like the token of the event store. From the Axon documentation on Event Handlers (https://docs.axoniq.io/reference-guide/implementing-domain-logic/event-handling/handling-events) we know that the @SequenceNumber is available as parameter, but this is only sequential within a single Aggregate instance.
Is there any simple way to get a “global” index (like the token) in the Event Handler that reflects the global order of the event in the event store?

Thans for your insights,
Jakob

Hi Jakob,

In your eventHandling method you could add ‘TrackingToken trackingToken’ to the signature and use the ‘position()’ method on the trackingToken

`

@EventHandler
public void on(SomeEvent event, TrackingToken trackingToken) {
long index = trackingToken.position().orElseThrow();
}

`

Kr,

Tom

Hi Tom,

thanks for your suggestion, I’ll definitely give it a try.
I did not find the possibility to use TrackingToken as handler parameter in the docs, so I’m curious if this means this is an unofficial API and subject to change or if it is simply missing in the docs (then we should open an issue on the axon docs repo)?

BR,
Jakob

In this case, I would say it simply missing in the docs.

The only ‘disadvantage’ of this approach is that it only works when using a TrackingProcessor. The SubscribingProcessor doesn’t attach TrackingTokens to events. Actually, I’m not even sure what would happen in that case. Worst case, the handler methods will be seen as ‘not matching the message’ and ignored.

You’re welcome to submit an issue (or PR) to the ref guide project.

Cheers,

Allard

I just did a test in one of our projects and when using a SubscribingProcessor the method with the trackingToken does not get invoked (expected).

But axon has this mechanism where it only executes the most specific method so you could do the following:

`

@EventHandler
public void on(SomeEvent event, TrackingToken trackingToken) {
longtrackingToken.position().orElseThrow();
}

@EventHandler
public void on(SomeEvent event) {
on(event, /* create a dummy token or pass null */);
}

`

Hey Allard and Tom,

thanks for your replies. Having the TrackingToken only available with TrackingProcessors is totally fine for us, since the query component is only using TrackingProcessors - so no Problem there. Still good to know how a SubscribingEventProcessor would behave (thanks Tom for sharing your insights).

Regarding the docs I’ll file an issue for the reference guide.

Thanks and Best Regards,
Jakob