Service State Management

The sole purpose of transactional programming is to address the recovery challenge by always leaving the system in a consistent state. The state of the system consists of all the resources that were involved in the transaction, plus the in-memory clients and service instances. Besides benefits such as auto-enlistment and participation in the two-phase commit protocol, the basic and obvious advantage of using a resource manager is that any change made to its state during a transaction will automatically roll back if the transaction aborts. This, however, is not true when it comes to the in-memory instance members and static members of the participating services, which means that if the transaction aborts the system will not be left in a consistent state. The problem is compounded by the fact that any transaction a service participates in may span multiple services, machines, and sites. Even if that service instance encounters no errors and votes to commit the transaction, other parties across the service boundary may eventually abort the transaction. If the service were to simply store its state in memory, how would it know about the outcome of the transaction so that it would somehow manually roll back the changes it had made to its state?

The solution to this instance state management problem is to develop the service as a state-aware service and proactively manage its state. As explained in Chapter 4, a state-aware service is not the same as a stateless service. If the service were truly stateless, there would not be any problem with instance state rollback. As long as a transaction is in progress, the service instance is allowed to maintain state in memory. Between transactions, the service should store its state in a resource manager. That state resource manager may be unrelated to any other business-logic-specific resource accessed during the transaction, or it may be one and the same. At the beginning of the transaction, the service should retrieve its state from the resource and, by doing so, enlist the resource in the transaction. At the end of the transaction, the service should save its state back to the resource manager.

The elegant thing about this technique is that it provides for state autorecovery. Any changes made to the instance state will commit or roll back as part of the transaction. If the transaction commits, the next time the service gets its state it will have the new state. If the transaction aborts, the next time it will have its pre-transaction state. Either way, the service will have a consistent state ready to be accessed by a new transaction. To force the service instance to purge all its in-memory state this way, by default, once the transaction completes, WCF destroys the service instance, ensuring there are no leftovers in memory that might jeopardize consistency.

The Transaction Boundary

There are two remaining problems when writing transactional state-aware services. The first is how a service can know when transactions start and end, so that it can get and save its state. The service may be part of a much larger transaction that spans multiple services and machines. At any moment between service calls, the transaction might end. Who will call the service, letting it know to save its state? The second problem has to do with isolation. Different clients might call the service concurrently, on different transactions. How can the service isolate changes made to its state by one transaction from another? The service cannot allow cross-transactional calls, because doing so would jeopardize isolation. If the other transaction were to access its state and operate based on its values, that transaction would be contaminated with foul state if the original transaction aborted and the changes rolled back.

The solution to both problems is for the service to equate method boundaries with transaction boundaries. At the beginning of every method call, the service should read its state, and at the end of each method call the service should save its state to the resource manager. Doing so ensures that if a transaction ends between method calls, the service’s state will either persist or roll back with it. Because the service equates method boundaries with transaction boundaries, the service instance must also vote on the transaction’s outcome at the end of every method call. From the service perspective, the transaction completes once the method returns. This is really why the TransactionAutoComplete property is named that instead of something like TransactionAutoVote: the service states that, as far as it is concerned, the transaction is complete. If the service is also the root of the transaction, completing it will indeed terminate the transaction.

In addition, reading and storing the state in the resource manager in each method call addresses the isolation challenge, because the service simply lets the resource manager isolate access to the state between concurrent transactions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.111.33