An SOA odyssey

Friday, October 07, 2005

Messaging Standards

Another aspect of the Service Communication part of our services platform are the messaging standards. These include both the envelope standards used for encapsulating messages as well as the message headers we use.

Envelope Standard
This envelope standard defines the XML schema used to represent messages. Rather than create a new standard the CI-SOA will use the Simple Object Access Protocol (SOAP) 1.1 standard envelope defined at http://www.w3.org/TR/2000/NOTE-soap-20000508/. As an example a request message looks as follows:

<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
soap:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"/>
<soap:Header>
<t:Transaction>5</t:Transaction>
</soap:Header>
<soap:Body>
<m:GetLastTradePrice xmlns:m="Some-URI">
<symbol>DEF</symbol>
</m:GetLastTradePrice>
</soap:Body>
</soap:Envelope>

This standard is intended to be used each time a service provider exchanges a message with a service consumer regardless of the binding used to call the service. For example, the same envelope will be used when invoking services over HTTP or MSMQ. As a result, our service agent framework builds SOAP v1.1 envelopes when services are invoked.

Further, developers use document style encoding (as opposed to RPC-style encoding) with XML Schema (XSD) when possible since doing so makes it possible to support arbitrarily complex types in XML messages. The .NET Framework supports document style encoding by default.

The reason the SOAP 1.1 standard is being used rather than SOAP 1.2 is that SOAP 1.1 is supported natively in the .NET Framework v1.1.

Header Standard
This header standard defines the XML schema used to add informational content to the Header element of the envelope. This information is useful for service management, routing, and security.

The standard includes two parts. The first is the WS-Addressing schema found at http://schemas.xmlsoap.org/ws/2004/08/addressing/. Elements in this schema are used for the following purposes.

