Adding Transaction Support


Of course in enterprise procurement, sometimes creativity is required to obtain the right materials at the right time. While this creativity often rests in the heads of the staff involved in the procurement process, the computing systems that support those roles must also be able to deal with the logistics of the kinds of situations where staff order numerous partial shipments of parts from a number of suppliers to fulfill a single order. In this situation, we need a mechanism that permits orders to execute seamlessly across a number of Web services as though it was a single order sent to a single provider. In short, we need transactional support.

In this section we use Java code to illustrate how to transaction-enable Web services and applications which consume those services. Although BPEL supports transactional semantics with its compensate activity, and furthermore supports distributed transactions via the WS-Transaction Business Activity protocol, at time of writing no BPEL toolkits are available that support this. However, bear in mind that mapping BPEL activity scopes onto transactions is relatively straightforward, and once BPEL tool support improves, transactional underpinnings for BPEL workflows will become commonplace.


Adding transaction support into a Web services-based application is a multistage process that must be implemented by the software agents of all parties intending to participate in transactions. Though each party's responsibilities are generally different, they can be categorized into one of two roles:

  • Client application is usually the software entity that drives the business logic of the application, now additionally demarks the transaction boundaries within that application which in effect groups calls on disparate Web services into a single logical, reversible piece of work.

  • Web service provider the Web services consumed by the client application must be augmented to understand the transaction protocol SOAP headers and, additionally, provide a participant that will handle transaction protocol messages on behalf of its associated service. The Web service provider generally also requires some work at the back end to integrate the business Web service and the associated participant.

In addition, the transacting Web services also need to agree on a mutually trusted transaction coordinator to control the execution of the transaction. While the nature of this trust varies (it can be based on physical location, or implemented using a WS-Security-based approach), Web services administrators may refuse to allow transactions involving their services to be driven by an unknown or untrusted coordinator. Such a refusal is usually on the basis that a rogue coordinator may attempt to interrupt the enterprise by holding up any resources involved with the transaction in a denial of service attack.

For each of the vendor's Web services involved in our EPS application, we need to perform the following tasks:

  1. Register header processors in the SOAP stacks of both vendors' Web services. The header processors are software entities that map transactional contexts onto local threads of activity within the Web service.

  2. Create a participant Web service for each vendor that deals with Web services transaction protocols (in this case, the OASIS BTP protocol) at the front end and maps that onto back-end resources in an appropriate manner.

The alternate part Web service is slightly different. Since it is effectively stateless and so does not require transactional support per se (transactions are for supporting consistent state changes across services), we don't need to register a header processor in the SOAP stack for the alternate part service, nor do we require that the service enrolls a participant since it has no interest in the outcome of the transaction and it knows that none of its state will be changed either way by its outcome.

Once the additional infrastructure to support transactionality has been added into the application, the architecture looks a little different. The architecture of the transaction-aware application is shown in Figure 13-27, where we see transaction-aware Vendor A and Vendor B services with their transaction protocol handlers that deal with SOAP header blocks containing transaction contexts, collocated with participants which deal with the transaction protocol messages. The business-level services and the participants are both able to invoke operations on the back end, which provides the implementation for the service. In a typical enterprise application, the transaction protocol messages will be mapped onto back-end database systems to determine whether the business-level changes made to the data will be made persistent when the transaction has finished.

Figure 13-27. The transaction-aware procurement application architecture.

graphics/13fig12.gif

Now that we have considered the additional non-functional requirements that adding transactions places on the application as a whole and designed an architecture that can accommodate those requirements, the next step in the engineering process is to implement and integrate the new system components.

For this example, the ArjunaWST 1.1 toolkit was used. ArjunaWST 1.1 provides a transaction manager, client libraries, participant-side infrastructure, and service protocol handlers for building BTP-based applications. It is available with a free 30-day license from http://www.arjuna.com/products/arjunawst/. It should be noted that other toolkits that implement BTP are available from a number of other vendors. Also bear in mind that while this example utilizes a BTP toolkit, the overall strategy and architecture are equally applicable to other Web services transactions protocols.


Changes to the Back End Systems

Transactions are a mechanism that helps to safeguard the consistency of state across Web services. However, in our previous back end implementation there was no mechanism available to allow changes to the back end state to be reversed. While we could order parts from the vendor services, there was no way of canceling that order once it had been made.

