SOA Patterns: Basic Structural Patterns – Part 2
The Transactional Service Pattern
Oct. 17, 2008 09:15 PM
Another option is to handle messages synchronously. Synchronous operation can prove to be very problematic in terms of performance, especially when the service needs to interact with external services, systems, or resources, as then the whole process has to wait for the other party to react before the service can return its reply. Even more important is the fact that this doesn't really solve the problem. If the service fails anywhere in the process, we don't really know where we are at. The only thing we can know is which message failed and we need the consumer's help for that.
It seems we can solve the problem, if the service will save its state into a persistent storage such as a database. I think that is a step in the right direction; however, we are not safe yet. We can still be in trouble if the service crashes just before persisting its state and the incoming message would still be lost without the service knowing. Another aspect we need to note is that using a persistent storage we can track where in the process a failure occurred - but we can't be sure if messages to other services were sent or not.
To solve these last issues as well as the whole reliability problem we need the Transactional Service.
The main component of the Transactional Service Pattern is the message pump (see Figure 7). The message pump listens on the endpoint or edge for incoming messages. When a message arrives, the message pump can then begin a transaction, read the message, pass it to some other component/classes to handle the message, and when the processing is finished, wrap the transaction (commit/abort). If it is possible to send replies or requests in a transactional manner, they can also participate in the transaction, otherwise you will need compensation logic if the transaction aborts.
The advantage of a using a transactional programming model is that it's all or nothing semantics, which means you don't need to deal with edge cases. Due to the ACID properties of transactions, all the operations and messages are guaranteed to be either completed or not, so you have a high assurance that if a message left the service, the incoming message that triggered that reaction has been fully handled.
The tradeoff you are making when you go with the Transactional Service Pattern is, of course, performance. Transactions are always slower than working without them because of the preparation, the IO needed for durability, lock managements, etc. What I usually do is define target scenarios and test early to make sure the solution is good enough.
One option to implementing the Transactional Service Pattern is to use a transactional message transport for all the messages that flow between the services. Having a transactional message transport makes implementing the pattern very easy -just follow the steps mentioned earlier: begin transaction, read, handle, send, commit. Another option , which I guess is the more common scenario, is to put incoming messages into a transactional resource such as a queue or database table upon receiving it, and then send an acknowledgment as a reply to service consumers. Since the initial message handling in this case is not transactional, you need to be able to cope with a message arriving multiple times if the consumer, for example, didn't get the acknowledge message and sends a request to withdrew $1 million - again.
Figure 8 shows a redesign of the example in Figure 6 in light of the Transactional Service Pattern. To recap, the scenario talks about an e-commerce front end that talks to an ordering service. The ordering service then registers the order, sends the order out to suppliers, and notifies a billing service. When everything is done, it sends a confirmation to the e-commerce front-end application.
Using the Transactional Service Pattern, steps 2.0 to 2.5 in Figure 8, which are the actions taken by the ordering service, are in the same transaction. This means that if you don't handle the place order message because of a crash or other mishap, no message leaves the service. This is a boon since now we don't have to write complicated compensation logic to handle such failures. A subtle issue here is what might happen if the ordering service crashes somewhere between steps 1.0 and 1.2. The scenario is not 100% fail-safe; there's a slight chance that we queue the incoming message for processing but then crash before we acknowledge the message. This may result in accepting the same request more than once. One way to handle these duplicate messages on the service's side is to look at the incoming queue on service startup and send acknowledgments for all the messages in the queue, in this case the consumer might get more than one acknowledgment for messages it sent.
Note that, for the example, using a single transaction would work only as long as the billing process just produces an invoice. It won't work if the billing service activity is to process a credit card and the ordering needs the confirmation to continue. When a single transaction won't work, the process needs to be broken into smaller transactions and the whole process becomes what's known as a long-running operation. Another reason besides long-running processes to break the flow into a few smaller transactions is if the service is distributed.
• • •
This article is based on the book SOA Patterns (http://www.manning.com/rotem) scheduled to print February 2009. This article is courtesy of Manning Publications (http://www.manning.com). The ebook is available and sold exclusively through Manning Publications.
Reader Feedback: Page 1 of 1
SOA World Latest Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
SYS-CON Featured Whitepapers
Most Read This Week