An SOA odyssey

Tuesday, October 04, 2005

Transaction Handling in EDAF

In my previous post I mentioned that I'd share a bit about how we modified EDAF v1.1 for our services platform here at Compassion. There are several ways we've done this that include:

  • Modifying the service interface adapters to use our messaging standards

  • Modifying the exception reporting framework to log exceptions to our central logging store

  • Creating a base class for business actions to include common code

  • Modifying the TransactionHandler to support a model where business actions do not throw exceptions and transactions can span the invocations of business actions


  • It is this final one that I'll explore today, the others will have to wait for a future post.

    Background
    There have been two major efforts at creating web service specifications in the industry as documented by Newcomer and Lowow in chapter 10:

  • WS-Transactions. This includes a family of specifications developed by Microsoft, BEA, and IBM that includes WS-AtomicTransaction, WS-BusinessActivity, and WS-Coordination.

  • WS-Composite Application Framework. This is a family of specifications developed by Arjuna, IONA, Oracle, Fujitsu, and Sun and includes WS-Context, WS-CoordinationFramework, and WS-TransactionManagement.


  • These specifications are similar and have considerable overlap. For example, in both cases "participants in a transaction register with a coordinator and specify the protocol type so that the coordinator can drive the appropriate protocol for use." Both can support various transaction processing requirements including two-phase commit, compensation, and business process transactions. The hope is that these will merge into a single specification in the future.

    However, in our evaluation of these specifications it was apparent that there is no infrastructure in place to support either. In other words, there would need to be a set of code in place that interpreted the SOAP header information specified in these various specifications and performed the registration as well as controlled the transaction. Support for WS-Transaction will be built into Indigo within Win/FX, however.

    Approach
    Because it is not practical for Compassion to implement the kind of infrastructure required, the approach we’ve taken is to modify and use the Transaction Handler present in EDAF to implement Component Services transactions across Business Actions.

    The Transaction Handler within EDAF takes advantage of EnterpriseServices (COM+) in the .NET Framework to implement transaction support. The handler itself uses an object named TransactionRequired, which is registered with COM+ by the TransactionHandlerInstaller assembly. The TransactionRequired object is configured to require transactions and set the isolation level to Serializable. Since this is a stacked around handler the target will always be included in the transaction. In addition, any handlers that are wrapped by this handler will also be included in the transaction. If execution is successful, the transaction is committed. If an exception is thrown, then the transaction is stopped. This handler can then be placed within the Service Implementation pipeline

    In our architecture services report both business and system exceptions within an Exceptions element that is returned within the SOAP body as in the example below.

    <Exceptions
    xmlns="http://schemas.compassion.com/common/
    exceptions/2005-03-01">
    <Exception Type="System" xmlns="">
    <DateTimestamp>0001-01-01T00:00:00.0000000-07:00
    </DateTimestamp>
    <Code>Compassion.Common.Data</Code>
    <Message>Could not create data reader from
    statement GetCon</Message>
    <StackTrace> at Compassion.Common.Data.
    DataFactory._throwException…
    </StackTrace>
    </Exception>
    <Exception Type="Business" xmlns="">
    <DateTimestamp>2005-05-18T09:09:54.9242939-06:00
    </DateTimestamp>
    <Source>Execute</Source>
    <Code>INVALID_ID</Code>
    <Message>Constituent with ID [353594]
    was not found.
    </Message>
    <StackTrace> at Compassion.Services.
    Actions.Constituent…
    </StackTrace>
    </Exception>
    </Exceptions>

    As a result, the Business Actions we develop will not throw .NET exceptions to their callers and in turn when using the TransactionHandler transactions will not typically be rolled back since the TransactionRequired class is marked with an AutoComplete attribute and so will only rollback when an exception is thrown from within one of the business actions or handlers it is encapsulating.

    To address this situation we’ve modified the TransactionRequired class within EDAF to remove the AutoComplete attribute and instead rollback or commit transactions using the ContextUtil.SetAbort and ContextUtil.SetComplete methods respectively. The class will do this when it finds that the TransactionValid key (a key the handler itself initializes) in the EDAF Context is set to false. This technique is analogous to the way in which COM+ manages transactions internally. Each component (in this case business action) that participates in the transaction "votes" on its outcome by either setting the flag to false signifying that the business action is not happy and the transaction should be rolled back or doing nothing which signifies that the business action is happy and the transaction should continue.

    Transaction context will flow across invocations of business actions since the TransactionRequired will have its transactional support set to Required as shown in the following dialog.



    In this way, the first business action that is invoked will act as the root of the distributed transaction. Each subsequent business action will be wrapped in another instance of the Transaction Handler and will enlist in the already started transaction. If one of the nested business actions sets its TransactionValid flag to false, its Transaction Handler will call ContextUtil.SetAbort thereby aborting the entire distributed transaction and rolling back the work done for all business actions in the call stack. This is illustrated in the following diagram where the step numbers denote the timing.




    Implications
    The implications of this technique are threefold:

    First, developers of business actions are required to set the TransactionValid flag to false when they wish their database changes to be rolled back. The general pattern for doing so is shown below.

    public void Execute(IContext objContext)
    {
    try
    {
    // Perform database writes or call
    // other business actions
    }
    catch (Exception ex)
    {
    // Handle System Exceptions and create Exception
    // elements to add to the response
    }
    finally
    {
    // Inspect response and create business Exception
    // elements to add to the response
    }

    // Vote on the outcome of the transaction
    if (objResp.Exceptions.Length>0)
    if (objContext["TransactionValid"] != null)
    objContext["TransactionValid"] == false;
    }

    Here, the entire activity of the business action is encapsulated in a try/catch block. System and business exceptions are detected either through catching exceptions thrown from the data access layer or by inspecting the contents of the response returned from the data access layer.

    It should be noted that in our implementation we've abstracted the setting of the TransactionValid flag in a base class from which all business action classes inherit.

    Using this approach implies that the business action will be implemented within a Service Implementation pipeline in which the Transaction Handler is configured. The work done in the try block will then be rolled back when the Transaction Handler aborts the transaction. Therefore business actions should not use local transactional control (they should not be starting and committing local transactions).

    Secondly, this architecture implies that in order for the transaction context to flow across business action requests the business action must be invoked using the in-process service interface adapter. In other words, if business action A invokes business action B and the work from the both actions needs to be part of a single distributed transaction, action A must invoke action B using the in-process adapter. This is the case since distributed transaction cannot flow across the other interface adapters in EDAF which include web services, MSMQ, and .NET Remoting.

    For example, a business action could invoke the SelectEmailRequest business action using the in process adapter like so:

    InProcDispatchingAdapter objDA =
    new InProcDispatchingAdapter();
    Request objRequest = new Request();
    objRequest.ServiceActionName = "UpdateAddress";

    objRequest.Payload =
    XmlSerializerHelper.SerializeToXml(objUpdateRequest);

    Context objDAContext = new Context(objRequest);
    objDAContext.Add("ValidateOnly",this.ValidateOnly);

    objDA.Submit(objDAContext);

    //get the response.
    AddressUpdateResponse objCompletedResponse =
    (AddressUpdateResponse)
    XmlSerializerHelper.Deserialize(((XmlDocument)
    objDAContext.Response.Payload).InnerXml,
    typeof(AddressUpdateResponse));

    //append any exceptions.
    this.AppendExceptions(objCompletedResponse.Exceptions);

    Here you’ll notice that a new Context object is created for the request to the UpdateAddress business action. In this case each business action will have its own Context object in which the Transaction Handler will check for and use the TransactionValid flag.

    The third implication of this approach is that service consumers need to be aware that there is no way in our current architecture of flowing transaction context across two or more service invocations. For example, if a service consumer uses a service agent to invoke the UpdateConstituent operation and then uses the same service agent to invoke CreateConstituent, these two invocations cannot participate in the same distributed transaction. This is the case since the service agents will invoke the services using SOAP over HTTP or MSMQ, neither of which offer distributed transactions at present. The thought is that in the future support for transactions across service calls will be supported by the Indigo framework in Win/FX using WS-Transactions, which can then be integrated into the service agent framework.

    0 Comments:

    Post a Comment

    << Home