Element Usage
  • MessageID (Required)- A GUID that uniquely identifies this message. Useful for logging and duplicate message handling

  • To (Required) The physical address the message is being sent to. Useful for routing. A URL for HTTP requests or a direct format name for a queue in the form of a URN, e.g. urn:DIRECT=OS:compassion.com\PUBLIC\MyQueue;JOURNAL

  • From (Required) The service or application that created the message. Typically uses only the Address element formatted as a URN to identify the creator of the message. Useful for logging.

  • Action (Required) The soapAction for the operation being invoked

  • ReplyTo (Optional) Contains an address used to send a response to. This can take the form of a URL for HTTP or a direct format queue name in the form of a URN. Optionally, this element may include reference properties if additional information is required for a particular endpoint. If not specified the reply is sent to the URI from which the original was sent. Services may also use this element in order to send delayed responses to a particular location. For example, the immediate response to the message may include an acknowledgement while the eventual complete response is sent to the ReplyTo address

  • RelatesTo (Optional) Populated in a response message from the request-response message exchange. Contains the MessageID of the original request message


  • The second schema is the http://schemas.compassion.com/common/headers/2005-04-05 schema that defines elements used within the SOAP Header element as well as the From element of WS-Addressing. The definition of the headers schema is as follows:

    <?xml version="1.0" encoding="utf-8" ?>
    <xs:schema id="CIHeaders"
    targetNamespace=
    "http://schemas.compassion.org/common/headers/2005-04-05"
    elementFormDefault="qualified"
    xmlns:ch=
    "http://schemas.compassion.com/common/headers/2005-04-05"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="MesssageStreamID" type="xs:anyURI">
    </xs:element>
    <xs:element name="CreatorIdentity"
    type="xs:string"></xs:element>
    <xs:element name="DateTimeStamp"
    type="xs:dateTime"></xs:element>
    <xs:element name="ProcessID"
    type="xs:anyURI"></xs:element>
    <xs:element name="Culture"
    type="xs:string"></xs:element>
    </xs:schema>

    The usage of the elements is as follows:

    Element Usage
  • MesssageStreamID (Required) A GUID that identifier a unique sequence of messages that flow through the SOA. Used for correlating messages and for logging purposes. If the creator of the message does not have a MessageStreamID in hand from a previous message, it should create one and place it in the Header.

  • CreatorIdentity (Required) The windows account representing the creator of the message. Useful for logging and to authorize requests using the trusted subsystem model

  • DateTimeStamp (Required) Identifies in Coordinated Universal Time (UTC) when the message was created by the service consumer or provider. Useful for logging

  • ProcessID (Optional) A GUID that identifies a process. Typically populated by process services. If a service receives a ProcessID it should include it in subsequent messages

  • Culture (Optional) The culture setting for the client specifying the formats of date time, currency, and numeric data that follows RFC 1766 derived from ISO 639-1 and ISO 3166, e.g. US English="en-US"

  • UICulture (Optional) The culture setting for the client specifying the language to use that follows RFC 1766 derived from ISO 639-1 and ISO 3166, e.g. US English="en-US"


  • In the future we'll likely also add a PartnerCountryCode element that identifies the Compassion partner country from which the request originated. This will allow services to take different actions (for example processing different business rules) when the requests come from different countries.

    Examples
    The following is an example request message that employs the standards discussed in this document:

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:ch=
    "http://schemas.compassion.com/common/headers/2005-04-05"
    xmlns:wsa=
    "http://schemas.xmlsoap.org/ws/2004/08/addressing">
    <soap:Header>
    <ch:MessageStreamID>uuid:bbbb-cccc-dddd-eeee
    </ch:MessageStreamID>
    <ch:DateTimeStamp>2005-04-05:T18:00:00
    </ch:DateTimeStamp>
    <ch:CreatorIdentity>ci/dfox</ch:CreatorIdentity>
    <ch:Culture>en-US</ch:Culture>
    <ch:UICulture>en-US</ch:UICulture>
    <wsa:MessageID>uuid:aaaa-cccc-dddd-eeee
    </wsa:MessageID>
    <wsa:To>http://compassion.com/DemoService.asmx
    </wsa:To>
    <wsa:Action>http://compassion.com/DemoServiceURI
    </wsa:Action>
    <wsa:From>
    <wsa:Address>urn:CompassionWeb</wsa:Address>
    </wsa:From>
    </soap:Header>
    <soap:Body>
    <HelloWorld
    xmlns="http://compassion.com/DemoServiceURI" />
    </soap:Body>
    </soap:Envelope>

    Note that the MessageStreamID is created by the service consumer since this is the first message in the stream. The CreatorIdentity is the Windows account of the creator of the message and the culture is the culture setting on the client machine making the request. In this case the message will pass over HTTP and so the To element contains the .asmx address and the Action element the soapAction from the WSDL contract. The From element contains a URN that identifies the application that created the message. Since no ReplyTo element has been provided the reply if any will be sent to the URI from the which the message was received or that defined by the WSDL for the service.

    The reply to the above message would look as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:ch=
    "http://schemas.compassion.com/common/headers/2005-04-05"
    xmlns:wsa=
    "http://schemas.xmlsoap.org/ws/2004/08/addressing">
    <soap:Header>
    <ch:MessageStreamID>uuid:bbbb-cccc-dddd-eeee
    </ch:MessageStreamID>
    <ch:DateTimeStamp>2005-04-05:T18:02:00
    </ch:DateTimeStamp>
    <ch:CreatorIdentity>ci/serviceAccount
    </ch:CreatorIdentity>
    <ch:Culture>en-US</ch:Culture>
    <ch:UICulture>en-US</ch:UICulture>
    <wsa:MessageID>uuid:gggg-cccc-dddd-eeee
    </wsa:MessageID>
    <wsa:To>urn:CompassionWeb</wsa:To>
    <wsa:Action>urn:CompassionWeb/ResponseMessage
    </wsa:Action>
    <wsa:From>
    <wsa:Address>http://compassion.com/DemoServiceURI
    </wsa:Address>
    </wsa:From>
    <wsa:RelatesTo>uuid:aaaa-cccc-dddd-eeee
    </wsa:RelatesTo>
    </soap:Header>
    <soap:Body>
    <HelloWorldResponse
    xmlns="http://compassion.com/DemoServiceURI" />
    </soap:Body>
    </soap:Envelope>

    Here the MessageStreamID is copied from the request message and new DateTimeStamp and MessageID are created. The CreatorIdentity is now the account under which the service is executing. The To element contains the URN of the service consumer from the request message and the Action element includes an identifier that identifies the type of message being returned. The From element contains the URI of the service that returned the result and the RelatesTo element contains the MesssageID of the request message.

    In the second example that follows the service consumer includes a ReplyTo element that alerts the service provider as to where the response should be sent. In this case it is an MSMQ queue. The request queue is also specified in the To element. Note that the Action element still identifies the operation being invoked.

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:ch=
    "http://schemas.compassion.com/common/headers/2005-04-05"
    xmlns:wsa=
    "http://schemas.xmlsoap.org/ws/2004/08/addressing">
    <soap:Header>
    <ch:MessageStreamID>uuid:bbbb-cccc-dddd-eeee
    </ch:MessageStreamID>
    <ch:DateTimeStamp>2005-04-05:T18:00:00</ch:DateTimeStamp>
    <ch:CreatorIdentity>ci/dfox</ch:CreatorIdentity>
    <wsa:MessageID>uuid:aaaa-cccc-dddd-eeee</wsa:MessageID>
    <wsa:To>urn:DIRECT=OS:compassion.com\PUBLIC\RequestQ
    </wsa:To>
    <wsa:Action>http://compassion.com/DemoServiceURI
    </wsa:Action>
    <wsa:ReplyTo>
    <wsa:Address>
    urn:DIRECT=OS:compassion.com\PUBLIC\ResponseQ
    </wsa:Address>
    </wsa:ReplyTo>
    <wsa:From>
    <wsa:Address>urn:CompassionWeb</wsa:Address>
    </wsa:From>
    </soap:Header>
    <soap:Body>
    <HelloWorld
    xmlns="http://compassion.com/DemoServiceURI" />
    </soap:Body>
    </soap:Envelope>

    Thursday, October 06, 2005

    Schema Standards

    One of the aspects of our services platform is service communication which I discussed at a high level awhile back. One of the detailed aspects of this area is related to how schemas are defined for our contracts.

    Obviously, we're using XSD to define the schemas and we chose to build all the schemas within BizTalk. The reasons behind this are twofold: we wanted to standardize on a tool for building the schemas and we wanted to make sure the message contracts we defined would work in BizTalk since it is the central hub for all of our long running process services.

    What follows is a discussion of the related standards we used for creating schemas for use in the Compassion SOA that a namespace standard, a naming standard, a versioning standard, and an extensibility standard.

    Namespace Standard
    This namespace standard defines how namespaces will be used within the schemas produced for the Compassion SOA.

    The namespace used to identify schemas will follow a three part convention.

    Part I: A reference to http://schemas.compassion.com. All lowercase should be used.

    Part II. A domain specific identifier. For example constituent, common, child, etc. If there are sub-domains that need to be specified, use a backward slash as in constituent/profile. All lowercase should be used.

    Part III. The date on which the schema was finalized. The data is meant to uniquely identify this version of the schema. When a breaking change is made to the schema, a new schema should be created with a new date. Dates should be in the form yyyy-mm-dd, e.g. 2005-04-05.

    Note: A breaking change to the schema is defined as one where non-optional elements or attributes are added to the schema, where data types are changed, or where the document structure is incompatible with the previous version.

    Valid domain names that have been identified at this time include:

  • constituent – includes constituent management schemas

  • common – includes common schemas such as headers, exceptions, and workflow

  • child – includes child management schemas


  • Examples using this three part convention include:

    http://schemas.compassion.com/constituent/2005-03-28
    http://schemas.compassion.com/constituent/2005-05-05
    http://schemas.compassion.com/common/headers/2005-04-05
    http://schemas.compassion.com/constituent/profile/2005-03-28


    Naming StandardThe naming standard is in place in order to specify how elements and attributes are named. It consists of the following:

  • Element names should use Pascal casing (upper case first letter of each word)

  • Attribute names should use Camel casing (lowercase first letter of the first word, upper case first letter of all other words

  • Attributes should be used for identifying information within an element. For example identifiers such as ConstituentID would be modeled as an attribute

  • The ID suffix should be uppercase when used


  • Versioning Standard
    The versioning standard is in place in order to support visibility to consumers of documents produced using the schema for non-breaking changes. For example, if a schema includes a new optional element or attribute, the versioning standard allows a consumer of the schema to note the version in order to dynamically add the appropriate element.

    The versioning standard mandates the inclusion of the following attribute in the root node of the schema:

    <xs:attribute name="schemaVersion" type="xs:decimal" />

    By adding this attribute documents can be created that specify the version number in addition to the unique identifier specified in the namespace.

    When a new schema is created, documents created using the schema should default the schemaVersion to 1.0. As revisions are published the latest schemaVersion will be placed in instance documents, for example, 1.2. Since the version numbers are not visible within the schemas but only in the instance documents, the schema repository will track older versions of the schema.

    Extensibility Standard
    In order to provide extensibility for consumers of schemas as the schema evolves the following extensibility standard has been created. The following element should be added to the root element of the schema as well as any other elements where extensibility may be expected.

    <xsd:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>

    This element insures that additional elements of any number from any namespace may occur within this element and that a consumer is not obligated to validate the contents ("lax").

    The second aspect of this standard is that the root element should also contain the following element.

    <xs:anyAttribute namespace="##any" processContents="lax"/>

    This allows additional attributes to be placed in the root element. This standard has implications for the way in which .NET schema consumers behave. Using this standard, .NET classes created using the Xml Schemas/DataTypes Support Utility (xsd.exe) will include arrays of XmlElement and XmlAttribute objects that are decorated with the XmlAnyElementAttribute and the XmlAnyAttributeAttribute. To populate these elements consumers will then need to create these types and add them to the array. While this provides extensibility, the extension objects are not strongly typed. See the examples section for the sample code to do this.

    Examples
    An example schema follows:

    <?xml version="1.0"?>
    <xs:schema
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    targetNamespace=
    "http://schemas.compassion.com/constituent/2005-03-01"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Constituent">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Name" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address1" type="xs:string"
    minOccurs ="1" />
    <xs:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="schemaVersion"
    type="xs:decimal" />
    <xs:attribute name="ConstituentId"
    type="xs:long" />
    <xs:anyAttribute namespace="##any"
    processContents="lax" />
    </xs:complexType>
    </xs:element>
    </xs:schema>

    Note the presence of schemaVersion as well as the namespace that adheres to the Namespace Standards defined above and the extensibility points.

    An instance document using this schema would then look as follows.

    <?xml version="1.0"?>
    <Constituent>
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    ConstituentId="1234" schemaVersion="1.0">
    <Name>John Jones</Name>
    <Address1>555 Elm Street</Address1>
    </Constituent>

    If a breaking change occurs, for example, an Address2 element is added that is not optional (it’s minOccurs attribute is set to "1") then the new schema would be defined as follows.

    <?xml version="1.0"?>
    <xs:schema
    xmlns=
    "http://schemas.compassion.com/constituent/2005-04-01"
    targetNamespace=
    "http://schemas.compassion.com/constituent/2005-04-01"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Constituent">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Name" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address1" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address2" type="xs:string"
    minOccurs ="1" />
    <xsd:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="schemaVersion"
    type="xs:decimal" />
    <xs:attribute name="ConstituentId"
    type="xs:long" />
    <xsd:anyAttribute namespace="##any"
    processContents="lax" />
    </xs:complexType>
    </xs:element>
    </xs:schema>

    Note that the targetNamespace has been changed since the schema contains a new element.

    However, if the schema published under the http://schemas.compassion.com/constituent/2005-03-01 namespace is modified to support an optional Address2 (minOccurs = "0"), then documents created using the new version should include the incremented schemaVersion, for example, to 1.5 as in the following example document.

    <?xml version="1.0"?>
    <Constituent>
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    ConstituentId="1234" schemaVersion="1.5">
    <Name>John Jones</Name>
    <Address1>555 Elm Street</Address1>
    <Address2>Apt 5678</Address2>
    </Constituent>

    In this way consumers of the new document will be valid under the original schema (1.0) through the extensibility points and the document can be identified as varying from the original through the schemaVersion. Documents created under the original version of the schema (1.0) will be valid under the new version since Address2 is optional.

    In order to illustrate how the extensibility portion of the standard works consider the following code.

    <XmlTypeAttribute([Namespace]:= _
    "http://schemas.compassion.com/constituent/2005-03-01"), _
    XmlRootAttribute([Namespace]:= _
    "http://schemas.compassion.com/constituent/2005-03-01", _
    IsNullable:=false)> _
    Public Class Constituent
    <XmlElementAttribute()> _
    Public Name As String

    <XmlElementAttribute()> _
    Public Address1 As String

    <XmlAnyElementAttribute()> _
    Public Any() As XmlElement

    <XmlAttributeAttribute()> _
    Public schemaVersion As Decimal

    <XmlIgnoreAttribute()> _
    Public schemaVersionSpecified As Boolean
    <XmlAttributeAttribute()> _
    Public ConstituentId As Long

    <XmlIgnoreAttribute()> _
    Public ConstituentIdSpecified As Boolean

    <XmlAnyAttributeAttribute()> _
    Public AnyAttr() As XmlAttribute
    End Class

    This code was generated using the schema shown previously using the xsd.exe utility. As you can see the tool generated an array of XmlElement objects called Any and an array of XmlAttribute objects called AnyAttr. Client code that needs to populate the new version of the schema (1.5) with the optional Address2 element would then need to do the following.

    Dim c As New Constituent

    ' Populate the document
    c.Name = "Willie Mays"
    c.Address1 = "Candlestick Park"
    c.ConstituentId = "1234"
    c.schemaVersion = 1.5

    ' Create the Address2 element
    Dim xdoc As New XmlDocument
    Dim x1 As XmlElement = xdoc.CreateElement("Address2", _
    "http://schemas.compassion.com/constituent/2005-03-01")
    Dim x2 As XmlText =
    xdoc.CreateTextNode("San Francisco Giants")
    Console.WriteLine(x1.NamespaceURI)

    ' Append it to the document
    x1.AppendChild(x2)

    ' Add it to the Constituent
    Dim ext(0) As XmlElement
    ext(0) = x1
    c.Any = ext

    The disadvantage to this approach this approach is that the code required to add the additional element is fairly cumbersome. However, doing so allows the Constituent class to remain unchanged in the event that it resides in a different assembly that will not be recompiled and redistributed to clients.

    Too Small To Ignore

    Yesterday the President and CEO of Compassion Wes Stafford spoke to the entire organization about his new book Too Small to Ignore: Why Children are the Next Big Thing published by Waterbrook Press.



    The book is a wakeup call that brings home the message that children matter and that those who have the power (which ultimately includes all of us) should use that power to be the voice for improverished children around the world. Wes grew up in rural west Africa (Ivory Coast) as the son of missionaries and lived among the people that Compassion now serves. He has a unique insight into the problem and the face of poverty that he also shares in the book in recounting some of his journey.

    The book will hit stores October 18th but you can preorder from the link above.

    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.