Technology

Atomikos — multi db transaction system

Pawel Szczerbicki, Jul 12, 2019

Eventual consistency, resiliency, microservices, CQRS, twelve factor app, or reactivity are not buzzwords any more. Most developers claim that the Reactive Manifesto12 factor app is the best and the only way to build responsive web apps. Transaction exists mostly inside a service or db, and term spread transaction sounds suspicious. I totally agree with that, but when you deal with an old fashioned monolith system with multiple databases you are very likely to struggle with this hell.

In this case Atomikos sounds like the best solution. According to webpage, Atomikos is a cloud-native transaction management system for Java and REST. But what does it mean? Atomikos is a library that supports multi-database transactions, including messages and REST. What’s more, no transaction server is needed and, according to their webpage, it’s the only library that supports that.

Use case

I would not even consider Atomikos, but one day I was working with a medium-size distributed monolith that provides a SaaS solution for enterprise clients. One of the key concerns was separating data between clients. While for most of them privacy is a top priority, we decided to go for physical separation rather than logical. Using Spring, it’s extremely easy to route between data sources but what if you have to store every new client who comes to your system, and then bootstrap a separate database? As a team, we decided to go for an admin system that prepared phantom environments in advance and then assigned it after successful registration. This flow requires inserting multiple entries in both databases: client and admin, and sending some events in one atomic transaction. Why? Don’t ask — it’s the result of many bad decisions. But still, for this case Atomikos sounds like the perfect solution — and it was.

Solution

Spring deals with a single datasource out of the box. For multiple DS, additional configuration is required especially when you need to spread transactions.

First of all, you have to switch from javax.sql.Datasource to com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBeanwhich of course implements javax.sql.Datasourceand has a connection pool built in. To create Datasource, simply setup new bean

AtomikosNonXADataSourceBean ds = new AtomikosNonXADataSourceBean();
ds.setDriverClassName(
DRIVER);
ds.setUrl(endpoint);
ds.setUser(user);
ds.setPassword(password);
ds.setUniqueResourceName(
randomAlphabetic(10));
ds.setTestQuery(
SQL);
ds.setMaxPoolSize(
MAX_CONNECTIONS_PER_DATABASE);

Then override default Spring transaction manager with one provided by Atomikos. Use UserTransactionManager which according to documentation is zero-setup implementation of TransactionManager

@Bean(destroyMethod = "close", initMethod = "init") 
public TransactionManager atomikosTransactionManager() {
    UserTransactionManager userTransactionManager = new 
UserTransactionManager();
    userTransactionManager.setForceShutdown(false);
    userTransactionManager.setTransactionTimeout(
 TRANSACTION_TIMEOUT); 
    return userTransactionManager; }

Create UserTransaction object which allows application to explicitly manage transactions. This class should be used only when the user wants to create their own transaction manager, and we want to do so using Atomikos

@Bean
public UserTransaction userTransaction() throws SystemException {
     UserTransactionImp userTransaction = new UserTransactionImp();
     userTransaction.setTransactionTimeout( 
TRANSACTION_TIMEOUT); 
     return userTransaction; 
}

And then finally override PlatformTransactionManager which according to Spring documentation is the central interface in Spring’s transaction infrastructure. Atomikos advises to use JtaTransactionManager which has to be fed with UserTransaction and TransactionManager we created before

@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(UserTransaction
userTransaction, TransactionManager atomikosTransactionManager)
throws Throwable {
     JtaTransactionManager jtaTransactionManager = new
JtaTransactionManager(userTransaction, atomikosTransactionManager);
     jtaTransactionManager.setDefaultTimeout(
TRANSACTION_TIMEOUT);
     return jtaTransactionManager; 
}

Now just enable transactions using @EnableTransactionManagement and you are done !

Summary

Even if we live in an era of event-driven and eventual consistency microservices architecture, a multi-system transaction management system is still crucial. Sexy words like reactivity, queues etc. are not applicable for all of our designs. Architects go too far with their approach and try to solve all the problems with microservices which often leads to a distributed monolith ridden with design pitfall and bugs. For some cases, especially when the software is young, a modular monolith is exactly what you need. And sometimes it should stay at this level. Then you will probably need multi db’s transactional system, and Atomikos will work for you well. For a distributed monolith, it worked well in my case. I do not encourage you to work or produce such code but when you somehow fall into this trap consider Atomikos as a transaction management system.