Section 7.2. Transactions


7.2. Transactions

The best (or perhaps only) way of maintaining system consistency and dealing properly with the error recovery challenge is to use transactions. A transaction is a set of potentially complex operations in which the failure of any single operation causes the entire set to fail, as one atomic operation. As illustrated in Figure 7-1, while the transaction is in progress, the system is allowed to be in a temporary inconsistent state, but once the transaction is complete, you are guaranteed to be in a consistent state, either a new consistent state (B) or the original consistent state the system was at before the transaction started (A).

Figure 7-1. A Transaction transfers the system between consistent states


If the transaction executed successfully, and managed to transfer the system from the consistent state A to the consistent state B, it is called a committed transaction. If the transaction encountered any error during its execution and rolled back all the intermediate steps that have already succeeded, it is called an aborted transaction. If the transaction failed to either commit or abort, it is called an in-doubt transaction. In-doubt transactions usually require administrator or user assistance to resolve and are beyond the scope of this book.

7.2.1. Transactional Resources

Transactional programming requires working with a resource such as a database or a message queue that is capable of participating in a transaction, and being able to commit or roll back the changes made during the transaction. Such resources have been around in one form or another for decades. Traditionally, you had to inform a resource that you would like to perform transactional work against it. This is called enlisting the resource in the transaction. Some resources may support auto-enlistment, that is, they can detect they are being accessed by a transaction and automatically enlist in it. Once enlisted, you then perform work against the resource, and if no error occurs, the resource is asked to commit the changes made to its state. If you encounter any error, the resource is ask to roll back the changes. During a transaction it is vital that you do not access any nontransactional resource (such as the filesystem on Windows XP), because changes made to those resources will not roll back if the transaction is aborted.

7.2.2. Transaction Properties

When you make use of transactions in your service-oriented applications, you must abide by four core properties, known as ACID: atomic, consistent, isolated, and durable. When you design transactional services, you must adhere to the ACID requirementsthey are not optional. As you will see throughout this chapter, WCF enforces them rigorously.

7.2.2.1. The Atomic property

Atomic[*] means that when a transaction completes, all the changes it made to the resource state must be made as if they were all one atomic, indivisible operation. The changes made to the resource are made as if everything else in the universe stops, the changes are made, and then everything resumes. It must not be possible for a party outside the transaction to observe the resources involved with only some of the changes but not all of them. A transaction should not leave things to do in the background once it is done, as those operations violate atomicity. Every operation resulting from the transaction must be included in the transaction itself.

[*] The word "atom" comes from the Greek word "atomos," meaning indivisible. The ancient Greeks thought that if you start dividing matter, and continue dividing it, eventually you get to indivisible pieces, which they called "atomos," The ancient Greeks were of course wrong, as atoms can be divided to subatomic particles such as electrons, protons, and neutrons. Transactions, however, are truly atomic.

Because transactions are atomic, a client application becomes a lot easier to develop. The client does not have to manage partial failure of its requests, or have complex recovery logic. The client knows that the transaction either succeeded or failed as a whole. In the case of failure, the client can choose to issue a new request (start a new transaction), or something else, such as alerting the user. The important thing is that the client does not have to recover the system.

7.2.2.2. The Consistent property

Consistent means the transaction must leave the system in a consistent state. Note that consistency is different from atomicity. Even if all the changes are committed as one atomic operation, the transaction is required to guarantee that all those changes are consistentthat they "make sense." Usually it is up to the developer to ensure that the semantics of the operations are consistent. All the transaction is required to do is to transfer the system from one consistent state to another.

7.2.2.3. The Isolated property

Isolated means no other entity (transactional or not) is able to see the intermediate state of the resources during the transaction, because it may be inconsistent. In fact, even if it is consistent, the transaction could still abort, and the changes could be rolled back. Isolation is crucial to overall system consistency. Suppose transaction A allows transaction B access to its intermediate state. Transaction A aborts, and transaction B decides to commit. The problem is that transaction B based its execution on system state that was rolled back, and therefore transaction B is left unknowingly inconsistent. Managing isolation is not trivial. The resources participating in a transaction must lock the data accessed by the transaction from all other parties, and must unlock access to that data when the transaction commits or aborts.

7.2.2.4. The Durable property

Traditionally, transactional support by a resource implies not just a transaction-aware resource, but also a durable one. This is because at any moment, the application could crash, and the memory it was using could be erased. If the changes to the system's state were in-memory changes, they would be lost, and the system would be in an inconsistent state. However, durability is really a range of options. How resilient to such catastrophes the resource should be is an open question that depends on the nature and sensitivity of the data, your budget, available time and available system administration staff, and so on. If durability is a range that actually means various degrees of persistence, then you could also consider the far end of the spectrumvolatile, in-memory resources. The advantage of volatile resources is that they offer better performance than durable resources, and, more importantly, they allow you to approximate much better conventional programming models, while using transaction support for error recovery. You will see later on in this chapter how and when your services can benefit from volatile resource managers (VRMs).

7.2.3. Transaction Management

WCF services can work directly against a transactional resource and manage the transaction explicitly using programming models such as that offered by ADO.NET. Using this model, you are responsible for explicitly starting and managing the transaction, as shown in Example 7-1.

