The field of transaction processing is by no means a new discipline. Transaction processing infrastructures such as CICS and Tuxedo, OTS/JTS and MS DTC have been around for decades, and much innovative and interesting work continues in the field today. However, underlying the development of transaction technology over the decades has been one pervasive notion: ACID.
ACID is the acronym used to describe the classical form of all-or-nothing transactions, which are Atomic, Consistent, Isolated, and Durable. These properties are of such fundamental importance to transaction processing systems that they warrant further elaboration, even in a Web services book. The ACID characteristics are:
Having established the fundamental properties of ACID transactions, it is worthwhile taking a look at how such abstractions are used within today's enterprise before we move up to running transactions at the Web services level. As we discussed earlier, in the simplest form, a transaction is a logically undoable unit of work. For illustrative purposes, we shall return to the bank account example since it is well known and relatively straightforward to comprehend. It is important to remember that we're dealing with principles in this section, and that industrial-strength transaction processing systems have vastly more sophisticated implementations than the mechanisms we're going to examine. However, the principles remain valid and with that caveat in mind we can examine the simple debit/credit transaction shown in Figure 7-1.
Figure 7-1. A successful debit/credit transaction.
Figure 7-1 shows the classical transaction example where one bank account is debited and another is credited. In this example there are no failures while the work is being undertaken, and the flow of work through the transaction is therefore straightforward.
In a typical transaction we operate on copies of the actual online data and only when we are satisfied that the work has come to a successful conclusion do we allow the copies to replace that original data. While successful transactions are interesting enough, it is far more entertaining to look at what happens when a system failure causes the transaction itself to fail, and show how such system failures can be masked by transactions. In the example shown in Figure 7-2, a system failure causes some difficulty for the executing transaction with the worst possible timing the debtor's account has been provisionally debited, while no monies have yet made their way to the creditor's account.
Figure 7-2. An unsuccessful debit/credit transaction.
Assuming the system that supports the transaction shown in Figure 7-2 can be re-started, the system must then recover any transactions that were in progress prior to the crash. In our example case, this is relatively straightforward since it is a matter of aborting the transaction simply by discarding shadow writes and unlocking any resources (database records in our case) that were associated with the transaction, as shown in Figure 7-3. This has the effect of completely reversing the transaction with no side affects, other than the fact that processing the payment will have been delayed by the amount of time that the system takes to rerun the work.
Figure 7-3. Recovering an unsuccessful debit/credit transaction.
Failure during the termination phase presents a different problem, but one which can be solved nonetheless. Imagine a situation such as that depicted in Figure 7-4, where the transaction has successfully progressed to termination, but where a system failure has caused the transaction to fail before the termination phase has completed.
Figure 7-4. A transaction failing during the termination phase.
In the situation portrayed in Figure 7-4, once the system has started its recovery process, it is a relatively simple matter to read the intention log and to effectively replay the termination phase to completion. Since no records have yet been unlocked, the data remains inaccessible to other transactions, which although it may cause inconvenience is perfectly safe. Using the decisions recorded in the intentions log, with the stored shadow writes recorded during the work phase, the recovery process can simply rerun the procedure, as we can see in Figure 7-5.
Figure 7-5. Recovery from a failed transaction during the termination phase.
Though the implementations are admittedly naïve, the concepts we've examined here are generic enough to provide a basic understanding of what a transaction is and how it provides its dependable unit-of-work abstraction. However, the examples we've considered have been centralized the transactions have executed with a single program and operated on locally available records. This is clearly not the ideal architecture on which to base Web services transactions, but fortunately there is a precedent and known techniques for running ACID transactions in a distributed environment.