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:
This recipe will guide you through a sample implementation of the second example using Oracle Service Bus.
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.
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.
getTotalPriceSplitJoin
) and then click on Next.A new Split-Join flow will appear in the main editing window.
getTotalPrice
). This will help prevent ambiguity later on.getTotalPriceResponse
).Initialisation
and the Assign action as Assign output variable
.getTotalPriceResponse.payload
).<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
.
counter
and the starting value to 1
. Click on the ellipses next to Final Counter Value to launch the expression editor.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.Book.priceCheck
).priceCheck
) as the name and then click on OK.priceCheckResponse
).Extract Individual Request
.priceCheck.payload
) and then click on the <Expression> link.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
Field |
Value |
---|---|
XPath: |
|
Variable |
getTotalPriceResponse.payload |
Expression |
|
Select Replace node contents |
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.
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:
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.
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.
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.
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.
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.