Building Evolvable and Composable Workflows


The previous revision of the enterprise procurement application relied on a static HTML page and a human to drive the application. While this may be a suitable approach for a small business, it does not scale well. In a large enterprise it may be preferable to capture the knowledge of the human operator and automate the process, freeing the human operator to work on more interesting work and allowing the process to execute more quickly.

A simple automation solution would involve screen-scraping techniques to allow a computer program that has been written to mimic the behavior of a human operator to drive the Web page itself via standard HTTP commands. However, this is a rather poor and brittle approach for three reasons:

  • If the Web page's HTML changes, the program's screen scraping routines will fail and have to be updated.

  • If the rules change, the program will have to be edited and rebuilt which is not an approach that supports rapid reconfiguration.

  • Composing such a standalone screen-scraping program into a larger system then gives rise to Enterprise Application Integration (EAI) headaches further down the track.

The advantages of using Web services-based workflow technology to orchestrate service interactions compared to the previous human-driven Web-based approach are twofold. First, human knowledge is encoded into XML, which can be easily re-encoded and evolved as and when the problem domain changes. Had this knowledge been encoded into compiled program code, its maintenance and rapid evolution would involve software engineers, system administrators and so forth that necessarily reduces the pace at which updates can be made. In this XML form (and indeed even more so if graphical tools that target the BPEL language are used), the job of evolving the workflow can be delegated to business analysts without necessarily having to resort to heavyweight software maintenance practices.

Second, as a Web service itself, the workflow script can easily form a component to be re-used by other Web services-based applications. Compared to the either the human-driven or programmatic screen-scraping approach, this method permits much more robust re-use of components. However, to reap these benefits, we must first automate the process into a workflow.

Of course, we could resort to traditional (proprietary) middleware to integrate the Web-based application. However, this is short sighted since it then produces another proprietary system that we may need to integrate at a later date. Better just to switch all integration work over to the canonical form that Web services provides now, unless there are extremely compelling reasons not to.

Not using Web services for integration work just stores headaches for later!


Automating the Procurement Process

A better solution is to cut out the middleman (the Web page and human operator) altogether and instead capture the process as a workflow script that encodes the kinds of decisions that humans would make. That workflow script can then be executed and interact directly with the back-end Web services, and can itself be exposed as a Web service allowing aggregation into larger applications, as shown in Figure 13-2.

Figure 13-2. Encoding Business Rules into a Workflow.

graphics/13fig02.gif

From the perspective of a consumer, the workflow-based Web service that drives the procurement is no different from any other Web service insofar as it is an endpoint through which messages are exchanged. In this case, the message exchanges need to be able to describe procurement of items based on cost, part number, and lead time.

It also needs to determine whether it is cost of lead time that is the most important factor for this order that is, whether the parts have to be procured quickly or cheaply. Given these criteria, as a first step toward designing the workflow, we can design a WSDL interface that describes this interface, which is shown in Figure 13-3.

Figure 13-3. Procurement Service WSDL (Abstract) Interface.
 <?xml version="1.0"?> <definitions targetNamespace="http://procurement.example.org" xmlns:tns="http://procurement.example.org" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:slnk="http://schemas.xmlsoap.org/ws/2002/07/service-link" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:ele="http://procurement.example.org/elements">   <types>     <schema targetNamespace=       "http://procurement.example.org/elements"       xmlns="http://www.w3.org/2001/XMLSchema">       <xsd:element name="partNumber" type="xsd:int"/>       <xsd:element name="quantity" type="xsd:int"/>       <xsd:element name="costOverLeadtime"         type="xsd:boolean"/>       <xsd:element name="orderNumber" type="xsd:int"/>     </schema>   </types>   <message name="OrderPartsRequest">     <part name="partNumber" element="ele:partNumber"/>     <part name="quantity" element="ele:quantity"/>     <part name="costOverLeadtime"       element="ele:costOverLeadtime"/>   </message>   <message name="OrderPartsResponse">     <part name="orderNumber" element="ele:orderNumber"/>   </message>   <portType name="PartsServicePortType">     <operation name="orderParts">       <input message="tns:OrderPartsRequest"/>       <output message="tns:OrderPartsResponse"/>     </operation>   </portType>   <slnk:serviceLinkType name="OrderPartsLT">     <slnk:role name="PartServer">       <slnk:portType name="tns:PartsServicePortType"/>     </slnk:role>   </slnk:serviceLinkType> </definitions> 

