Service State Management





Service State Management

The sole propose 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 the advantage of a WCF resource manager 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. Consequently, the system would not be in a consistent state after the transaction. The problem is compounded by the fact that the transaction the service participates in may span multiple services, machines, and sites. Even if the service instance encounters no errors and votes to commit transaction, the transaction may eventually be aborted by other parties across the service boundary. 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 made to its state?

The solution for the service 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 not be related 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 auto-recovery. Any changes made to the instance state would 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, then 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 indeed purge all its in-memory state this way, by default once the transaction completes, WCF destroys the service instance, ensuring no leftovers in memory that might jeopardize consistency.

Transaction Boundary

There are two remaining problems with writing transactional state-aware services. The first is how would the service know when transactions start and end so that it could 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 would call the service, letting it know to save its state? The second problem is isolationdifferent clients might call the service concurrently on different transactions. How would the service isolate the change made to its state by one transaction from the other? 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 once 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, the service should read its state, and at the end of each method, the service should save its state to the resource manager. By doing so, when the transaction ends between method calls, the service is assured its state will persist or roll back with it. Because the service equates method boundaries with transaction boundaries, the service instance must therefore also vote on the transaction's outcome at the end of every method. From the service perspective, the transaction completes once the method returns. This is really why the TRansactionAutoComplete property is called 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.

State Identifier

Because there could be many instances of the same service type accessing the same resource manager, every operation must contain some parameters that allow the service instance to find its state in the resource manager and bind against it. The best approach is to have each operation contain some key as a parameter identifying the state. I call that parameter the state identifier. The client must provide the state identifier. Typical state identifiers are account numbers, order numbers, and so on. For example, the client creates a new transactional order-processing object, and on every method call, the client must provide the order number as a parameter, in addition to other parameters.

Figure shows a template for implementing a transactional per-call service.

Implementing a transactional service

[DataContract]
class Param
{...}

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod( );
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract,IDisposable
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(Param stateIdentifier)
   {
      GetState(stateIdentifier);
      DoWork( );
      SaveState(stateIdentifier);
   }
   void GetState(Param stateIdentifier)
   {...}
   void DoWork( )
   {...}
   void SaveState(Param stateIdentifier)
   {...}
   public void Dispose( )
   {...}
}

The MyMethod( ) signature contains a state identifier parameter of the type Param (a pseudotype invented for this example) used to get the state from a resource manager with the GetState( ) helper method. The service instance then performs its work using the DoWork( ) helper method. The service instance then saves its state back to the resource manager using the SaveState( ) method, specifying its identifier.

With a per-call service, the resource managers used to store the service state can also be volatile resource managers accessed as static member variables.


Note that not all of the service instance state can be saved by value to the resource manager. If the state contains references to other objects, GetState( ) should create those objects, and SaveState( ) (or Dispose( )) should dispose of them.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows