Migrating from Axon 3 - 4

Hi guys!

There is no documentation in the migration doc here:
https://docs.axoniq.io/reference-guide/3-migration/migration-guide

Most of the upgrades were classes moving around. That was no big deal.

The biggest change was the default move to tracking from subscribing event processing.

It made me have to create a:

@Bean(name = "tokenStore")
public TokenStore tokenStore() {
    return MongoTokenStore.builder()
            .mongoTemplate(axonMongoCMSTemplate())
            .serializer(xStreamSerializer())
            .build();
}

I could see that 21 entries were successfully being created; each entry corresponding to packages that contain event handlers. By default, tokenType is set to: [B and token is a binary value. When it gets time to deserialize, Axon doesn’t know what to do with type [B.

@Bean(name = "jacksonSerializer")
public Serializer jacksonSerializer() {
    return JacksonSerializer.builder()
            .objectMapper(this.objectMapper)
            .converter(chainingConverter())
            .build();
}

@Bean(name = "chainingConverter")
public ChainingConverter chainingConverter() {
    ChainingConverter converter = new ChainingConverter();

    converter.registerConverter(new StringToMongoTrackingTokenConverter(this.objectMapper));
    converter.registerConverter(new MongoTrackingTokenToStringConverter(this.objectMapper));
    converter.registerConverter(new StringToJsonNodeConverter(this.objectMapper));
    converter.registerConverter(new JsonNodeToStringConverter());

    return converter;
}

I tried to create custom converters and instead use the jacksonSerializer for the tokenStore. I changed the contentType on MongoTokenStore to use either String, JsonNode, MongoTrackingToken instead of the default byte[] but to no avail. When I set the content type to String, I could see the token in json text in my collection. But, when it tried to deserialize again, it couldn’t find a proper converter.

Right now, this is my one blocker.

I am using Spring Boot. When I first upgraded from 3 to 4 and hadn’t yet configured a tokenStore, the default in-memory one went into effect. Once my application had started, the tracking processor couldn’t find any previous position so it started processing all my events starting with the very first one. I don’t want that to happen when I deploy this to production as I’d be faced with some very confused customers and a whole lot of automatic emails :wink: I saw there was some way to control the position of the processor here:
https://docs.axoniq.io/reference-guide/1.3-infrastructure-components/event-processing#custom-tracking-token-position. But the documentation doesn’t tell me how to configure it in a Spring Boot environment. I am guessing it is something like

@Autowired
public void configure(EventProcessingConfigurer config) {
    config.usingSubscribingEventProcessors(); // default all processors to subscribing mode.

}

But the documentation is not very clear on this.

I really wanted to move over to using tracking once I realized that’s the proper way to do replays. However, I can’t see a straight migration path from a production system that’s currently using subscribing event processor.

So this is where I am stuck at the moment. I will go ahead and deploy my upgrade from Axon 3 - 4, still using subscriber event processing. I would love to see a migration document that can show me a migration path from subscriber to tracking and that includes answers to my questions above. I would also love to know how to migrate from xStream to Jackson in a production system without any downtime.

Finally, on a side note, when the tracking processor started processing my old events from 2 years back, it failed to process some of the events. It seems on some older version of Axon, timestamp was referred to as timeStamp and it couldn’t find that property and promptly failed.

Thank you,
Bjorn

registerTrackingEventProcessor

Hi Bjorn,

The migration guide is a pending implementation, which is currently biting you…we’re sorry for that Bjorn!

It’s on the to-do list for the Reference Guide. We’re however first updating the overall look and feel of the guide to be up to par with how we view the framework could (and should) be used.

A part from that, you’re encountering a couple of things:

  • Mongo Tokens:

I would not assume you’d have any trouble with de-/serializing MongoTrackingTokens.

It is a correct assumption that you initially did not have any tracking tokens, right?
If that’s the case, I do not fully comprehend why the serializer wouldn’t be able to cope with them.

Nonetheless, I can tell you that you shouldn’t have to bother with custom ContentTypeConverters to get it to work.

What you could try is to use the XStreamSerializer for your tokens however.

Configuring the serializer can be done on several levels, namely default, message and event.

Typically, just setting the eventSerializer to something like the JacksonSerializer is desired, whilst the rest could just as well stay in the default XML format.

  • Tracking Event Processor token initialization:

You can configure where your TrackingEventProcessor initialize their token.
This is done by instantiating a TrackingEventProcessorConfiguration, either with the forSingleThreadedProcessing() or forParallelProcessing(int) static functions on that class, and calling the andInitialTrackingToken() function.
The andInitialTrackingToken() expects a Function<StreamableMessageSource, TrackingToken> as input. The StreamableMessageSource in turn has function to create a tail, head or token-at TrackingToken.

If you use the StreamableMessageSource#createHeadToken() function here, you’ll ensure the tracking event processor will start with an initial token at the head of the event stream.
This thus ensure your query models will not be created a new, or that any undesired emails are sent for a second time.

The TrackingEventProcessorConfiguration instance should be provided when registering your Tracking Event Processors with the EventProcessingConfigurer#registerTrackingEventProcessor() function.

The last parameter of that function allows you to provide the TrackingEventProcessorConfiguration instance.
This thus requires some work to configure correctly, but it should be doable.
And, your concern is heard, we will add this to the migration guide.

  • Old event side note:
    Could you specify which version you mean with ‘older version of axon 3’?

I’ve been in the loop with one Axon project for over 2 years now, following every Axon Framework upgrade since 3.0 release candidates.
Can’t recall we’ve hit such an issue yet though, so I am curious what this is exactly.

Hope this helps you out Bjorn.
And please do not hesitate to pose further questions in regards to your migration!

Cheers,
Steven

Hi Steven!

Thank you for your detailed response.

  1. You are correct. I did not have any tracking tokens before I upgraded. I have tried using both xstream and jackson and whichever way I try to configure it, I run into deserialize [and sometimes serialize] errors. What I don’t understand is that MongoTokenStore doesn’t like the default setup, where I don’t indicate the content type. Is it correct that the byte[] tokenType should serialize to “[B”? When it tries to deserialize, Axon doesn’t know what [B means. That goes for both xstream and jackson serializers.

  2. Regarding TrackingEventProcessorConfiguration. Do you suggest I create a Spring @Bean instance of this class or does it get instantiated someplace else?

The last time I can see “timeStamp” in our domainevents collection corresponds with our upgrade from Axon 2 to Axon 3 (June 26th 2017). I do have axon-legacy dependency included. Around the same time, a few customers also reported not being able to update their data, because Axon couldn’t find the aggregate. I wonder if that had something to do with it. Have still not figure out the cause for that. But this is speculation and off topic.

Thank you again and look forward to your response. Would love to get 1 and 2 out of the way :slight_smile:

Hi Bjorn,

No problem Bjorn, I am happy to help you out here! :slight_smile:

Let’s go back to your questions:

  1. I’ve just done some testing locally to figure out this issue you’re describing.

What I encountered when using Axon 4, thus the Axon Mongo Extensions 4.0, is that the MongoTokenStore is incorrectly storing the token type.

I’ve marked this as a bug, which you can find here. I’ll resolve this ASAP, which should result in a 4.0.1 release of 4.0.1.

The only Serializer hitting this issue was the JacksonSerializer however.

Thus, if time is of the essence, you could set the XStreamSerializer for your MongoTokenStore, which I assume should do the trick.

You’ve however pointed out that you’ve tried using the XStreamSerializer as well, with no avail…this might happen due to some irregularities in your configuration.

As you’re in a Spring Boot environment, simply creating a Serializer bean with the name ‘serializer’ should set the default serializer to what you desire.

If you’d want Jackson for your events, you can simply instantiate a Serializer bean with the name ‘eventSerializer’.

  1. What I would suggest to configure all your TrackingEventProcessor in such a fashion, is to have a function like this somewhere in your configuration:
@Autowired
public void configureTrackingEventProcessors(EventProcessingConfigurer configurer) {
    // List of all the processing groups you have in your application
    List<String> processingGroups = new ArrayList<>();

    processingGroups.forEach(processingGroup -> {
        TrackingEventProcessorConfiguration trackingProcessorConfig =
                TrackingEventProcessorConfiguration.forSingleThreadedProcessing()
                                                   .andInitialTrackingToken(StreamableMessageSource::createHeadToken);

        configurer.registerTrackingEventProcessor(processingGroup,
                                                  Configuration::eventStore,
                                                  config -> trackingProcessorConfig);
    });
}

This should ensure that every processing group will be initialized.

The tough part here is how to get the ‘processingGroups’ list of course.
What I typically end up doing for this, is specifying every processing group explicitly through the ‘@ProcessingGroup’ annotation.

Additionally, I have all these processing group names situated in an enum/constants list, which I use as the common reference throughout the application.

I hope this get’s you going with Axon 4 Bjorn!

If anything is unclear, feel free to reach out again.

Cheers,

Steven

Hi Bjorn,

I’ve just gone through the effort of fixing the bug and releasing Axon-Mongo 4.0.1.

It has been pushed to Maven central, although this typically takes some time before it’s accessible.
I’d definitely expect it to be in place tomorrow though.

Cheers,
Steven

Hi Steven,

Thank you for that and sorry for the late reply. Will upgrade to 4.0.1 and try again.

Last question from me Steven.

Regarding processing groups: As I can see, they are created automatically and persisted in my token store based on where Axon finds event listeners in my package structure. How do they relate to me having to create my own processing groups programmatically… or are they two different things altogether.

Please enlighten me :slight_smile:

Hi Bjorn,

Let me enlighten you! :smiley:

If you Event Handling Components, so classes with @EventHandler annotated functions in them, they will be assigned to a processing group.
As this is a requirement, but we didn’t feel that users should be required to use the @ProcessingGroup annotation, we decided to set up a default.

The default processing group of any Event Handling Component (so when you haven’t specified @ProcessingGroup on class level), is the package name of the component.
Thus Event Handling Components which are contained under the same package name, will be part of the same processing group.

When your application matures, it is however recommended to group your Event Handling Components into sensible processing groups.
This is thus where you’d introduce the @ProcessingGroup annotation on your components to group them together.

I hope this gives you the needed background Bjorn!

Cheers,
Steven

Hi Steven!

Thank you. That clarifies it. Now how do I access these default groups without the @ProcessingGroup annotations so I can set the tracker to head?

Cheers :slight_smile:

Hi Bjorn,

the default processing group is the fully qualified package name of the handler’s class. You can change that default by defining your own mappings in the EventProcessingConfigurer class (either have it injected using an @Autowired method, or use the Configurer.eventProcessing() method to get hold of it).
The methods you’ll be looking at are:

  • byDefaultAssignHandlerInstancesTo, which takes a function of Object (your handler instance) to String (the processing group name)
  • byDefaultAssignHandlerTypesTo, taking a Class (type of handler instance) to String (processing group name).
  • byDefaultAssignTo, which only takes the processing group name, and assigns all handlers to that (unless another explicit rule overrides it)

Hope this helps.
Cheers,

Cheers Allard