When drawing up a microservices architecture, you need to consider some design trade-offs. These trade-offs are best expressed by the CAP theorem, which states that it’s impossible for a distributed data store to simultaneously guarantee consistency, availability, and partition tolerance. One of the key decisions you will often have to make is whether you need the strong consistency of ACID transactions (which feature atomicity, consistency, isolation, and durability) or if eventual consistency will suffice.
ACID transactions take away the hassle of ensuring data consistency across all participants of the distributed transaction, but they have a significant impact on scalability, performance, and availability of systems at scale, and they add considerable complexity to run-time operations. Eventual consistency, on the other hand, allows data updates to be a bit more…relaxed. It guarantees that in the absence of new updates, all copies of data converge to a common state eventually. It trades off strong consistency for better performance, availability, and partition tolerance.
Neither approach is perfect for every scenario, and the debate of using strong consistency vs eventual consistency is beyond the scope of this blog post. However, if you are designing a system and have decided you need to use transactions, I’d like to introduce you to transactions in the context of Solace PubSub+ Event Broker, specifically the types of transactions it supports, how it supports them, and how they differ.
Transactions enable enterprise applications to group a set of guaranteed messages to be published and/or consumed as an atomic unit of work. For instance, an application might need to consume a message from a queue endpoint, update some database tables, and publish the message on another queue endpoint, in a single operation. If any of these operations fails or a system failure occurs, the entire operation should roll back to its original state. That is, the published messages are deleted, and any messages to be consumed remain on the endpoints they were spooled to.
Combining operations in a transactional unit ensures the integrity of messaging operations that depend on one another and also ensures the validity and consistency of data.
Reservation systems (airline, train tickets, theatre tickets), travel booking, sales order management are all good examples of transactional processing. Consider an electronic fund transfer in an online banking system where one account is debited and another credited with an equal amount. If the debit and credit operations cannot be executed as a transactional unit, neither should be executed as it leads to an inconsistent state and the bank’s books will not balance at the end of the day. The same applies to a travel booking process for a travel agency that books flights and the hotel upon receiving a customer’s travel request. If the customer cancels the request, both the flight and hotel booking operations needs to be cancelled, or else the systems will be in an inconsistent state with invalid data.
Solace PubSub+ Event Broker can support:
Let’s look at what transacted sessions are, and how Solace PubSub+ can support transactions in transacted sessions.
PubSub+ supports transactions in local transacted sessions. A session can be configured as transacted, and the Solace Java, Java RTO, C, .NET, and JMS messaging APIs provide methods for initiating, committing, or rolling back local transactions.
Transacted sessions enable client applications to group multiple message send and/or receive operations together in single atomic transactions. They only involve a single resource—the event broker and hence also known as local transactions.
The scope of a local transaction is always a single session. That is, one or more producer or consumer operations performed in the context of a single session can be grouped into a single local transaction.
To publish and/or consume messages in a transaction, a client application must create a transacted session within a session. The transaction is automatically initiated once the transacted session is created. Then in that transacted session the client application can establish a producer flow to publish messages in a transaction, and/or establish a consumer flow to receive messages in a transaction.
In Release 9.7 and earlier, a client can publish and/or consume up to a combined maximum of 256 guaranteed messages in a single transaction. As of Release 9.8, a controlled availability feature will allow this limit to be configurable via a client-profile setting for applications that require larger transactions. Using larger transactions can have broker-wide performance implications, so please review your use case with Solace before changing the configuration to allow for larger transactions.
Messages that are published and received in a transaction are staged on the message broker.
To complete all of the message send and receive operations in a transaction, the transaction must be committed. When a transaction is committed, for publish operations, the staged messages are sent to their destinations and for consume operations, the received messages are acknowledged by the consumers and then removed from the endpoints they were spooled to.
If a particular send or receive operation within the transaction fails, an exception is raised. The client application can handle the exception by ignoring it, retrying the operation or rolling back the entire transaction so that all of its publish and receive operations are cancelled. When a rollback is performed, for publish operations, the published messages are deleted and for consume operations, the messages remain on the endpoints on which they were spooled. Once a transaction is completed, another transaction automatically begins.
The main steps for performing local transactions are:
When you break a monolithic application into microservices, each service may have its own database or queueing system that it talks to and the transaction spans across multiple databases or messaging services. This now becomes a distributed transaction, i.e., transactions in transaction branches.
Solace PubSub+ Event Broker also supports transactions in transaction branches within an XA session that can be used in global distributed transactions. XA transaction branches are similar to transactions contained within a local transacted session in that they encapsulate multiple message send and/or receive operations as single atomic units, but transaction branches are to be used in a larger distributed transaction. Hence referred to as distributed transactions. XA transaction branches are only supported by the Solace implementation of the JMS API.
Each of these transaction branches can be included in distributed XA transactions that may span multiple resources, such as database systems and messaging services. An external transaction manager is required to manage the various transaction branches in a distributed transaction in a coordinated way. The transaction manager tracks and manages operations performed by multiple resource managers using a two-phase commit (2PC) protocol defined in the Java Transaction API (JTA), XA Resource API Specification. The interaction between resource managers and the transaction manager are described in the JTA specification.
The Solace JMS implementation Version 7.1 and greater supports the Java Transaction API (JTA) and the XAResource interface. The XAResource interface provides a Java mapping of the XA interface that follows the X/Open CAE Specification. The following figure depicts the conceptual view of a distributed transaction:
By supporting the XAResource interface, a client can use the Solace JMS implementation to create an XA Session in which it can create transaction branches to publish and/or receive a series of Guaranteed messages and participate in distributed transactions. The XAResource relies on Xids that uniquely identify each XA transaction and the global transaction they are associated with.
The transaction manager uses an XAResource object obtained for each XA Session that will participate in a distributed transaction. Using these XAResource objects, the Transaction Manager can in a coordinated fashion, instruct and manage each XAResource to perform specific actions for their respective transaction branches according to a two-phase commit process used for distributed transactions.
Solace does not offer a Transaction Manager; however, to allow you to integrate Solace PubSub+ with Java Platform Enterprise Edition (Java EE) application server environments, Solace provides a Java Connector Architecture (JCA) v1.5 resource adapter for Red Hat JBoss EAP 6.2, WebSphere V7 and V8, as well as WebLogic 11gR1 PS5 and newer. The Solace JCA resource adapter is provided as a standalone resource adapter archive (RAR) file that includes embedded versions of the Solace JMS API libraries.
XA transactions are usually used within Java EE application servers that contain a transaction manager to manage the lifecycle of XA transactions. However, if you want to understand some of the underlying interfaces and constructs used by XA transactions, I have listed below the basic steps for performing transactions in XA branches. These are the steps for a client to perform an XA transaction outside an application server using the Solace JMS implementation.
XAResourceto be used by the XA session and the Xid for the transaction to start a transaction within an XA session.
A new transaction cannot be started until the current transaction is freed through a successful commit or is rolled back.
XATransactions.javaexample in the samples directory that shows how you can make use of XA transactions in your application. This sample code is manually managing the XA transaction by calling the relevant XAResource methods.
I hope this post has helped you understand the types of transactions supported by Solace PubSub+ Event Broker and how you can use each of them. You may refer our API developer guide for more information on Using Local Transactions and Using XA Transactions. If you have any questions, post them to the Solace Developer Community.