Atomikos — multi db transaction system

Paweł Szczerbicki
The Startup
Published in
3 min readJul 17, 2019

Eventual consistency, resiliency, microservices, CQRS, twelve factor app, or reactivity are not buzzwords any more. Most developers claim that the Reactive Manifesto, 12 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. Check out this story to learn more. 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.AtomikosNonXADataSourceBean which of course implements javax.sql.Datasource and 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);

Note that using AtomikosNonXADataSourceBean it is not safe with respect to recovery. Use XA datasource for this purpose.

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

@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.

--

--