Clearly for a transactional system, we need to be dealing with data structures with which we can back out changes if needed i.e., if a transaction fails. Therefore in this example, we chose to extend the original back end implementation so it supported a rudimentary cancel operation which, given an order number, will cancel the order and return parts to stock.

The ability to undo work is the kind of behavior we would have available had we used a message queue or database at the back end. We use a simple data structure in preference to databases or queues because we want to concentrate specifically on Web services aspects of the application and not get into the detail of writing back-end enterprise systems.


The implementation of the enhanced back end system is shown in Figure 13-28.

Figure 13-28. Back end system implementation.
 package  com.backend; import java.util.Hashtable; /**  * A more complex model  of an enterprise back-end, designed  * to simulate some  scenarios where  databases, queues and  * so forth are used to implement a Web service.  It's not a  * "true" enterprise back-end  because  it is necessarily  * simplified, but the overall concepts are  very similar.  */ public class ComplexBackend  extends  SimpleBackend {   public ComplexBackend(){}   /**    * The orderParts method picks items from stock and    * readies them for shipping.    * It gives the caller a unique order number which    * references the shipping items.    *    * @param  int  partNo The part  number of the part being    *         ordered.    * @param  int  quantity The number  of those parts that    *         are  required.    * @return  The  order number for the ordered parts.  Will    *          return  -1 if the order could not be honored    *          (e.g. not enough in stock).    */   public int orderParts(int partNo, int quantity)   {     if(_inventory.getQuantityInStock(partNo) >=  quantity)     {       _orderNumber++;       // Place parts onto  shipping queue       InventoryEntry shippingParts =         new InventoryEntry          (_inventory.getPartFromPartNumber(partNo),           quantity);       _shippingOrders.put(new  Integer(_orderNumber),         shippingParts );       // Remove parts from stock.       _inventory.removeItem         (_inventory.getPartFromPartNumber(partNo),          quantity);       // Give  a visual indicator that  parts are shipping       System.out.println("Shipping: \n" +  shippingParts);       System.out.println        (shippingParts.getPart().getPartNumber() +         " left  in stock: "  +         _inventory.getQuantityInStock        (shippingParts.getPart().getPartNumber()));       return _orderNumber;     }     return -1; // Could  not  honor order   }   /**    * The cancel order method can be used to send an order    * from shipping back into inventory. While this method    * could be consumed directly by,  Web services  clients,    * in  this example we  use  it as an example of    * application level compensation which  is invoked by the    * underlying transaction system.    *    * @param  int  orderNo  The  order of the number  that is    *         to be cancelled. This will be  the  same int    *         value that  was  returned when the order was    *         placed.    */   public void  cancelOrder(int  orderNo)   {     if(_shippingOrders.containsKey(new Integer(orderNo)))     {       // If there  is an order  in shipping, stop and return       // the parts to the inventory       InventoryEntry entry = (InventoryEntry)                 _shippingOrders.remove(new Integer(orderNo));       _inventory.addItem(entry.getPart(),                    entry.getQuantity(), entry.getLeadtime());       // Alert user       System.out.println("Returned to inventory: ");       System.out.println(entry);     }   }   private  Hashtable _shippingOrders =  new  Hashtable(); } 

The back end now consists of a more fully featured orderParts method that not only removes items from stock, but also immediately places them into a second structure that holds shipping orders. The back end is said to behave optimistically because it commits resources for shipping immediately. The cancelOrder method provides an application-level logical reverse of the orderParts method that removes the order from the set of shipping orders, and increments the inventory with the number of parts that have been removed from shipping.

Ordinarily, mechanisms like the cancelOrder method are not required since the underlying (transactional) database or queue is available to both the transaction infrastructure and the business logic. However, in this case the back end system effectively plays the role of the database and so it must be equipped with rollback-like features.


At this point, we now have the back end system in place for each vendor service, and we know that the back ends are now suitable for running transactions across. The next step is to expose our new "transactional" resource to the network through its associated business Web service.

Transaction-Aware Service Implementation

Now that we understand the architectural changes that have taken place and seen how the back end has been remodeled to support transactional behavior, we can develop our strategy for supporting transactions at the Web services level. There are two distinct aspects to this support, the first being the construction of a service that is transaction-context aware and the second being the integration of this context-aware service with the underlying transaction toolkit.

To make the business-level Web service transaction-context aware, we need to integrate it with the underlying transaction toolkit and SOAP server. In the transaction toolkit we use in this example, this is a matter of registering a handler for transaction contexts at the same time that the service itself is deployed onto the SOAP server. Since we use the standard Apache Axis toolkit to deploy our sample application, we enhance our deployment configuration file (deploy.wsdd) and specify the name of the handler class that we want to support our service. This is shown in Figure 13-29.

The Axis deployment description shown in Figure 13-29 registers both the transactional services and handlers for transaction contexts for those services that require transactional support (i.e., those services that rely on transactions to guarantee consistent state). In fact, for each transactional service, we need to register three items with the underlying SOAP engine. These are:

  • The name of the class that provides the service implementation.

  • The name of the handler that provides the transaction-context processing capabilities for incoming contexts in SOAP header blocks (i.e., the SOAP actor that handles thread-transaction mapping).

    Figure 13-29. Deploying a transactional service with an Axis deployment file.
     <deployment name="TransactionalServiceDeployment"  xmlns="http://xml.apache.org/axis/wsdd/"  xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">   <handler name="BTPContextProcessor"     type= "java:com.hp.mw.xts.platforms.java.axis.service.BTPContextProcessor"/>   <service name="TransactionalVendorAService"     provider="java:RPC">     <requestFlow>      <handler type="BTPContextProcessor" />     </requestFlow>     <responseFlow>       <handler type="BTPContextProcessor" />     </responseFlow>     <parameter name="className"       value="com.vendora.TransactionalVendorAService"/>   </service>   <service name="TransactionalVendorBService"     provider="java:RPC">     <requestFlow>      <handler type="BTPContextProcessor"/>     </requestFlow>     <responseFlow>       <handler type="BTPContextProcessor" />     </responseFlow>     <parameter name="className"      value="com.vendorb.TransactionalVendorBService"/>   </service>   <service name="AlternatePartService" provider="java:RPC">     <parameter name="className"       value="com.altpart.AlternatePartService"/>   </service> </deployment> 
  • The name of the handler that provides the transaction context processing capabilities for outgoing contexts.

With the transaction toolkit that we use in this example, incoming and outgoing context management are both handled by the same implementing class, com.hp.mw.xts.platforms.java.axis.service.BTPContextProcessor, and so the element <handler type="BTPContextProcessor"/> appears in both the <requestFlow> and <responseFlow> elements. Since we intend for our service to be invoked via SOAP-RPC, we register it with the RPC provider using provider="java:RPC" in the <service> element. Finally, the implementing class for the service is specified as a parameter <parameter name="className" value="com.altpart.AlternatePartService"/>.

Once we have our deployment file written, we use the Axis administrative features to register the service with a running SOAP server. This is easily achieved on the command line with the command:

 java org.apache.axis.client.AdminClient  -lhttp://localhost:8080/axis/services/AdminService 

where the URL specified as the l switch is the location where your Axis installation is hosted. Once this command is executed, the Axis SOAP server will alter its configuration in accordance with the configuration file, as shown in Figure 13-30, and we are ready to invoke it and include it in business transactions.

Figure 13-30. The Web service-side infrastructure after deployment.

graphics/13fig13.gif

In addition to the changes at the back end and the introduction of transaction-processing infrastructure, we have also enhanced the Web service itself such that it exposes information about the business level operations. This enhancement consists of an additional static method and hash table, which maintains a mapping between transaction contexts (provided by the underlying transaction toolkit) and order numbers (generated by the back end). Thus, given a transaction context, it is possible to discover the order numbers associated with that context, so that should the associated transaction fail, the orders (via their order numbers) can be readily cancelled.

Note that context to order number mapping method is not itself exposed as a Web service since it is intended to be consumed by a collocated participant. If, however, the participant needed to be remotely located from the service (for logistical or performance reasons), it is simple enough to provide a Web service to support remote invocations and indeed this is well within the capabilities of most SOAP toolkits to automate.


The service implementation for the vendor services is shown in Figure 13-31. Although we show only the Vendor A service here, the Vendor B service is similar enough (though with Vendor B-specific method names) that it has been omitted for the sake of brevity.

Figure 13-31. The transaction-aware Vendor A service implementation.
 package  com.vendora; import java.util.Hashtable; /**  * The TransactionalVendorAService provides additional  * infrastructure to support transactionality for the  * VendorAService.  */ public class TransactionalVendorAService                 extends VendorAService {   /**    * The placeOrder method has the same functionality  as the    * method from  the  parent class, aside  from it  stores    * an in-memory  mapping between the order number  and  the    * transaction  context  that it  was created within.    *    * Note: in a true enterprise system, this in-memory    * mapping would have to be persistent (e.g. serialized to    * disk or database) in case the service crashed. It is    * not made  persistent here  for the sake  of clarity.    *    * @param int partNumber The part number of the    *        requested parts.    * @param int quantity The quantity of the parts    *        requested.    */   public int placeOrder(int partNumber, int quantity)   {     int  orderNo  = super.placeOrder(partNumber, quantity);     try     {       // Do the mapping thing with the TX context       // and  order ID       com.hp.mw.ts.arjuna.common.Uid currentTxUid  =     com.hp.mw.xts.management.ServiceManagement.getUidFromContext();       if(currentTxUid  != null)       {         if(_txContextOrderMappings.containsKey(currentTxUid))         {           Integer[] existingOrders = (Integer[])                    _txContextOrderMappings.get(currentTxUid);           Integer[] newOrders  =                      new Integer[existingOrders.length  + 1];           for(int  i =  0; i < existingOrders.length; i++)           {             newOrders[i] = existingOrders[i];           }           newOrders[newOrders.length-1] =                                        new  Integer(orderNo);           for(int  i =  0; i < newOrders.length; i++)           {             System.out.println(newOrders[i]);           }         }       }     }     catch(Exception  e)     {       // If we get here, there's no transaction to       // worry about.     }     return orderNo;   }   /**    * The getOrderID method returns the orderID(s) associated    * with the current transaction context.    *    * @return  int[] The order  ID's associated  with the    * current transaction context.    */   static int[] getOrderID()   {     try     {       // Retrieve the array of orders that are associated       // with this context       com.hp.mw.ts.arjuna.common.Uid currentTxUid  =      com.hp.mw.xts.management.ServiceManagement.getUidFromContext();       Integer[] orderNumbers =         (Integer[])_txContextOrderMappings.get(currentTxUid);       if(orderNumbers  == null)       {         return null;       }       else       {         int[] result = new int[orderNumbers.length];         for(int  i =  0; i < result.length; i++)         {           result[i] =  orderNumbers[i].intValue();         }         return result;       }     }     catch(Exception  e)     {       return null;     }   }   /**    * This hashtable is keyed on transaction context, and    * stores arrays of integers which  are  themselves order    * numbers.    */   private  static Hashtable _txContextOrderMappings =                                              new Hashtable(); } 

The final piece of work to complete this service is the configuration of the transaction infrastructure from the chosen transaction toolkit, so that when the service is invoked within the scope of a transaction (that is, it receives a message with a transaction context in the SOAP header) an appropriate participant is enrolled into the transaction. If we recall the explanation offered in Chapter 6, the participant is the entity that deals with the transaction protocol on behalf of the associated business-level Web service. However, the enrollment of participants by a service is more of an administrative task than an architectural or development task, and as such the underlying transaction toolkit allows us to administratively specify the location of a participant to enroll whenever a transactional invocation occurs. This is achieved through a configuration file, which is shown in Figure 13-32.

The default-service-rule-engine-config.xml configuration file is used to declare a set of simple rules that the underlying transaction toolkit can use to make choices about which participants to enroll in response to certain invocations, based on what is being invoked and within the scope of what kind of transaction the invocation occurred.

Figure 13-32. default-service-rule-engine-config.xml configuration file.
 <sre:default-service-rule-engine xmlns:sre=   "http://www.arjuna.com/schemas/xts/service-rule-engine/2002/10/">   <sre:rule>     <sre:transaction-type>any</sre:transaction-type>     <sre:service-endpoint>      http://localhost:8080/axis/services/TransactionalVendorAService     </sre:service-endpoint>     <sre:participant>       http://localhost:8080/axis/services/VendorAParticipant     </sre:participant>   </sre:rule>   <sre:rule>     <sre:transaction-type>any</sre:transaction-type>     <sre:service-endpoint>      http://localhost:8080/axis/services/TransactionalVendorBService     </sre:service-endpoint>     <sre:participant>       http://localhost:8080/axis/services/VendorBParticipant     </sre:participant>   </sre:rule> </sre:default-service-rule-engine> 

A rule consists of a triplet of values for transaction-type, service-endpoint, and participant. That is, a rule allows the administrative specification of which participant to enroll when a particular service is invoked within the scope of a particular type of transaction. For instance, one of the rules in Figure 13-32 states that whenever any kind of transaction context arrives with an invocation on the TransactionalVendorAService, that VendorAParticipant will be enrolled. This can be refined as necessary such that different participant implementations can be enrolled based on specific transaction types, or even a combination of transaction type and service invoked without having to resort to code.

The use of a default-service-rule-engine-config.xml configuration file is particular to the transaction toolkit that we used in this example (Arjuna's WST 1.1), and will differ from vendor to vendor. For instance the HP toolkit that we used in Chapter 6 relied on the service developer to implement enrollments through a programmatic API.


From this point the business-level service is complete, and we have constructed a service-side architecture that looks like the diagram shown in Figure 13-33.

Figure 13-33. The service-side infrastructure up close.

graphics/13fig14.gif

As Figure 13-33 shows, although we had to upgrade our service to expose order numbers associated with particular transactions, most of this work is handled by the underlying transaction toolkit. The Web services aspects of the application have not forced changes onto the actual business logic back end, if we ignore that we had to emulate transactionality with additional cancellation methods in the back end (since that functionality is usually provide by default by a database or other transactional resource). At this point we are ready to join together the transaction-aware service with the underlying transaction protocol, which is done through the development and deployment of a participant.

Implementing Participants

The participant implementation is the piece of logic that determines what back end transactional behavior occurs in response to transaction protocol messages out on the Web services network. Using our toolkit, integrating participants into an application is a two-stage process which involves developing the participant logic, and specifying rules for its enrollment (which are complimentary to the rules that the service uses to enroll participants) into a transaction.

However, let's begin our discussion of the participant looking at the implementation for the Vendor A participant, as shown in Figure 13-34. Note that the implementation for Vendor B's participant is once again similar enough to Vendor A's that we can safely omit a full discussion of its implementation here.

The methods that the VendorAParticipant supports in Figure 13-34 directly correspond to the messages exchanges that a participant is involved in within the scope of a BTP transaction. The most important of these methods are the three that are involved in the two-phase confirm aspects of BTP: prepare, confirm, and cancel.

The prepare method returns a Vote that indicates to the calling transaction coordinator whether it will proceed to make the work done in the scope of the transaction durable to indicate whether state changes in the service will remain or be deleted when the transaction finishes. In this case, the method is hardwired to always return a VoteConfirm object, which indicates to the invoking transaction manager that state changes associated with this participant will be persisted after the transaction is finished. Since we have chosen this optimistic approach, only in those (presumably minor number of) cases where something goes wrong will the participant have to take restorative action.

Figure 13-34. The VendorAParticipant implementation.
 package com.vendora; import com.hp.mw.xts.participant.*; import com.hp.mw.ts.arjuna.common.Uid; import com.hp.mw.xts.core.exceptions.*; import com.hp.mw.xts.core.common.Vote; import com.hp.mw.xts.core.common.VoteConfirm; import com.hp.mw.xts.core.common.VoteCancel; import com.hp.mw.xts.qualifiers.Qualifier; import com.hp.mw.xts.management.ParticipantManagement; import com.hp.mw.ts.arjuna.state.InputObjectState; import com.hp.mw.ts.arjuna.state.OutputObjectState; /**  * The VendorAParticipant provides the transaction  * participant (the actor that understands the transaction  * protocol) for the TransactionalVendorAService.  */ public class VendorAParticipant implements Participant {     /**      * Implements the Participant.prepare operation.      *      * @param id The identifier of the entity that is to be      *        prepared      * @param qualifiers ignored in this implementation      * @return Vote to CONFIRM or CANCEL the transaction      */     public Vote prepare(Uid id, Qualifier[] qualifiers)         throws GeneralException, InvalidInferiorException,         WrongStateException, HeuristicHazardException,         HeuristicMixedException     {         // VendorA takes an optimistic strategy and always         // internally commits resources and votes to confirm.         return new VoteConfirm();     }     /**      * Implements the Participant.confirm operation.      *      * @param id The inferior which is to have <i>confirm</i>      *        invoked on it      * @param qualifiers ignored in this implementation      */     public void confirm(Uid id, Qualifier[] qualifiers)         throws GeneralException, InvalidInferiorException,         WrongStateException, HeuristicHazardException,         HeuristicMixedException     {         // Do nothing on confirm because VendorA's strategy         // is to confirm immediately.     }     /**      * Implements the Participant.cancel operation.      *      * @param id The inferior which is to have <i>cancel</i>      *        invoked on it      * @param qualifiers ignored in this implementation      */     public void cancel(Uid id, Qualifier[] qualifiers)         throws GeneralException, InvalidInferiorException,         WrongStateException, HeuristicHazardException,         HeuristicMixedException     {         // The resources have already been committed. Must         // now perform compensating action.         // Get the orderNos from the         // TrasactionalVendorAService         int[] ordersToCancel =                     TransactionalVendorAService.getOrderID();         if(ordersToCancel != null)         {             for(int i = 0; i < ordersToCancel.length; i++)             {                 // Invoke compensating action at the back end                 TransactionalVendorAService.                      _backend.cancelOrder(ordersToCancel[i]);             }         }     }     /**      * Implements the Participant.contradiction operation.      *      * @param id The inferior to inform      * @param qualifiers ignored in this implementation      */     public void contradiction(Uid id, Qualifier[] qualifiers)         throws GeneralException, InvalidInferiorException,         WrongStateException     {         // There has been a contradiction, shout for help!         System.err.println(                     "\t\tVendorAParticipant.contradiction(" +                     id.stringForm() + ")");     }     /**      * Implements the Participant.superiorState operation.      *      * Not required for this example      *      * @param id      * @param qualifiers      * @return null in all cases      */     public String superiorState(Uid id,                                 Qualifier[] qualifiers)         throws GeneralException, InvalidInferiorException     {         return null;     }     /**      * Implements the Participant.defaultIsCancel operation.      *      * The underlying business logic does not support      * timeouts.      *      * @param id The inferior to query      * @return false in all cases because default action is      *         to confirm.      */     public boolean defaultIsCancel(Uid id)     {         return false;     }     /**      * Implements the Participant.confirmedReceived      * operation.      *      * This implementation does not acknowledge receipt of      * confirm messages.      *      * @param id The inferior to query      * @return false in all cases      */     public boolean confirmedReceived(Uid id)     {         return false;     }     public int status(Uid id, Qualifier[] qualifiers) throws                    GeneralException, InvalidInferiorException     {         System.err.println(                     "\t\tVendorAParticipant.superiorState(" +                     id.stringForm() + ")");         return           com.hp.mw.xts.core.common.TwoPhaseStatus.UNKNOWN;     }     public String name()     {         return "VendorAParticipant implementation";     }     // These are left empty because the VendorAParticipant     // does not need to be recoverable.     public boolean packState (OutputObjectState os)     {         return true;     }     public boolean unpackState (InputObjectState os)     {         return true;     } } 

Since we have chosen to commit state changes in the first phases of the two-phase confirm process (during the prepare phase), there is nothing to do if the transaction gets to the confirm phase and so the confirm method is left empty. Conversely, should the transaction coordinator decide to cancel the transaction, the cancel method will be invoked, which will perform a compensating action to reverse the work done by the service. In this case that compensation will be to invoke the cancelOrder method on the back end system for each part that was ordered within the scope of the transaction.

Like the business-level Web services, the participant is also deployed into the SOAP server. The only difference here is that the consumer of the participant service is always another Web service (the transaction manager service) and not client applications. Deploying the service is straightforward and relies on a simple deployment descriptor file as shown in Figure 13-35.

Figure 13-35. Deploying a participant with an Axis deployment file.
 <deployment name="ParticipantDeployment" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">   <service name="VendorAParticipant" style="message">     <parameter name="className" value= "com.hp.mw.xts.platforms.java.axis.documentHandlers.ParticipantDocumentHandler"/>     <parameter name="allowedMethods" value="invoke"/>   </service>   <service name="VendorBParticipant" style="message">     <parameter name="className" value= "com.hp.mw.xts.platforms.java.axis.documentHandlers.ParticipantDocumentHandler"/>     <parameter name="allowedMethods" value="invoke"/>   </service> </deployment> 

Like business Web services, participants must also be deployed into a SOAP server to be accessible. However, the participant services don't require any additional SOAP actors to be registered as handlers in their service stacks, since they only deal with transaction protocol messages and not application-plus-context messages. Thus, the deployment configuration files are correspondingly simpler, as we see in Figure 13-35.

There are, however, a few remarkable differences between the participant deployment configuration file and the transactional service deployment configuration file that we saw in Figure 13-29. First, the participant service uses document-style SOAP to exchange messages (it exchanges arbitrary XML documents in its SOAP payload, not SOAP-schema encoded payloads) and so the style attribute in the service element is set to style="message".

Furthermore, because the service now needs to understand arbitrary SOAP message encodings, it needs to be configured with an implementation class that can decode those messages and dispatch them appropriately. This is handled by the className parameter, which specifies a particular document handler to which incoming messages will be routed. The ParticipantDocumentHandler class is a component from the underlying transaction toolkit that understands the transaction protocol messages and is able to dispatch them appropriately at the back end.

The final parameter, allowedMethods, specifies a list of the methods from the ParticipantDocumentHandler class that will be exposed as a Web service. In this case, we only require that the invoke method is exposed since this is the only necessary conduit to enable BTP messages to be routed through to the back end.

The last piece in the puzzle for service-side transactionality is the administration of the participant. For this we complete the participant-side rule engine configuration, from the underlying transaction engine toolkit, where we specify a mapping from endpoints to particular implementations. This is in effect the reciprocal feature of the service side rule engine configuration, and allows a particular participant implementation to be used based on the endpoint of the ultimateReceiver so that many different participant implementations can be hosted and routed by a single SOAP server instance.

Figure 13-36. default-participant-rule-engine-config.xml configuration file.
 <?xml version="1.0" encoding="UTF-8"?> <pre:default-participant-rule-engine xmlns:pre="http://www.arjuna.com/schemas/xts/participant-rule-engine/2002/10/">   <pre:rule>     <pre:participant-endpoint>       http://localhost:8080/axis/services/VendorAParticipant     </pre:participant-endpoint>     <pre:participant-implementation>       com.vendora.VendorAParticipant     </pre:participant-implementation>   </pre:rule>   <pre:rule>     <pre:participant-endpoint>       http://localhost:8080/axis/services/VendorBParticipant     </pre:participant-endpoint>     <pre:participant-implementation>       com.vendorb.VendorBParticipant     </pre:participant-implementation>   </pre:rule> </pre:default-participant-rule-engine> 

In Figure 13-36, we map the participant endpoint onto the implementing Java class. Now when a BTP message arrives and is processed by the ParticipantDocumentHandler, it will automatically be dispatched to the correct participant implementation. Like the equivalent service-side rules, this means that a system can be evolved over time without having to resort to coding.

As we see in Figure 13-37, at this point the transactional service is ready for consumption. The participant implementation can cooperate with the service to provide the additional transactional quality of service that enterprises require. All that is left now is to consume the functionality offered by the service, and see how we can drive its transactional aspects.

Figure 13-37. Participant and service-side infrastructure.

graphics/13fig15.gif

Consuming Transactional Web Services

Writing transactional applications involves not only developing business logic, but also controlling the underlying transactional behavior of the system. The key to developing such applications is to partition the work into suitable transactions so that work can be reversed, replayed and so forth as necessary.

In this example, we try to purchase components from both suppliers where we will only proceed with the order if both suppliers commit to providing the necessary parts. Where either one of the suppliers is unable to meet its commitment, or where there is a failure that prevents the order from making progress, we will cancel the transaction. Canceling the transaction causes the transaction manager to instruct all enrolled participants to perform the appropriate restorative action at the back end of their associated service. As we saw in Figure 13-34, this ultimately has the effect of reversing any work done so that the transaction appears to have never run. A sample client application is shown in Figure 13-38.

Figure 13-38. Client application implementation.
 package  client; import com.hp.mw.xts.*; import com.hp.mw.xts.core.common.StatusItem; import com.hp.mw.xts.core.common.Vote; import com.hp.mw.xts.core.common.VoteConfirm; import com.hp.mwlabs.xts.portability.xml.XMLProvider; import com.vendora.VendorAStub; import com.vendorb.VendorBStub; import com.altpart.AlternatePartStub; /**  * Runs a simple transactional invocation on each service.  */ public class TransactionalClient {   public static void main(String[] args)   {     try     {       TransactionManager tm =         TransactionFactory.getTransaction(                          XMLProvider.getDOMDocument(CONFIG));       UserTransaction  ut = (UserTransaction)tm;       // We're going to repeat this order  until someone is       // out of stock to show transactions cancelling...       // We happen to  know that there  are  only 250 spark       // plugs available in VendorB,  so 7 times round will       // do it       for(int  i =  0; i < 6; i++)       {         // Start the transaction - in this case an atom         ut.begin(TxTypes.ATOM);         // Business  logic         VendorAStub  vas  = new VendorAStub(new java.net.URL( "http://localhost:8080/axis/services/TransactionalVendorAService"));         VendorBStub  vbs  = new VendorBStub(new java.net.URL( "http://localhost:8080/axis/services/TransactionalVendorBService"));         // Going to order 2 Head Gaskets from VendorA and         // 45 spark  plugs from B, or nothing at  all.         int  orderA = vas.placeOrder(101, 2);         int  orderB = vbs.orderComponent(45,  888);         // Terminate the transaction         Vote prepareResult = ut.prepare();         if(prepareResult instanceof  VoteConfirm  &&             orderA >= 0 && orderB >=  0)         {           System.out.println("Orders:  " +  orderA + " and "              + orderB +  " have successfully  been placed.");           ut.confirm();         }         else         {           System.out.println("Orders:  " +  orderA + " and "                        + orderB +  " have been  cancelled.");           ut.cancel(null);         }       }       System.exit(0);     }     catch(Exception  e)     {       e.printStackTrace(System.err);       System.exit(1);     }   }   private static final String CONFIG = "<proxy-config   xmlns=\"http://www.arjuna.com/schemas/xts/proxy/2002/10/\">"      +"\n<properties>"      +"\n<property name=\"usertransaction_implementation\" "      +"value=com.hp.mwlabs.xts.api.proxy.UserTransactionProxy\"/>"      +"\n<property name=\"transactionmanager_implementation\" "      +"value=\"com.hp.mwlabs.xts.api.proxy.TransactionManagerProxy\" />"      +"\n</properties>"      +"\n<transaction-manager-url>"      +"http://localhost:8080/axis/services/WebTransactionManager"      +"</transaction-manager-url>"      +"\n</proxy-config>"; } 

This application can be broken down into four broad phases. The first of these phases is where the client binds to the transaction manager that will support the subsequent transactional behavior. This is realized by the opening lines of the main method that create and initialize a TransactionManager and UserTransaction instance (which is achieved through an XML configuration file which the application encodes as a static string).

It is the UserTransaction instance that is then subsequently used to delimit the transactional behavior of the application. The begin method of the UserTransaction instance is called to instruct the remote transaction manager to create a new BTP atom. Once this call completes any further work, including Web services invocations (where the Web services support transactions), is implicitly within the scope of this transaction. At the client application side, the current thread is associated with the context. Whenever a Web services invocation is made, the XML form of the context travels in a SOAP header block to the invoked service where, as we have seen, the service-side infrastructure handles thread-context association and enrollment of participants.

The real work of this application is contained within its business logic. In this case the application simply orders parts from the two vendor services. Supported by the transaction, the orders continue until such point that either there is an underlying fault or until the order process fails which causes the transaction to cancel, and any orders and parts currently unsettled to be cancelled are returned to stock.

It is important to understand that this is possible because of the open-top nature of the underlying transaction protocol. Since both the prepare and confirm/cancel phases of the two-phase confirm are driven by the client application, it is possible for the application to affect the transaction directly and assert that the transaction confirm or cancel, just like the transaction manager. Application-level rules can be used to complete or abort transactions in addition to the standard semantic where faults are the cause of transactional aborts.

Once the client application is developed and built, the final stage is to deploy and execute. Since we have chosen the Apache Axis platform for building this demo application, we need to configure our application to use the Axis client-side infrastructure to propagate transaction contexts on our behalf. The configuration for this is shown in Figure 13-39. Like the service-side configuration, the client configuration registers handlers that deal with thread-context association at the client side. Once this configuration is in place, the application is ready to execute.

Figure 13-39. An Axis client-config.wsdd configuration file.
 <?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">   <globalConfiguration>    <parameter name="sendXsiTypes" value="true"/>    <parameter name="sendMultiRefs" value="true"/>    <parameter name="sendXMLDeclaration" value="true"/>    <requestFlow>     <handler    type=    "java:com.hp.mw.xts.platforms.java.axis.client.OutgoingContextHandler"/>    </requestFlow>    <responseFlow>     <handler    type=    "java:com.hp.mw.xts.platforms.java.axis.client.IncomingContextHandler"/>    </responseFlow>   </globalConfiguration>   <transport name="http"    pivot="java:org.apache.axis.transport.http.HTTPSender"/> </deployment> 


Developing Enterprise Web Services. An Architect's Guide
Developing Enterprise Web Services: An Architects Guide: An Architects Guide
ISBN: 0131401602
EAN: 2147483647
Year: 2003
Pages: 141

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