One of the key advantages of an Enterprise Service Bus, as well as Service Oriented Architecture in general is the quality of Agility, that is, the ability to easily compose new orchestrations of web service operations. In this recipe, we will consider a scenario in which a standard service contract might be implemented by multiple providers, the selection of which we want to dynamically configure at runtime.
For example, let us suppose that we are running an online bookstore and wish to automate stock order requests to multiple publishers. A stock order includes the name of the publisher, details of the book, and the quantity to order:
<stockOrder> <publisher>ACME</publisher> <bookOrder> <book> <isbn>1234567890123</isbn> <title>Barry Potter</title> … </book> <quantity>Skylight vampires</quantity> </bookOrder> </stockOrder>
This service could be fulfilled by various publishers and new publishers might be joining our network all the time. Ideally, rather than needing to re-code and publish our routing logic every time a new publisher is added, we would like to be able to keep our Stock Order service running with no outage and simply update the routing rules using a configuration file, such as the following:
<routing> <rule> <key>ACME</key> <serviceName>PublisherApp/PublisherACME</serviceName> </rule> <rule> <key>Packt</key> <serviceName>PublisherApp/PublisherPackt</serviceName> </rule> <rule> <key>Skynet</key> <serviceName>PublisherApp/PublisherSkynet</serviceName> </rule> </routing>
Each of these serviceNames
are OSB proxy services, wrapping a particular publisher's online stock ordering service. The idea is that when we want to add a new publisher, we implement a new proxy service implementing the previous service contract and add a <rule>
element to the configuration file referencing it.
This recipe will demonstrate how this dynamic routing can be implemented in OSB.
In order to use this design pattern, you will need to define a standard WSDL to be implemented by each of your routing destinations (publishers in our example).
For our purposes, we have defined the WSDL Publisher_Service_1.0.wsdl, which defines the operation submitBookOrder
.
We have provided three basic implementations of this service, as defined in the following table:
Publisher |
Proxy service |
---|---|
ACME |
PublisherApp/PublisherACME |
Packt |
PublisherApp/PublisherPackt |
Skynet |
PublisherApp/PublisherSkynet |
These implementations are defined in the PublisherApp OSB project which is included in the sample for the book. You will need to open this sample in OEPE and deploy to your instance of OSB.
PublisherService
and select Publisher_Service_1.0.wsdl as the WSDL.xquery version "1.0" encoding "Cp1252"; (:: pragma type="xs:anyType" ::) declare namespace xf = "http://tempuri.org/PublisherApp/dynamic-routing-rules/"; declare function xf:dynamic-routing-rules() as element(*) { <routing> <!-- dynamic-routing-rules.xq --> <rule> <key>ACME</key> <serviceName>PublisherApp/PublisherACME</serviceName> </rule> <rule> <key>Packt</key> <serviceName>PublisherApp/PublisherPackt</serviceName> </rule> <rule> <key>Skynet</key> <serviceName>PublisherApp/PublisherSkynet</serviceName> </rule> </routing> }; xf:dynamic-routing-rules()
Note that in our example, key
is the key field we will use to determine which routing rule to apply while serviceName
indicates the exact path (within OSB) of the corresponding destination proxy service.
Save this file within your OSB project as dynamic-routing-rules.xq
.
LookupDestination
. Within the new stage, add two Assign actions, as shown in the following screenshot:routingRules
and select dynamic-routing-rules.xq
as the <Expression>.destination
and use the following XML fragment as your expression:<ctx:route> <ctx:service isProxy="true">{ $routingRules/rule[key/text()=$body//*:publisher/text()]/serviceName/text() }</ctx:service> </ctx:route>
$destination
.The key feature of OSB used for this design pattern is the Dynamic Routing action. The expression used in this action is expected in a very specific format, as per the following XML fragment:
<ctx:route> <ctx:service isProxy="$isProxy">$serviceName</ctx:service> <!-- operation is optional --> <ctx:operation>$operationName</ctx:operation> </ctx:route>
Note that isProxy
is an xsd:boolean
. If set to "true"
then serviceName
must be the complete path to an OSB proxy service, otherwise serviceName
must be the complete path of an OSB business service. The complete path should include the OSB project and any subfolders included in the project structure.
In our example, in the LookupDestination
stage, we've used the following XPath to determine the serviceName
:
$routingRules/rule[key/text()=$body//*:publisher/text()]/serviceName/text()
This roughly reads as "return the serviceName
corresponding to the rule in routingRules
for which the key matches the publisher element in the request". In other words, it's a key-value lookup into our configuration file dynamic-routing-rules.xq
.
Now, if we need to add a new publisher service, we only need to ensure that its proxy service is added to dynamic-routing-rules.xq
and implements the same WSDL.
The generic contract of this service comes at a price. Publisher IDs accepted by the service are not restricted and so it's possible to submit a stock order for a publisher which may not exist in the routing rules.
This is easily remedied by adding a conditional block after the LookupDestination
stage to confirm that a valid address was retrieved before attempting to route. The expression to use for the condition is as follows:
fn:data($destination/*:service) = ""
The conditional block should include appropriate logic, such as a Raise Error
action.
Another potential issue with this design pattern is that the destination service may not exist! This could be due to a mistake in the routing rules, or because the destination service has not been deployed for whatever reason.
If this is the case, then OSB will raise the following internal error:
BEA-382612: Error preparing message for dispatch
To detect this issue at runtime, include the following condition expression in your Error Handler
block:
$fault/ctx:errorCode = "BEA-382612"