The WSDL shown in Figure 13-3 is the basis for all interaction with the procurement Web service. Through this interface, external applications can invoke the logic captured as a BPEL script, which will interact with the vendor services on the users' behalf. The service description is straightforward, and simply consists of a set of types which are composed into two messages (OrderPartsRequest for inbound requests and OrderPartsResponse for outgoing responses), and a single portType declaration, which supports the orderParts operation. Thus the orderParts operation has a signature that we can approximate to the semantically equivalent Java, int orderParts(int partNumber, int quantity, boolean costOrLeadtime). The return value for this method is the order number for the specified quantity of parts and the arguments represent a part number that has relevance for a particular part with a particular vendor the number of those parts required and whether cost or leadtime is the overriding priority.

The only element that is different from a vanilla WSDL document is the serviceLinkType element, which is a BPEL construct that specifies which services can link to (are allowed to invoke) the part ordering service. In this case, the serviceLinkType specifies only a single role, which is the PartsServicePortType. This means that the service places no constraints on the interfaces of other services that may wish to invoke the part ordering service. Had another role been specified, it would have meant that any service invoking the part ordering service would have had to fulfill the indicated role that is, a portType for the part ordering service to call back to must be supported by the invoker of the part ordering service.

The choice of using a synchronous message exchange pattern as opposed to a one-way pattern has been made under the assumption that the procurement process will be short-lived. This approach has the benefit that it is simple to understand and matches the message exchange pattern exhibited by the underlying vendor services.

However, the one-way approach would have the benefit that network resources would not be consumed while waiting for the procurement process to finish, and also decouples the procurement service from the procurer so they may fail and recover independently of one-another without upsetting the behavior of the system.


Augmenting Remote WSDL Interfaces

Having understood the interface and message exchanges between the client and the workflow-based service, we now need to analyze the interfaces and message exchanges that occur at the back end so we can utilize the functionality exposed by those services within the workflow script. To utilize the functionality of a Web service from within a BPEL workflow, the service's interface needs to be augmented with appropriate serviceLinkType declarations, just like the WSDL for the workflow itself. This is achieved most easily via the WSDL import directive.

We can use the WSDL import directive to extend the existing WSDL documents without having to re-write them. In this case we write a new WSDL document for each service, but include only the serviceLinkType declarations that BPEL workflows require. The WSDL descriptions of each of the vendor services, and the alternate part service as shown in Figure 13-4, Figure 13-5 and Figure 13-6, respectively. In each case, the original WSDL interface is imported and augmented by an appropriate serviceLinkType declaration.

Figure 13-4. The vendor a service WSDL interface.
 <?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://procurement.example.org/va" xmlns:vas="http://localhost:8080/axis/services/VendorAService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:slnk="http://schemas.xmlsoap.org/ws/2002/07/service-link">   <import namespace="http://localhost:8080/axis/services/VendorAService" location=   "http://localhost:8080/axis/services/VendorAService?wsdl"/>   <slnk:serviceLinkType name="VendorAServiceLT">     <slnk:role name="VendorAServiceRole">       <slnk:portType name="vas:VendorAService"/>     </slnk:role>   </slnk:serviceLinkType> </definitions> 

Figure 13-4 shows the serviceLinkType that defines the role of the Vendor A service in the workflow. Since Vendor A is a synchronous service, it specifies only a single role for itself and does not place any restrictions on a particular portType that must be supported by the caller. This is also true of the Vendor B service shown in Figure 13-5.

Figure 13-5. The Vendor B service WSDL interface.
 <?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://procurement.example.org/vb" xmlns:vbs="http://localhost:8080/axis/services/VendorBService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:slnk="http://schemas.xmlsoap.org/ws/2002/07/service-link">   <import namespace="http://localhost:8080/axis/services/VendorBService" location= "http://localhost:8080/axis/services/VendorBService?wsdl"/>   <slnk:serviceLinkType name="VendorBServiceLT">     <slnk:role name="VendorBServiceRole">       <slnk:portType name="vbs:VendorBService"/>     </slnk:role>   </slnk:serviceLinkType> </definitions> 

