Blog

James Goddard

Dynamic Split-Join in OSB

by James Goddard
in OSB
31 Jan 2012  |  1 Comment

As part of implementing a web service it is often necessary to delegate to a number of independent subtasks. For a synchronous service, carrying out these tasks sequentially may take an unacceptable amount of time causing the client to time out waiting on the service. Therefore the preferred approach is to process all independent tasks in parallel and consolidate the results.

This pattern is referred to as a “Split-Join” and comes in two flavours:

  • “Static” in which the subtasks are always the same.  For example, in planning a holiday one needs to book both a flight and accommodation, each of which represents an independent subtask which may be completed in parallel.
  • “Dynamic” in which there are a variable number of subtasks, to be determined at runtime.  For example, to complete an internet shopping order a bookstore must query each book before confirming the total price, but has no way of knowing how many different items will be required prior to reviewing the order.

This recipe will guide you through a sample implementation of the second example using Oracle Service Bus.

Example of a Dynamic Split-Join

Getting Ready...

Oracle Enterprise Pack for Eclipse

Although it is possible to develop for the Oracle Service Bus using only the Web Console interface, for a fully featured IDE (including support for version control, source code editing plug-ins, lower latency and independence of development environments) it is generally preferable to use the OSB Workshop perspective of the OEPE.
This recipe assumes the use of an existing OSB Configuration project within the OSB Workshop for development so ensure that you have installed and familiarised yourself with it prior to beginning.

Target Operation

Prior to beginning this recipe, you will need to prepare the target WSDL operation which will be invoked to process individual items.  In the example, this will be the priceCheck operation of the Book service, which determines how much each should cost.

Schema and WSDL

You should always finalise the service contract prior to beginning development.
If you wish to follow along exactly with these instructions you will require a copy of the schema and WSDL files used in the example. You can obtain a copy of these, as well as a mock implementation of the Book service from the following link: BookStore.zip

How to do it...

Creating the Split-Join

  • If it doesn’t exist already add an OSB Project (in this example, BookStore) to your OSB Configuration Project and then create a sub folder within called SplitJoin.
  • Right click on this new subfolder and select New -> Split-Join from the context menu.New Split-Join
  • Enter a descriptive filename (e.g., getTotalPriceSplitJoin) and then click Next.
  • Expand the project structure to select the “parent” operation used to invoke the split-join (e.g., BookStore.wsdl -> BookStoreBinding -> operation: getTotalPrice) and then click Finish.Specify parent operation for Split-Join

A new flow will appear in the main editing window.

Editing the flow

  • Select the root node and expand its properties by clicking on the small triangle on its left.  Select the request variable and click Edit....Edit request variable
  • Rename the variable to match the parent operation (e.g., getTotalPrice).  This will help prevent ambiguity later on.
  • Similarly, rename the response variable (to e.g., getTotalPriceResponse).
  • Drag an Assign action over from the Design Palette, to between the Receive and Reply nodes.New Assign action
  • Label the new scope as Initialisation and the Assign Action as Assign output variable.
  • Click on the new Assign action.  In the properties tab below, select the Variable as the payload of the parent operation’s response (e.g., getTotalPriceResponse.payload).
  • Next, click on the <Expression> link.  Provide XML similar to the following and then click OK.
<stor:getTotalPriceResponse xmlns:stor="http://www.example.org/BookStore/">
<totalPrice>0</totalPrice>
</stor:getTotalPriceResponse>

Note in the above example the aggregate total has been initialized to 0.

  • Drag a For Each construct from the Design Palette to just below the Initialisation scope.For Each
  • Click on the new For Each construct.  In the Properties tab, set the Counter Variable Name to counter and the starting value to 1.  Click on the ellipses next to Final Counter Value to launch the expression editor.For Each properties
  • Select the XPath Functions tab, and drag the count function out into the Expression text area.Count
  • Click on the Variable Structures tab and expand the request structure to find the recurring element (e.g., the book id) on which the split should be based.  Drag it out to replace the place-holder $arg-nodeset and then click OK to complete the expression.count id variable
  • Drag an Invoke Service action into the loop’s Scope.  Label it per the “child” service and operation you intend to loop over (e.g., Book.priceCheck).Invoke Service
  • In the Properties tab below, select the Operation category.  Click Browse, select the child operation (e.g., Book.proxy -> priceCheck), and click OK.Select child operation
  • Select the Input Variable category in the Properties tab.  From the Message Variable dropdown select Create Message Variable....  Provide the name of the child operation (in our example, priceCheck) as the Name and then click OK.Child input variable
  • Use the same method to create and set the Output Variable (e.g., as priceCheckResponse).
  • Drag an Assign Action to the start of the Loop Scope and label it as Extract Individual Request.
  • Select the Assign Action.  In the Properties tab, set the Variable as the request payload of the child operation (e.g., priceCheck.payload) and then click the <Expression> link.
  • Use the expression editor to generate each individual request using the $counter index defined earlier.  For example:
