Dynamic Split-Join in OSB

As part of implementing a web service it is often necessary to delegate portions of the work 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 flavors:

  • Static: In this 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 this 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.
    Dynamic Split-Join in OSB

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

Note

An alternative way of implementing the previous scenario is to use a FlowN (BPEL 1.1) or a parallel ForEach (BPEL 2.0) activity within a BPEL process. As OSB is stateless, it has less overhead and, therefore, will be more performant. However, another key consideration is what happens if something goes wrong?

In the case of our example, as we are not modifying any data, our error handling is relatively straightforward. But, if we were using the Split-Join to modify data in the target system, for example splitting out an order into individual line items which are then ordered separately; then if an error occurred we may want to undo all the successfully generated line item orders.

It may be tempting to try and do all this within an XA transaction. However, this has the potential to create large distributed transactions, with significant impact on performance and scalability.

In this scenario (that is where we are modifying state) a better approach would be to implement this pattern in BPEL and use compensation for error handling.

In summary, where the Split-Join is not a modifying state, it is safe and more performant to use OSB. But, in cases where the state of the backend system is being modified you should implement this pattern in BPEL.

Getting ready

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 book should cost.

If you wish to follow along exactly with these instructions open the BookStoreApp (included with the code samples for the book) in Eclipse. This contains the required schema and WSDL files, as well as a mock implementation of the Book service.

How to do it...

  1. Right-click on the BookStoreApp project and select New | Split-Join from the context menu.
  2. Enter a descriptive filename (for example, getTotalPriceSplitJoin) and then click on Next.
  3. Expand the project structure to select the parent operation used to invoke the Split-Join (for example, BookStoreService_1.0.wsdl | BookStoreServiceBinding | operation: getTotalPrice) and then click on Finish.
    How to do it...

    A new Split-Join flow will appear in the main editing window.

  4. Select the root node and expand its properties by clicking on the small triangle on its left. Select the request variable and click on Edit....
    How to do it...
  5. Rename the variable to match the parent operation (for example, getTotalPrice). This will help prevent ambiguity later on.
  6. Repeat steps 4 and 5 to rename the response variable (for example, getTotalPriceResponse).
  7. Drag an Assign action from the Design Palette, to between the Receive and Reply nodes.
  8. Label the new scope as Initialisation and the Assign action as Assign output variable.
  9. Click on the new Assign action. In the Properties tab (shown in the following screenshot), select the variable as the payload of the parent operation's response (for example, getTotalPriceResponse.payload).
  10. Next, click on the <Expression> link. Provide an XML similar to the following and then click on OK.
    <stor:priceCheckResponse
      xmlns:stor="http://rubiconred.com/ckbk/svc/BookStore">
      <stor:totalPrice>0</stor:totalPrice>
    </stor:priceCheckResponse>

    Note, that in the previous example the aggregate total has been initialized to 0.

  11. Drag a For Each construct from the Design Palette to just below the Initialisation scope.
  12. Click on the new For Each construct. In the Properties tab, set the Counter Variable Name field to counter and the starting value to 1. Click on the ellipses next to Final Counter Value to launch the expression editor.
    How to do it...
  13. Select the XPath Functions tab, and drag the count function out into the Expression text area.
  14. Click on the Variable Structures tab and expand the request structure to find the recurring element (for example, bookOrder) on which the split should be based. Drag it out to replace the place-holder $arg-nodeset and then click on OK to complete the expression.
    How to do it...
  15. Drag an Invoke Service action into the Scope within the ForEach loop. Label it as per the child service and operation you intend to loop over (for example, Book.priceCheck).
  16. In the Properties tab (shown in the following screenshot), select the Operation category. Click on Browse, select the child operation (for example, BookService.proxy | priceCheck), and click on OK.
    How to do it...
  17. Select the Input Variable category in the Properties tab. From the Message Variable drop-down list select Create Message Variable.... Provide the name of the child operation (in our example, priceCheck) as the name and then click on OK.
    How to do it...
  18. Use the same method to create and set the output variable (for example, as priceCheckResponse).
  19. Drag an Assign action to the start of the Loop Scope and label it as Extract Individual Request.
  20. Select the Assign action. In the Properties tab, set the variable as the request payload of the child operation (for example, priceCheck.payload) and then click on the <Expression> link.
  21. We will use the XQuery priceCheck.xq to generate the input request to our call-out to the Book.priceCheck service. We will need to pass in the ISBN of the book using the $counter index defined earlier. For example:
    $getTotalPrice.payload/book:bookOrderList/book:bookOrder[xs:integer($counter)]/book:book/book:isbn
  22. Following the Invoke Service action, apply any aggregate logic. For our Book Store example, we would add a Replace action with the following properties:

    Field

    Value

    XPath:

    ./totalPrice

    Variable

    getTotalPriceResponse.payload

    Expression

    xs:float($getTotalPriceResponse.payload/bind:totalPrice) +

    (xs:float($priceCheckResponse.payload/ns1:price) *

    $getTotalPrice.payload/book:bookOrderList/book:bookOrder

    [xs:integer($counter)]/book:quantity)

     

    Select Replace node contents

  23. Save your progress by selecting File | Save from the menu.
  24. 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 on OK.

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

How it works…

Refer to the following, more completely labelled version of the Split-Join message flow for an end-to-end, annotated view of the final solution:

How it works…

Procedurally, the pseudocode for the BookStore example might look to be (just going by the annotations) as follows:

Operation getTotalPrice( book_list ):
  totalPrice := 0
  for each order in book_list
  loop
    total_price := total_price + 
      Book.priceCheck(order.isbn ) * order.qty
  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 that 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 bookOrder nodes and then assigning each scope a different $counter variable integer between 1 and that total count. So, a more accurate representation of the pseudocode 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(order[counter].isbn ) * 
      order[counter].qty )
  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…

This recipe 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.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.125.205