Like the Vendor A and Vendor B augmentations, the AlternatePartService also declares a unary serviceLinkType, as shown in Figure 13-6.

Figure 13-6. The alternate part service WSDL interface.
 <?xml version="1.0" encoding="UTF-8"?> <definitions targetNamespace="http://procurement.example.org/ap" xmlns:aps=  "http://localhost:8080/axis/services/AlternatePartService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:slnk=  "http://schemas.xmlsoap.org/ws/2002/07/service-link">   <import namespace=   "http://localhost:8080/axis/services/AlternatePartService" location= "http://localhost:8080/axis/services/AlternatePartService?wsdl"/>   <slnk:serviceLinkType name="AlternatePartServiceLT">     <slnk:role name="AlternatePartServiceRole">       <slnk:portType name="aps:AlternatePartService"/>     </slnk:role>   </slnk:serviceLinkType> </definitions> 

Now that we have our BPEL augmented interfaces in place, the next step is to build the workflow that will drive the services behind the interfaces.

Implementing the BPEL Workflow Script

The final goal of our automation project is to capture the rules that a human operator would use when procuring parts in BPEL format. When building the workflow script, the problem can be broken down into these sections:

  1. Declaring the services that the script will interact with, the role that each plays respective to the workflow and the serviceLinkType which references them in the <partners> section of the BPEL script.

  2. Declaration of <containers> to hold messages that are sent and received from the vendor and alternate part services and between the workflow instance and the client application that instantiated it.

  3. Creation of the algorithmic aspect of the workflow script that drives the constituent Web services through the procurement process.

The algorithm that we will implement, which captures the human operator behavior, is straightforward to understand. In the first phase of the algorithm the vendor services are asked for the price and leadtimes for a specific quantity of a particular part, using the alternate part service to lookup the equivalent part number for Vendor B. This is done in parallel, using <sequence> activities nested inside <flow> activities, for performance reasons, and is safe to execute in this manner because only read-only operations are invoked on the vendor services.

Once the cost and leadtime have been discovered, the algorithm then checks to see which is the most important factor, based on the contents of the message sent by the invoking application. Based on that decision, the cheapest or fastest offer is taken up by placing the order with the appropriate vendor service and returning the order number to the invoking application. This is depicted in Figure 13-7.

Figure 13-7. BPEL procurement workflow diagram.

graphics/13fig07.jpg

The workflow is shown graphically in Figure 13-7 (which is, in fact, an audit trail from the engine used to execute the workflow). The workflow instance is created on receipt of a message from an invoking application. From there a parallel activity follows where Vendor A service is asked for price and leadtime information (in the right-hand side of the parallel activity) while Vendor B is asked for the same after the alternate part service has retrieved the alternate appropriate part number.

Once both threads of parallel activity have completed, there is a synchronization at which point the decision for cheapest versus fastest is taken and an order placed with one or other of the vendor services in the subsequent (serial) activity. This strategy is captured in the BPEL workflow script shown in Figure 13-8 and Figure 13-9.

Figure 13-8. BPEL procurement workflow script.
 <process name="Procurement" targetNamespace="http://procurement.example.org" suppressJoinFailure="yes" xmlns:tns="http://procurement.example.org" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sref="http://schemas.xmlsoap.org/ws/2002/07/service-reference/" xmlns="http://schemas.xmlsoap.org/ws/2002/07/business-process/" xmlns:vas="http://localhost:8080/axis/services/VendorAService" xmlns:vbs="http://localhost:8080/axis/services/VendorBService" xmlns:aps="http://localhost:8080/axis/services/AlternatePartService" xmlns:vasn="http://procurement.example.org/va" xmlns:vbsn="http://procurement.example.org/vb" xmlns:apsn="http://procurement.example.org/ap"> 

The XML in Figure 13-8 shows the opening tag of a BPEL process. It declares a process element (the root for all BPEL processes) and the namespaces of the procurement workflow service, its partners and serviceLinkType declarations, and the BPEL namespaces.

