Articles
|
SOA Best Practices: The BPEL Cookbook |
|
Making BPEL Processes Dynamic
Learn how to achieve dynamic binding by manipulating endpoint references at runtime.
Web services and service-oriented architecture (SOA) allow business processes to be easily extended through interaction with other business processes and applications. BPEL processes define this interaction through partner links, which define the interface (messages and operations), transport protocol, and most important, the location of each service to be used. In most basic process designs, partner links are static; they refer to a single external process selected by the developer at design time. This approach is appropriate for highly targeted or constrained systems. However, in larger systems business processes are more complex. They interact with multiple external services and define multiple partner links, and some of these partner links might not be known at design time. As a result, all potential callouts and logic for deciding which partner links to use must be built inside the business process itself—unnecessarily complicating that process. Furthermore, as additional partner links are added, the resulting process grows more and more unwieldy, as any changes to the partner links require modification of the entire business process. Fortunately, the BPEL language supports the concept of dynamic binding of partner links. Dynamic binding allows the developer to add new services through configuration or run-time inputs. This approach eliminates the need to anticipate and manage all parent-child relationships at design time.In this installment of The BPEL Cookbook, I will outline the effective strategy of shielding BPEL processes from partner relationship changes by letting the system manage partner links dynamically at runtime. I will also explain how to invoke multiple BPEL processes dynamically, either sequentially or in parallel. Dynamic Binding Overview Similar to object-oriented analysis and design in the traditional programming world, dynamic binding of partner links allows for modularization of code and run-time binding between processes. The benefits of this approach include:
The use of dynamic processing can also have a significant advantage in organizations where there is a division of responsibility between development versus configuration and maintenance of high-level process flow. The development team can be responsible for understanding the details of the BPEL implementation and creation of the process components, and then domain experts such as business analysts or support teams can assemble these components into individual workflows without needing detailed knowledge of partner links, namespaces, WSDL, XPATH, and other technical details. Building Dynamic BPEL Processes As I explained previously, partner links describe interfaces to business processes or other services. BPEL processes call these external services using information stored in the partner links. The partner link defines operations and message types that make up the interface to the service using portTypes in WSDL. As illustrated in Figure 1, portTypes also indirectly define the transport used to communicate with the service (bindings) and the location of the service (services).
Figure 1 portTypes in action Note: When you first load and deploy the Dynamic Partner Link sample, do not make any modifications to the code in the Oracle JDeveloper visual designer--just deploy it as-is. If you were to make and save such changes, JDeveloper would reformat the BPEL code based on its standard XML layout by introducing newline characters. JDeveloper modifies the <Address> and <ServiceName> tags inside the <EndpointReference> data, adding a newline character before the </Address> and </ServiceName> close tags. The newline appended to the data inside these elements breaks the binding. If necessary, you can correct the problem by removing the newline character before the close tag on the service and address. Later I will present an alternative method for populating the endpoint reference that is not affected by the formatting applied by JDeveloper. When you launch the sample from the console, you are asked for the standard loan application data for the loan flow demo (SSN, email, and so on) along with a "provider" field. Specify one of the following values in the provider string: united, american, or star. Run the sample with each of them to see how it works. The process will dynamically make calls to appropriate loan provider. It is also interesting to try it with some other value in the provider string, as well as with no value at all. To understand how this dynamic process works, it is first necessary to analyze the DynamicPartnerLink.bpel file. The first interesting thing in this file is the loan service partner link:
<partnerLink name="LoanService" partnerLinkType="
services:LoanService"
myRole="LoanServiceRequester" partnerRole="LoanServiceProvider"/>
Rather than defining a specific loan service (like
UnitedLoan), a generic loan service name and type is specified (
services:LoanService). The
LoanService partner link is defined in the LoanService.wsdl file; this file is imported by adding it to the bpel.xml file in the <partnerLinkBindings> section as shown below:
<partnerLinkBinding name="LoanService"> <property name="wsdlLocation">LoanService.wsdl</property> </partnerLinkBinding>You'll observe in the LoanService.wsdl file that each of the available loan providers is defined as a <service> within this single WSDL file, as shown below. <service name="StarLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/StarLoan"/> </port> </service> <service name="UnitedLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/UnitedLoan"/> </port> </service> <service name="AmericanLoan"> <port name="LoanServicePort" binding="tns:LoanServiceBinding"> <soap:address location="http://localhost:9700/orabpel/default/AmericanLoan"/> </port> </service>It is important to understand that there is no "real" service called "LoanService." Rather, LoanService is a template from which you select one of the real loan provider services (UnitedLoan, AmericanLoan, StarLoan). This approach works as long as the real services all support the same interface (same data types, messages, roles, ports, and partner link types) as that defined in the template WSDL. It is important to define this template interface carefully because a change here can affect many processes down the line. The LoanService.wsdl file defines all the service options, which the parent process can elect to call dynamically. This model requires a redeployment of the WSDL file as each new service is added. This approach represents a remarkable improvement over modifying the parent process to include new partner links and routing logic for each new service. (Later you will see how to disassociate the service endpoints from the WSDL file as well.) Returning to the DynamicPartnerlink.bpel file, the next feature we want to look at is the partnerReference variable:<variable name="partnerReference" element="wsa:EndpointReference"/>This variable is of the type EndpointReference. It has a namespace wsa: defined at the top of the BPEL file as xmlns:wsa="http://schemas.xmlsoap.org/ws/2003/03/addressing"The WS-Addressing standard provides the schema for the EndpointReference type. You can <assign> variables of this type to a partner link in order to modify the address and service information, thus providing the ability to modify the partner link at runtime. The DynamicPartnerLink process basically consists of a switch. It inspects the "provider" string passed in by the caller. Then, it assigns an EndpointReference xml data structure to the partnerReference variable containing the information relevant to the service that you're requesting. After the switch, the partnerReference variable is assigned to the LoanService partner link and the partner link is invoked. Here's how to accomplish this task when the input string (the service provider) is "united":
<assign>
<copy>
<from>
<EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing">
<Address>http://localhost:9700/orabpel/default/UnitedLoan</Address>
<ServiceName
xmlns:ns1="http://services.otn.com">ns1:UnitedLoan</ServiceName>
</EndpointReference>
</from>
<to variable="partnerReference"/>
</copy>
</assign>
Everything between the <from> and </from> tags is literal XML that you're assigning to the
partnerReference variable. This data will override the address and service specified in the
LoanService partner link when you assign the
partnerReference variable to that link.
Now that you've explored the use of the LoanService partner link and LoanService.wsdl to invoke services selected at run time, you can move on to building a dynamic process. Creating a Dynamic BPEL Process Now, let's create a dynamic BPEL process from scratch. 1. Create a new BPEL project.
Copy the LoanService.wsdl file from the DynamicPartnerLink sample into the working directory of MyDL project ([BPEL_HOME]\integration\jdev\jdev\mywork\Workspace1\MyDL by default). (This approach will save you the time and trouble of creating your own dynamic WSDL and subprocess services.) Then right-click the MyDL project in the Applications Navigator and select Add to Project... Pick the LoanService.wsdl file from the directory and click on OK. The LoanService.wsdl file has not yet been added to the bpel.xml file. You won't do that until much later in the process when you implement the EndpointReference variable. 3. Create the loan service partner link template.
Figure 2 "Create Partner Link" dialog box Drag one each of the invoke and receive actions from the component palette into your process (between the receiveInput and callbackClient actions). Drag one of the arrows from invoke to the DynamicLoanService partner link and create the input variable. Do the same for receive. Variables should be called loanInput and loanOutput. 5. Configure input data loanInput. Typically you would modify the MyDL.wsdl file to get the loan input data from the user. For the sake of simplicity here you'll just hard-code an assign to populate the loanInput variable. Place the assign after the receiveInput action and create a copy rule that puts the value "123456789" (this is a string, not a number, so don't forget to quote it) into the SSN element of loanInput as follows: <assign name="PopulateSSN"> <copy> <from expression="'123456789'"/> <to variable="loanInput" part="payload" query="/ns2:loanApplication/ns2:SSN"/> </copy> </assign>6. Create the partnerReference variable. In the Structure window, expand the Variables tree, then Process, and select the Variables item (see Figure 3).
Figure 3 Expanding the "Variables" tree
Figure 4 Selecting "EndpointReference" Create another assign before the DynamicLoanService invoke. Use this assign to setup the partnerReference variable. Initially you'll hard-code it to the UnitedLoan service but you'll make it dynamic in the next section. Here, you can avoid the issue encountered in the DynamicPartnerLink sample by reformatting the EndpointReference xml data. Create a copy rule that populates the partnerReference variable with this empty EndpointReference:
<EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:ns1="http://services.otn.com">
<Address/>
<ServiceName/>
</EndpointReference>
In the "from" block of copy rule, be sure to select the type "XML Fragment" before entering the information above. You have to make this copy in order to establish the namespace information for the
partnerReference because the
partnerReference variable is treated as a separate XML document when it is copied over to the DynamicLoanService partner link. Otherwise, expect to get a null pointer exception when you try to assign the
partnerReference variable to the partner link.
Now you can populate the
ServiceName and
Address elements of the
partnerReference variable with standard copy rules. Be sure to specify the same namespace for the service (
ns1) as what is defined in your blank endpoint reference. The <assign> should look like this:
<assign name="SetupPartnerlink">
<copy>
<from>
<EndpointReference
xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:ns1="http://services.otn.com">
<Address/>
<ServiceName/>
</EndpointReference>
</from>
<to variable="partnerReference"/>
</copy>
<copy>
<from expression="'ns1:UnitedLoan'"/>
<to variable="partnerReference" query="/ns3:EndpointReference/ns3:ServiceName"/>
</copy>
<copy>
<from expression="'http://localhost:9700/orabpel/default/UnitedLoan'"/>
<to variable="partnerReference" query="/ns3:EndpointReference/ns3:Address"/>
</copy>
</assign>
Also note that it is not until this point, when you use the
partnerReference variable, that the LoanService.wsdl file is added to your bpel.xml file (so that the
EndpointReference schema can be accessed).
8. Copy the
partnerReference variable into the DynamicLoanService partner link.
Create the new <assign> between the SetupPartnerlink action and the <invoke> for the DynamicLoanService. Create a new copy rule and set it up as shown in Figure 5.
Figure 5 Creating a new copy rule
Figure 6 The new BPEL process
<assign name="SetupPartnerlink">
<copy>
<from>
<EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:ns1="http://services.otn.com">
<ServiceName/>
</EndpointReference>
</from>
<to variable="partnerReference"/>
</copy>
<copy>
<from expression="'ns1:UnitedLoan'"/>
<to variable="partnerReference" query="/ns3:EndpointReference/ns3:ServiceName"/>
</copy>
</assign>
Now, deploy and run the MyDL process again. Despite the lack of an address, it still makes a successful call to the UnitedLoan subprocess. This action can be verified by looking at the process tree view in the BPEL console. The result is that it is possible to modify the behavior of dynamic processes by simply deploying a new WSDL, which will contain modified address information for the service. The tradeoff is that in order to add new services the WSDL will need to be modified and redeployed.
WSDL-independent services. In some cases, in addition to having many services to manage, you may also have a situation where the service addresses change frequently or where you want to avoid frequent updates to the WSDL file. Allowing the process to specify the address of the endpoint reference at runtime can solve this problem.
Go back to the previous version of the MyDL.bpel file that has the address manipulation copy rules. Instead of removing the address information, remove the service information both from the template XML fragment and the
ServiceName copy rule. The <assign> should now look like this:
<assign name="SetupPartnerlink">
<copy>
<from>
<EndpointReference xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:ns1="http://services.otn.com">
<Address/>
</EndpointReference>
</from>
<to variable="partnerReference"/>
</copy>
<copy>
<from expression="'http://localhost:9700/orabpel/default/UnitedLoan'"/>
<to variable="partnerReference" query="/ns3:EndpointReference/ns3:Address"/>
</copy>
</assign>
When you run the sample, the process makes the correct call to the UnitedLoan service, even though the service name is not specified. You can create the DynamicPartnerLink WSDL with only a single dummy service and call out to other services not listed in the WSDL as long as the addresses of those services is known at runtime. If you don't specify an address for some reason, it will use the address of the default service in the WSDL. Therefore, it may be a good idea to have that service point to a real BPEL process, possibly one that logs an error or sends a notification.
One application of this technique is in building a framework for exception handling. If you have more than one available address where a given service is available (such as a local server and a remote redundant server), you can roll-over to the secondary address when a call to the primary fails by using an exception handler to override the address information in the endpoint reference and retry the invocation of the service.
Invoking multiple dynamic processes. In some cases, a single data set may need to be passed to multiple sub-processes either in sequence or in parallel. You can use one or more while loops to implement this type of behavior.
Let's look at a quick example. A loan service provider's availability could be based on the day of the week. This information is stored in the database. A loan request arrives on Monday, and when the database is queried, it returns with a list of available Loan Service Providers (United and Star). To process the loan, the United and Star subprocesses will need to be called, either sequentially or in parallel. The database query returns the following result:
<dbOutput>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="response-
headers">null</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="DynamiclinksCollection">
<n:DynamiclinksCollection
xmlns:n=http://xmlns.oracle.com/pcbpel/adapter/db/top/MyDynamicLink
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Dynamiclinks>
<address>http://localhost:9700/orabpel/default/UnitedLoan</address>
<day>monday</day>
<uid>1</uid>
</Dynamiclinks>
<Dynamiclinks>
<address>http://localhost:9700/orabpel/default/StarLoan</address>
<day>united</day>
<uid>4</uid>
</Dynamiclinks>
</n:DynamiclinksCollection>
</part>
</dbOutput>
In order to call these in sequence, you would create a
while loop. This
while loop gets the address from the collection and performs a dynamic invoke/receive on each service.
<!-- first setup the counter variable "i" -->
<assign name="CounterReset">
<copy>
<from expression="1"/>
<to variable="i"/>
</copy>
</assign>
<!-- while loop goes until all link collection notes are done -->
<while name="LoanLoop" condition="bpws:getVariableData('i') <=
count(bpws:getVariableData('dbOutput','DynamiclinksCollection',
'/ns3:Dynamiclin ksCollection/Dynamiclinks'))">
<sequence name="Sequence_1">
<!-- reset the endpoint with the usual xml fragment -->
<assign name="ClearEndpoint">
<copy>
<from>
<EndpointReference
xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing"
xmlns:ns1="http://services.otn.com">
<Address/>
</EndpointReference>
</from>
<to variable="partnerReference"/>
</copy>
</assign>
<!-- set the address in the endpoint variable
based on the current node -->
<assign name="SetEndpoint">
<copy>
<from variable="dbOutput" part="DynamiclinksCollection"
query="/ns3:DynamiclinksCollection/Dynamiclinks
[number(bpws:getVariableData('i'))]/address"/>
<to variable="partnerReference"
query="/wsa:EndpointReference/wsa:Address"/>
</copy>
</assign>
<!-- copy the endpoint variable into the partner link -->
<assign name="DoPartnerlink">
<copy>
<from variable="partnerReference"/>
<to partnerLink="LoanService"/>
</copy>
</assign>
<!-- invoke the partner link -->
<invoke name="Invoke_2" partnerLink="LoanService"
portType="ns2:LoanService" operation="initiate"
inputVariable="loanInput"/>
<!-- be sure to increment your counter or you have an infinite loop -->
<assign name="CounterIncrement">
<copy>
<from expression="bpws:getVariableData('i')+1"/>
<to variable="i"/>
</copy>
</assign>
</sequence>
</while>
In the above example you're calling asynchronous services. It is possible to call them in parallel by removing the <receive> from the <invoke> while loop and giving it a while loop of its own. Responses from each of the callout processes will queue up until a <receive> is run to catch them. The receive task will collect the responses in the order that they return. This approach will prevent a response from a short-running task from being queued up behind the response from a long-running task.
It is not recommended to proceed out of the <receive>
while loop until all of asynchronous responses have been collected.
Conclusion
As you've seen here, by binding dynamically using endpoint referencing, BPEL processes can become more agile and adapt to changing business conditions quickly. By decoupling business logic from partner addressing, you can make processes much more adaptive and portable.
Sean Carey is a Software Architect at
SPS Commerce, a leader in hosted EDI. Sean has over seven years of experience in mission-critical e-commerce implementations and 15 years of industry experience in software design.
Send us your comments
|