Example 7-1. Explicit transaction management

 [ServiceContract] interface IMyContract {    [OperationContract]    void MyMethod( ); } class MyService : IMyContract {    public void MyMethod( )    {       //Avoid this programming model:       string connectionString = "...";       IDbConnection connection = new SqlConnection(connectionString);       connection.Open( );       IDbCommand command = new SqlCommand( );       command.Connection = connection;       IDbTransaction transaction = connection.BeginTransaction( );//Enlisting       command.Transaction = transaction;       try       {          /* Interact with database here, then commit the transaction */          transaction.Commit( );       }       catch       {          transaction.Rollback( ); //Abort transaction       }       finally       {          connection.Close( );          command.Dispose( );          transaction.Dispose( );       }    } } 

You obtain an object representing the underlying database transaction by calling BeginTransaction( ) on the connection object. BeginTransaction( ) returns an implementation of the interface IDbTransaction used to manage the transaction. When the database is enlisted, it does not really execute any of the requests made. Instead it merely logs the requests against the transaction. If all updates or other changes made to the database are consistent and no error took place, you simply call Commit( ) on the transaction object. This will instruct the database to commit the changes as one atomic operation. If any exception occurred, it skips over the call to Commit( ), and the catch statement aborts the transaction by calling Rollback( ). Aborting the transaction instructs the database to discard all the changes logged so far.

7.2.3.1. The transaction management challenge

While the explicit programming model is straightforward, requiring nothing of the service performing the transaction, it is most suitable for a client calling a single service interacting with a single database (or a single transactional resource), where the service starts and manages the transaction, as shown in Figure 7-2.

Figure 7-2. Single service/single resource transaction


This is due to the transaction coordination problem. Consider, for example, a service-oriented application where the client interacts with multiple services that in turn interact with each other and with multiple resources, as shown in Figure 7-3.

Figure 7-3. Distributed transactional service-oriented application


The question now is, which one of the participating services is responsible for beginning the transaction and enlisting each resource? If all of them will do that, you will end up with multiple transactions. Putting the enlistment logic in the service code will create a great deal of coupling between the services and the resources. Furthermore, which one of the services is responsible for committing or rolling back the transactions? How would one service know what the rest of the services feel about the transaction? How would the service managing the transaction inform other services about the transaction's outcome? Trying to pass the transaction object or some identifier as an operation parameter is not service-oriented, because the clients and the services could all be using any implementation platform and technology. The services could of course be deployed in different processes or even across different machines or sites, and issues such as network failures or machine crashes introduce additional complexity for managing the transaction, because one service can crash, while others can continue processing the transaction. One possible solution is to couple the clients and the services by adding logic for the transaction coordination, but such an approach is very fragile and would not withstand even minor changes to the business flow or the number of participating services. In addition, the services in Figure 7-3 could have been developed by different vendors, which would preclude any such coordination. Even if you find a way of solving the coordination problem at the service level, when multiple resources are involved, you have multiple independent points of failure, because each of the resources could fail independently of the services.

7.2.3.2. Distributed transactions

The predicament just described is called a distributed transaction. A distributed transaction contains two or more independent services (often in different execution contexts), or even just a single service with two or more transactional resources. It is impractical to try to explicitly manage the potential error cases of a distributed transaction. For a distributed transaction, you need to rely on the two-phase commit protocol, and a dedicated transaction manager. A transaction manager is a third party that will manage the transaction for you, because the last thing you want is to place the transaction management logic in your service code.

7.2.3.3. The two-phase commit protocol

To overcome the complexity of a distributed transaction, the transaction manager uses a transaction management protocol called the two-phase commit protocol to decide on the outcome of the transaction as well as to commit or roll back the changes to the system state. The two-phase commit protocol is what enforces atomicity and consistency in a distributed system. The protocol enables WCF to support transactions that involve multiple clients, services, and resources. You will see later in this chapter just how transactions start and how they flow across service boundaries. For now, the important thing to note is that while a transaction is in progress, the transaction manager stays largely out of the way. New services may join the transaction, and every resource accessed is enlisted with that transaction. The services execute business logic, and the resources record the changes made under the scope of the transaction. During the transaction, all the services (and the clients participating in the transaction) must vote if they want to commit the changes they performed or if they want to abort the transaction for whatever reason.

When the transaction ends (and you will see when transactions end later on), the transaction manager checks the combined vote of the participating services. If any service or client voted to abort, the transaction is doomed. All the participating resources are instructed to discard the changes made during the transaction. If all the services in the transaction voted to commit, the two-phase commit protocol starts. In the first phase, the transaction manager asks all the resources that took part in the transaction if they have any reservations in committing the changes recorded during the transaction. That is, if they were asked to commit, would they? Note that the transaction manager is not instructing the resources to commit the changes. It is merely asking for their vote on the matter. At the end of the first phase, the transaction manager has the combined vote of the resources. The second phase of the protocol is acting upon that combined vote. If all the resources voted to commit the transaction in the first phase, then the transaction manager instructs all of them to commit the changes. If even one of the resources said in phase one that it could not commit the changes, then in phase two the transaction manager instructs all the resources to roll back the changes made, thus aborting the transaction and restoring the system to its pre-transaction state.

It is important to emphasize that a resource vote that it would commit if asked to is special: it is an unbreakable promise. If a resource votes to commit a transaction, it means that it cannot fail if subsequently, in the second phase, it is instructed to commit. The resource should verify before voting to commit that all the changes are consistent and legitimate. A resource can never go back on its vote. This is the basis for enabling distributed transactions. The various resource vendors have gone to great lengths to implement this behavior exactly.

7.2.4. WCF Resource Managers

A WCF Resource Manager (RM) is any resource that supports both automatic enlistment and the two-phase commit protocol managed by one of WCF's transaction managers. The resource must detect that it is being accessed by a transaction and automatically enlist in it exactly once. The RM can be either a durable resource or a volatile resource, such as a transactional integer, string, or collection. While the RM must support the two-phase commit protocol, the RM can optionally implement an optimized protocol used when it is the only RM in the transaction. The optimized protocol is called the single-phase commit protocol, when the RM is the one informing the transaction manager in one step about the success or failure of an attempt to commit.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net