Once we have these namespaces, we can start to use their contents to author the workflow script itself, starting with the partner declarations as shown in Figure 13-9.

Figure 13-9. Partner declarations.
 <partners>   <partner name="VendorAService"     serviceLinkType="vasn:VendorAServiceLT"     partnerRole="VendorAServiceRole"/>   <partner name="VendorBService"     serviceLinkType="vbsn:VendorBServiceLT"     partnerRole="VendorBServiceRole"/>   <partner name="AltPartService"     serviceLinkType="apsn:AlternatePartServiceLT"     partnerRole="VendorBServiceRole"/>   <partner name="client" serviceLinkType="tns:OrderPartsLT"     myRole="PartServer"/> </partners> 

Figure 13-9 declares each partner that is involved in the process, and their roles in terms of the serviceLinkTypes that we defined earlier in Figure 13-4, Figure 13-5, and Figure 13-6. Once each partner has been declared, it becomes a reference to a Web service we can interact with as part of our workflow.

To interact with partners (and to do local processing where necessary), we need to be able to send them messages. The container declarations for this workflow are shown in Figure 13-10.

Figure 13-10. Container declarations.
 <containers>   <container name="clientRequests"     messageType="tns:OrderPartsRequest"/>   <container name="clientResponses"     messageType="tns:OrderPartsResponse"/>   <container name="vendorAOrders"     messageType="vas:placeOrderRequest"/>   <container name="vendorAOrderNumbers"     messageType="vas:placeOrderResponse"/>   <container name="vendorAPriceRequests"     messageType="vas:getPriceRequest"/>   <container name="vendorAPriceResponses"     messageType="vas:getPriceResponse"/>   <container name="vendorALeadtimeRequests"     messageType="vas:getLeadTimeRequest"/>   <container name="vendorALeadtimeResponses"     messageType="vas:getLeadTimeResponse"/>   <container name="vendorBOrders"     messageType="vbs:orderComponentRequest"/>   <container name="vendorBOrderNumbers"     messageType="vbs:orderComponentResponse"/>   <container name="vendorBPriceRequests"     messageType="vbs:componentListPriceRequest"/>   <container name="vendorBPriceResponses"     messageType="vbs:componentListPriceResponse"/>   <container name="vendorBLeadtimeRequests"     messageType="vbs:componentLeadtimeRequest"/>   <container name="vendorBLeadtimeResponses"     messageType="vbs:componentLeadtimeResponse"/>   <container name="alternatePartRequests"     messageType="aps:getAltPartRequest"/>   <container name="alternatePartResponses"     messageType="aps:getAltPartResponse"/> </containers> 

Each of the variables used in the process is typed according to the WSDL messages found in the interfaces of the partners and in the procurement service itself. The containers element is placed at process scope in Figure 13-10 because all variables have process scope in BPEL 1.0 in BPEL 1.1 this has (fortunately) been changed to allow the equivalent of containers (called variables) to be declared within arbitrary scopes.

Having containers and partners gives us the ability to exchange the messages held in containers with the Web services represented by partners. The first of these message exchanges in this process is presented in Figure 13-11, and iscaptured in graphical form (from the audit trail of the workflow instance) in Figure 13-12.

Figure 13-11. Initiating a workflow instance.
 <sequence>   <!-- receive input from requestor -->   <receive partner="client"     portType="tns:PartsServicePortType"     operation="orderParts" container="clientRequests"     createInstance="yes"/> 
Figure 13-12. Graphical representation of initiating a Workflow instance.

graphics/13fig04.jpg

The workflow logic begins in the receive activity in Figure 13-11. This activity receives an OrderPartsRequest message through the orderParts operation in the PartsServicePortType portType, which it deposits into the clientRequests container. Since the receipt of this message is the logical beginning of the workflow logic, a new instance of the workflow is created by setting the createInstance="yes" attribute.

Once we have the client request, we can begin to compute how best to meet the procurement needs of the client, as per Figure 13-13.

Figure 13-13. Graphical representation of invoking operations on VendorA.