<book:priceCheck xmlns:book="http://www.example.org/Book/">
{$getTotalPrice.parameters/id[xs:integer($counter)]}
</book:priceCheck>
  • Following the Invoke Service Action, apply any aggregate logic.  For our BookStore example, we would add a Replace Action with the following properties:
    • XPath: ./totalPrice

    • Variable: getTotalPriceResponse.parameters

    • Expression: xs:float($getTotalPriceResponse.parameters/totalPrice) + xs:float($priceCheckResponse.parameters/price)

    • Replace node contents
  • Save your progress by select File -> Save from the menu.

Wrapping the service

Before the Split-Join can be used in a proxy service, it must first be encapsulated in a standard OSB Business Service.

  • In the Project Explorer on the left, right-click on the Split-Join file and then select Oracle Service Bus -> Generate Business Service.
  • Accept the default name and location, and click OK.

The business service is now ready for use in any OSB Proxy Service.  Test it out.

How it works...

Full flow

Refer to the more completely labelled version of the Split-Join message flow above for an end-to-end, annotated view of the final solution.  Procedurally, the pseudo-code for the BookStore example might look to be (just going by the annotations) as follows:

Operation getTotalPrice( book_list ):
totalPrice := 0
for each id in book_list
loop
total_price := total_price + Book.priceCheck( id )
end loop
return total_price

The key difference is that the For Each section has a property called “Parallel” set by default to yes (Note, if desired, this can be set to no to force sequential execution). This instructs Oracle Service Bus to execute all (or as many as it has threads) iterations of the Loop scope within the For Each statement concurrently.

Readers paying close attention will also have noticed that the For Each block does not actually iterate over the book IDs directly; rather the OSB determines the number of Loop scopes simply by counting the number of id nodes and then assigning each scope a different $counter variable integer between 1 and that total count. So a more accurate representation of the pseudo code would be as follows:

Operation getTotalPrice( book_list ):
totalPrice := 0
for counter in 1 .. size(book_list)
thread concurrently
total_price := total_price + Book.priceCheck( book_list[counter].id )
end thread
return total_price

Performing this addition in parallel allows the BookStore service to compute the total much faster, dividing the total time of priceChecks by the number of concurrent threads.

There's more...

The recipe above represents a reasonably standard, cookie-cutter implementation of how one would use the Split-Join feature of Oracle Service Bus to iterate over a dynamic sequence of identical elements in a list.  It should be enough to get you started on any similar problem; however it only scratches the surface of the possibilities for what can be accomplished with a Split-Join message flow.

Fault Handling

Without appropriate Error Handling logic, the first fault thrown by a service invocation within any one of the Split-Join’s threads will re-raise in the Split-Join and halt the entire message flow.
In order to prevent this, “Catch” clauses need to be added to the scope of each thread as follows:

  • Right-click on the loop’s scope and select Add Catch.Add Catch
  • Select the new Catch block, label it with the name of the Fault you wish to catch and then review the Properties tab below.
  • Click on <Soap Fault Variable Name> and assign any name you like (the default is simply soapFault which should be fine).
  • Select Define Fault and enter the Fault Name and Namespace of the fault you expect to catch.
  • Drag in a new Scope below the Catch and add any mitigation Actions as necessary to resolve the Fault.  It may be appropriate to do nothing, simply log the error, or perhaps aggregate a default value into the total  All variables available within the normal scope are also available to you within the Catch block.
  • Repeat the steps above for each expected Soap Fault.
  • Optionally, add a Catch All clause to capture any unexpected Faults.

Other aggregation logic

Rather than simply summing up numerical values, you can aggregate the results of service calls any way you like.  A common example is appending the results to a dynamic sequence using an Insert Action.

More service calls

Note that you are not limited to a single Invoke Service Action. Multiple “child” operations may be invoked sequentially or in parallel.
In fact the premise of a “Static” Split-Join is that instead of using a For Each loop, you would use an explicit Parallel construct (see Flow Control in the Design Palette) and drop a different Invoke Service Action into each lane.
Any combination of flow constructs desired can be layered to create complex concurrent processing systems within a single Split-Join Message Flow.

Conflicts

With any software system involving multi-threading, there is always a possibility of Deadlocks or Conflicts. Although variables within a Split-join message flow are protected from these scenarios, Oracle Service Bus does not provide any built-in mitigation tools for external systems.
It is outside the scope of this discussion to prescribe how one might resolve concurrent update issues in external systems; however designers and developers should always be aware when there is such a possibility and take appropriate action.

 
Comments (1)

To the point.

14 Jul 2012, Sanjay

RSS Feed Subscribe to the Rubicon Red Blog

 
Leave A Comment

Name *

Email * (will not be published)

Website

Comment *

Please type the characters you see below

Visual verification
Hard to read? Click here for a new code.