Since version 4.7.0 of Axon Framework, it’s possible to leverage the transactions API provided by MongoDB. The main advantage this will provide is to keep projections consistent in case of errors. Transactions were added to MongoDB at a later stage. It’s easy to make mistakes by only using the transaction API for part of the writes. This blog post will focus on using the Mongo extension in combination with Spring Boot.
When the transactions API is properly used, it can prevent certain problems. For example, an event could trigger an update to a MongoDB document using an event processor. By the time the token is updated in the token store, the database might have become unavailable. This means if the processor is restarted, the same event might be processed again. By using transactions, either both updates succeed or fail. However, there are some caveats, hence this blog.
Make use of transactions in the framework components
We have to change two things to use transactions properly. First, we need to replace the MongoTemplate with one that takes into account transactions for the calls to the collections. Secondly, we need to have transaction managers on the components so they can properly manage the transactions.
For each of the framework components, enabling transactions works in a similar way. These components are the token store, saga store, dead-letter queue, and event store*. Depending on the application, you might not use MongoDB for all these components. The specific classes needed for Spring are available from a new module, the axon-mongo-spring module.
*Please note that from Axon Framework 4 we no longer recommend using MongoDB as an event store. There are several problems, most of them because MongoDB lacks a sequence generator. The lack of this means instead of a global sequence, we need to use a timestamp field instead. The timestamp field is populated by the applications running Axon Framework, if they are not properly synced events will be processed out of order. This might in turn cause exceptions or data corruption. Relying on timestamps also makes the tracking tokens large and complicated. In some cases they might even become too big. After many events, the index will become too large to fit in memory. Above all, the
performance will be a lot worse compared to Axon Server. In the near future, we will publish a blog that goes into more detail about this.
Setting the mongo template
All the components need a mongo template. Until the 4.7.0 release, the only implementation provided by the extension was the DefaultMongoTemplate. We introduced a SpringMongoTemplate to support MongoDB transactions with Spring. This was necessary to leverage the Spring MongoDatabaseUtils class to get the collections, which makes sure the transactions API is used.
The SpringMongoTemplate can be created using the MongoDatabaseFactory. This will happen with Spring auto-configuration like:
If you are using Spring, you can use the SpringMongoTransactionManager. If you don’t use Spring, you need to implement the TransactionManager yourself.
Since 4.7.0, we also added auto-configuration, where we set the transaction manager for you. Note this is not used when you define the bean yourself. Hence, removing your bean might be the easiest way to enable transactions.
With the exception of the dead-letter queue, all the components will be auto-configured when you use the starter. There are some exceptions to this. If you also use a relational database, it's likely you get either the JPA or JDBC implementations instead. To get the event store, you need to set the axon.mongo.event-store.enabled property to true.
Make use of transactions in application code
When using the Mongo extension, it's likely an event processor’s event handlers updating a MongoDB collection. With the information above, the token store used by the processor can properly use the transactions API. To get all the benefits, it’s also important the code called by the event processor will also use the transactions API.
The easiest way to make sure the transactions API is used is by using a MongoRepository. An example can be found here. Another way is to use the MongoDatabaseUtils like is done in the SpringMongoTemplate mentioned above. Please note that not doing so might mean the transactions initiated from Axon Framework might not contain all the operations. As the framework will already take care of starting a transaction, you don’t need to add a transactional annotation in the application code. To enable this, you need to configure the aforementioned MongoTransactionManager with your event processor.
See the java documentation for more details on the Spring Mongo Transaction Manager. I also created a small demo app to prepare for adding transaction support to the Mongo Extension. The non-main branches use transactions, while the main branch does not, resulting in data missing in the projection. A Baeldung article explaining how to use MongoDB transactions in Spring might be helpful too. Lastly, for an example using the 4.7 auto-configuration, Spring Boot 3 and records in this example. Although it's never explicit there, because of the auto configuration and the transactions being started from the event processor, it does use them. For other questions related to Axon Framework, please reach out on Discuss.
Gerard Klijs is a developer at AxonIQ. In previous projects, he has worked a lot with Apache Kafka. He works both on the Axon Framework itself, and some of the extensions, like the Kafka and Mongo extension. I like to dabble in less proven technology and languages like Clojure, Rust, WebAssembly, GraphQL and gRPC.