graphics/13fig05.jpg

The flow from Figure 13-12 is codified in Figure 13-14 where we invoke operations on Vendor A's service to determine the price of the requested part. In Figure 13-14 we declare a flow activity that we use to support the parallel execution of activities in Figure 13-14 (where the workflow interacts with Vendor A's Web service) and in Figure 13-16 (where the workflow interacts with the alternate part service and Vendor B).

The activities in Figure 13-14 are scoped within a sequence to serialize their execution. The strategy is to build a request message to invoke the price operation of VendorA and then the same for the leadtime operation. These results are then stored in the vendorAPriceResponses and vendorALeadtimeResponses containers for later processing. Hence, in Figure 13-14 we see a set of assignment activities to build the outgoing message, an invocation on VendorA's Web service using that message, and then more assignment logic to store the response from the invocation in the appropriate containers.

Figure 13-14. Invoking operations on VendorA in parallel with other operations.
 <!-- Ask vendor A service for a price and leadtime --> <flow>   <sequence>     <assign>       <copy>         <from container="clientRequests"           part="partNumber"/>         <to container="vendorAPriceRequests"           part="partNumber"/>       </copy>     </assign>     <assign>       <copy>         <from container="clientRequests"           part="quantity"/>         <to container="vendorAPriceRequests"           part="quantity"/>       </copy>     </assign>     <invoke partner="VendorAService"       portType="vas:VendorAService" operation="getPrice"       inputContainer="vendorAPriceRequests"       outputContainer="vendorAPriceResponses"/>     <assign>       <copy>         <from container="clientRequests"           part="partNumber"/>         <to container="vendorALeadtimeRequests"           part="partNumber"/>       </copy>     </assign>     <assign>       <copy>         <from container="clientRequests"           part="quantity"/>         <to container="vendorALeadtimeRequests"           part="quantity"/>       </copy>     </assign>     <invoke partner="VendorAService"       portType="vas:VendorAService"       operation="getLeadTime"       inputContainer="vendorALeadtimeRequests"       outputContainer="vendorALeadtimeResponses"/>   </sequence> 

This same strategy is used to invoke operations on Vendor B's Web service and the alternate part service (in parallel with the invocations on Vendor A because they are within the scope of the same flow activity), as shown in Figure 13-15 and expressed in BPEL in Figure 13-16.

Figure 13-16. Invoking operations on Vendor B and the alternate part service in parallel with operations on Vendor A.
   <!--First find out the alternative part name -->   <sequence>     <assign>       <copy>         <from container="clientRequests"           part="partNumber"/>         <to container="alternatePartRequests"           part="partNo"/>       </copy>     </assign>     <invoke partner="AltPartService"       portType="aps:AlternatePartService"       operation="getAltPart"       inputContainer="alternatePartRequests"       outputContainer="alternatePartResponses"/>     <!-- And ask the VendorB service for the price and       leadtime -->     <assign>       <copy>         <from container="alternatePartResponses"           part="getAltPartReturn"/>         <to container="vendorBPriceRequests"           part="partNumber"/>       </copy>     </assign>     <assign>       <copy>         <from container="clientRequests"           part="quantity"/>         <to container="vendorBPriceRequests"           part="quantity"/>       </copy>     </assign>     <invoke partner="VendorBService"       portType="vbs:VendorBService"       operation="componentListPrice"       inputContainer="vendorBPriceRequests"       outputContainer="vendorBPriceResponses"/>     <assign>       <copy>         <from container="alternatePartResponses"           part="getAltPartReturn"/>         <to container="vendorBLeadtimeRequests"           part="partNumber"/>       </copy>     </assign>     <assign>       <copy>         <from container="clientRequests"           part="quantity"/>         <to container="vendorBLeadtimeRequests"           part="quantity"/>       </copy>     </assign>     <invoke partner="VendorBService"       portType="vbs:VendorBService"       operation="componentLeadtime"       inputContainer="vendorBLeadtimeRequests"       outputContainer="vendorBLeadtimeResponses"/>   </sequence> </flow> 
Figure 13-15. Graphical representation of invoking operations on Vendor B and the alternate part service.

graphics/13fig06.jpg

