For decades developers have relied on global distributed XA transactions with a two phase commit protocol to coordinate state updates across different resource types. Application vendors fueled this drug addiction by building increasingly powerful transaction managers and introducing extensions like last participant support. The XA protocol and the strong consistency provided by transactions does not model real life where eventual consistency abounds.
In a distributed system built on the microservices architectural style deployed on the cloud the implicit guarantees provided by the default rollback of the XA protocol no longer work. In distributed systems the uncertainty of the state has to be explicitly dealt by the application. This inconsistency in a distributed application is dealt with by a Saga or a Process Manager. Note that saga's and process manager's are NOT antithetical to DDD.
Data consistency in a microservices system cannot be implemented by distributed transactions. Use the SAGA pattern to string together a series of local transactions with compensation events. Using Saga's or process managers correctly will not violate your context boundaries. In fact using a higher level state machine will allow you to focus more on the domain concerns rather than bike shedding over the implementation(re-sequencing, de-duplication) of an event driven architecture.
In today’s distributed world, consider global transaction managers an architecture smell. See Kevin Hoffman's article on this topic: Distributed Transactions in a Cloud-Native, Microservice World.
Pat Helland in his updated paper Life Beyond Distributed Transactions states
Another excellent source of insight on this topic is from Josh Long and Kenny Bastani in their excellent book Cloud Native Java. From See JTA and XA Transaction Management in Appendix A Using Spring Boot with Java EE in CloudNativeJava book.In a system that cannot count on distributed transactions, the management of uncertainty must be implemented in the business logic. The uncertainty of the outcome is held in the business semantics rather than in the record lock. This is simply workflow. Nothing magic, just that we can’t use distributed transaction so we need to use workflow.
From https://www.safaribooksonline.com/library/view/Cloud+Native+Java/9781449374631/part03ch02.html#messagingDistributed transactions gate the ability of one service to process transactions at an independent cadence. Distributed transactions imply that state is being maintained across multiple services when they should ideally be in a single microservice. Ideally, services should share state not at the database level but at the API level, in which case this whole discussion is moot: REST APIs don’t support the X/Open protocol, anyway! There are other patterns for state synchronization that promote horizontal scalability and temporal decoupling, centered around messaging. You’ll find more on messaging in our discussion of messaging and integration.
With event carried state transfer or event sourcing We can use the saga pattern and design compensating transactions for every service with which we integrate and any possible failure conditions, but we might be able to get away with something simpler if we use a message broker. Message brokers are conceptually very simple: as messages are delivered to the broker, they’re stored and delivered to connected consumers. If there are no connected consumers, the broker will store the messages and redeliver them upon connection of a consumer.
Message brokers have their own, resource-local notion of a transaction. A producer may deliver a message and then, if necessary, withdraw it, effectively rolling the message back. A consumer may accept delivery of a message, attempt to do something with it, and then acknowledge the delivery—or, if something should go wrong, return the message to the broker, effectively rolling back the delivery. Eventually both sides will agree upon the state. This is different than a distributed transaction in that the message broker introduces the variable of time, or temporal decoupling. In doing so, it simplifies the integration between services. This property makes it easier to reason about state in a distributed system. You can ensure that two otherwise non-transactional resources will eventually agree upon state. In this way, a message broker bridges the two otherwise non transactional resources.
If you wan to do Distributed Transactions In Spring, With And Without XA take a look at this article from David Syer and the code samples that go along with it on how to do non XA distrubuted transactions with Spring
Best Efforts 1PC Pattern
The Best Efforts 1PC pattern is fairly general but can fail in some circumstances that the developer must be aware of. This is a non-XA pattern that involves a synchronized single-phase commit of a number of resources. Because the 2PC is not used, it can never be as safe as an XA transaction, but is often good enough if the participants are aware of the compromises. Many high-volume, high-throughput transaction-processing systems are set up this way to improve performance.
So in summary
- Prefer Resource Local best effort single single resource Transactions over global transactions
- Prefer Spring’s @Transactional variant over EJB’s @javax.ejb.TransactionAttribute or JTA’s @javax.transaction.Transactional
Moving away from distributed transactions requires an adjustment in mindset to make use of new paradigms. Move away from JTA to an architecture and implementation style where you model the domain uncertainty and make the best effort to model and handle it in the business logic instead of delegating this core responsibility to the underlying platform.
- Daniel Frey has created A Demo App to illustrate the use of the JPATransactionManager to handle Database and JMS “rollbacks”. distributed-tx