In Figure 13-16, the workflow script first builds a message in the alternatePartRequests container through a set of assign activities to send to the alternate part service. Once that message has been constructed, it is sent to the alternate part service via an invoke activity, which synchronously receives a response from the alternate part service and stores the message in the alternatePartResponses container.

The getAltPartReturn part of the alternatePartResponses container is then used with the quantity part of the clientRequests container (which contains the original order details) to create messages in the vendorBPriceRequests and vendorBLeadtimeRequests containers. These messages are then used to invoke the componentListPrice and componentLeadtime operations on Vendor B's Web service, and the synchronous responses from those invocations are stored in the vendorBPriceResponses and vendorBLeadtimeResponses containers, respectively, ready for further processing.

Once the price and leadtime requests from Vendor A and Vendor B have been received, the parallel activity in this workflow (the sequence activities enclosed in the flow activity) comes to an end, the processing of the information received from partner Web services can begin, as shown in Figure 13-17.

The BPEL fragment shown in Figure 13-17 deals with procuring parts where cost is the most important metric, that is where the costOverLeadtime part of the clientRequests container contains the value "true" as computed by the outermost switch activity (if this value was set to "false" then the code in Figure 13-20 would instead be executed since it constitutes the otherwise branch of the outermost switch).

Inside the outermost switch, there is a nested switch activity which, depending on whether Vendor A or Vendor B delivered the cheapest quote, executes either the case branch (Vendor B's quote is greater than Vendor A's) or the otherwise branch (where by implication Vendor A's price is the largest).

Figure 13-17. Ordering from either vendor based on cost.
 <switch>   <case condition=     "bpws:getContainerData('clientRequests',       'costOverLeadtime')">     <!-- cost is most important -->     <switch>       <case condition=         "bpws:getContainerData('vendorBPriceResponses',           'componentListPriceReturn') >          bpws:getContainerData('vendorAPriceResponses',           'getPriceReturn') ">         <sequence>           <!-- Vendor A is cheapest -->           <assign>             <copy>               <from container="clientRequests"                 part="partNumber"/>               <to container="vendorAOrders"                 part="partNumber"/>             </copy>           </assign>           <assign>             <copy>               <from container="clientRequests"                 part="quantity"/>               <to container="vendorAOrders"                 part="quantity"/>             </copy>           </assign>           <invoke partner="VendorAService"             portType="vas:VendorAService"             operation="placeOrder"             inputContainer="vendorAOrders"             outputContainer="vendorAOrderNumbers"/>           <assign>             <copy>               <from container="vendorAOrderNumbers"                 part="placeOrderReturn"/>               <to container="clientResponses"                 part="orderNumber"/>             </copy>           </assign>         </sequence>       </case>       <otherwise>         <!-- VendorB is cheapest -->         <sequence>           <assign>             <copy>               <from container="alternatePartResponses"                 part="getAltPartReturn"/>               <to container="vendorBOrders"                 part="partNumber"/>             </copy>           </assign>           <assign>             <copy>               <from container="clientRequests"                 part="quantity"/>               <to container="vendorBOrders"                 part="quantity"/>             </copy>           </assign>           <invoke partner="VendorBService"             portType="vbs:VendorBService"             operation="orderComponent"             inputContainer="vendorBOrders"             outputContainer="vendorBOrderNumbers"/>           <assign>             <copy>               <from container="vendorBOrderNumbers"                 part="orderComponentReturn"/>               <to container="clientResponses"                 part="orderNumber"/>             </copy>           </assign>         </sequence>       </otherwise>     </switch>   </case> 

The logic is straightforward in either case in that whichever service offered the cheapest price has a message created and the appropriate "purchase" operation is invoked. This is high lighted in Figure 13-18 where in the audit trail for a particular instance, we see that Vendor A offered the least expensive option and that accordingly messages are exchanged with Vendor A to procure parts.

Figure 13-18. Graphical representation of ordering from VendorA based on cost.

graphics/13fig03.jpg

The results from this invocation are then stored in the clientResponses container ready to return the results of the procurement to the original client, highlighted in Figure 13-19.

Figure 13-19. Graphical representation of replying with the cheapest procurement offer to the client.

graphics/13fig08.jpg

Our workflow logic also has the ability to procure items based on timeliness criteria. Figure 13-20 shows the continuation of the switch activity that began in Figure 13-17 and is executed where timeliness, rather than cost, is the overriding priority. This part of the workflow is executed when the condition <case condition= "bpws:getContainerData('clientRequests', 'costOverLeadtime')"> evaluates to false i.e., when the message that the client used to initiate the workflow contained the value false in the costOverLeadtime part.

Figure 13-20. Ordering from either vendor based on leadtime.
    <otherwise>     <!-- speed is most important -->     <switch>       <case condition=        "bpws:getContainerData('vendorBLeadtimeResponses',         'componentLeadtimeReturn') >        bpws:getContainerData('vendorALeadtimeResponses',         'getLeadTimeReturn') ">         <!-- Vendor A is fastest -->         <sequence>           <assign>             <copy>               <from container="clientRequests"                 part="partNumber"/>               <to container="vendorAOrders"                 part="partNumber"/>             </copy>           </assign>           <assign>             <copy>               <from container="clientRequests"                 part="quantity"/>               <to container="vendorAOrders"                 part="quantity"/>             </copy>           </assign>           <invoke partner="VendorAService"             portType="vas:VendorAService"             operation="placeOrder"             inputContainer="vendorAOrders"             outputContainer="vendorAOrderNumbers"/>           <assign>             <copy>               <from container="vendorAOrderNumbers"                 part="placeOrderReturn"/>               <to container="clientResponses"                 part="orderNumber"/>             </copy>           </assign>         </sequence>       </case>       <otherwise>         <!-- VendorB is fastest -->         <sequence>           <assign>             <copy>               <from container="alternatePartResponses"                 part="getAltPartReturn"/>               <to container="vendorBOrders"                 part="partNumber"/>             </copy>           </assign>           <assign>             <copy>               <from container="clientRequests"                 part="quantity"/>               <to container="vendorBOrders"                 part="quantity"/>             </copy>           </assign>           <invoke partner="VendorBService"             portType="vbs:VendorBService"             operation="orderComponent"             inputContainer="vendorBOrders"             outputContainer="vendorBOrderNumbers"/>           <assign>             <copy>               <from container="vendorBOrderNumbers"                 part="orderComponentReturn"/>               <to container="clientResponses"                 part="orderNumber"/>             </copy>           </assign>         </sequence>       </otherwise>     </switch>   </otherwise> </switch> 

Figure 13-20 is almost identical to Figure 13-17, with the caveat that orders are placed based on lead time, not price. Again, the logic is straightforward in that whichever service offered the fastest turnaround has a message created and the appropriate "purchase" operation is invoked. The results from this invocation once again stored in the clientResponses container ready to return the results of the procurement to the original client.

The final part of the workflow script is to respond to the original client the invoked the workflow. This is shown in Figure 13-21.

Figure 13-21. Replying with the cheapest or fastest procurement offer to the client.
     <!-- respond output to requestor -->     <reply partner="client"       portType="tns:PartsServicePortType"       operation="orderParts"       container="clientResponses"/>   </sequence> </process> 

The reply activity shown in Figure 13-21 simply takes the message that has been built by the script in the clientResponses container and uses it to respond to the invocation it received through the PartsServicePortType portType from the client, before terminating and closing both the sequence that the reply activity was part of and the process as a whole.

Deploying and Executing BPEL Workflows

Having developed the workflow script that captures the knowledge of the human worker, we must deploy it onto a workflow server to expose its functionality to the network.

For this example, we chose the Collaxa 2.0 (beta 8) to host our scripts. This software provides the ability to develop and execute BPEL workflow scripts, and to expose those workflows as Web services. It is available with a free 30-day license from http://www.collaxa.com/product.welcome.html. There are other toolkits available from other vendors (including IBM's BPWS4J and Microsoft's BizTalk), but it should be noted that while the actual workflow scripts we developed are, by definition, portable between different engines, the configuration and method of deployment will differ.


The deployment configuration that we ultimately want to arrive at is shown in Figure 13-22. In this case, both the workflow script and the WSDL augmentation are hosted by the BPEL engine. The engine takes the workflow script WSDL and creates the additional binding and service entries in that WSDL so it can expose the workflow script as a Web service. It also takes the additional WSDL augmentations supplied at deploy time and imports the WSDL served by the vendor and alternate part services, so the underlying engine can process all the necessary binding information for the remote services.

Figure 13-22. The procurement workflow collaxa deployment.

graphics/13fig09.gif

The key to deploying this configuration is the project file that ties together the workflow script, its (abstract) WSDL definition and the WSDL of the remote services. This is shown in Figure 13-23.

Figure 13-23. A collaxa project configuration file.
 <?xml version="1.0" encoding="UTF-8"?> <bpel-scenario src="/books/2/87/1/html/2/Procurement.bpel Procurement.wsdl">   <properties >     <property name="wsdl-location">       http://localhost:9700/xmllib/VendorAService.wsdl     </property>   </properties>   <properties >     <property name="wsdl-location">       http://localhost:9700/xmllib/VendorBService.wsdl     </property>   </properties>   <properties >     <property name="wsdl-location">       http://localhost:9700/xmllib/AlternatePartService.wsdl     </property>   </properties> </bpel-scenario> 

The key point about Figure 13-23 is that each service name matches exactly the name of a partner in the BPEL workflow script that we presented in Figure 13-22. Additionally, each wsdl-location property references the locally hosted WSDL file which in turn imports the remotely hosted WSDL at each Web service invoked by the workflow.

Figure 13-24. A Collaxa ant script.
 <?xml version="1.0"?> <project name="Procurement" default="main" basedir=".">   <property name="deploy" value="true"/>   <property name="rev" value="1.0"/>   <property name="out"     value="${CXHome}/server-default/classes"/>   <target name="main">     <copy file="${basedir}/AlternatePartService.wsdl"       todir="${CXHome}/server-default/xmllib/"/>     <copy file="${basedir}/VendorAService.wsdl"       todir="${CXHome}/server-default/xmllib/"/>     <copy file="${basedir}/VendorBService.wsdl"       todir="${CXHome}/server-default/xmllib/"/>     <bpelc input="${basedir}/Procurement.xml" rev="${rev}"       sourcepath="${basedir}" deploy="${deploy}"/>   </target> </project> 

The final stage in deploying this workflow is to execute the compiler/deployment tool on the project file, and to deploy the additional WSDL onto the workflow engine server where it can be accessed by workflow instances. To simplify matters, the Collaxa 2.0 engine comes with a variant of the popular Apache Ant build scripting language and, thus, the entire deployment can be achieved by issuing the command cxant in the same directory as the Ant script shown in Figure 13-24. This is, in fact, a standard Ant script for which the cxant program automatically sets the Collaxa-specific variables (like CXHome which is the location of the Collaxa installation). Other than that, the script uses the standard Ant copy task to move the WSDL files into the server's WSDL directory and the Collaxa bpelc task that invokes the toolkit's BPEL compilation and deployment utility.

Having successfully deployed the workflow script, we are now able to execute it. The easiest way to do this is to simply use the Collaxa 2.0 console to execute an instance of the workflow, as shown in Figure 13-25. The form presented to the user allows the construction of the initial message to the workflow instance and is, therefore, composed from a part number (integer), a quantity (integer), and a preference for leadtime or cost (Boolean value). Once the "initiate flow" button is clicked, the code behind the Web page sends these values as an OrderPartsRequest to the Web service endpoint exposed by the engine and execution proceeds as shown in Figure 13-26.

Figure 13-25. Executing a workflow instance.

graphics/13fig10.jpg

Figure 13-26. Executing the procurement workflow in the Collaxa 2.0 environment.

graphics/13fig11.gif

However, it is generally not the case that we would want this workflow to be executed from the server console. So instead of using the console to invoke the service on our behalf, it is possible to access the workflow Web service programmatically through its WSDL interface. To do this, we obtain the address of the (full) WSDL for the service from the console, and use the client-side tools from a Web services toolkit to consume that WSDL and create appropriate proxies for use within other applications and services.



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