Wednesday, August 13, 2014

Implementing Asynchronous communication with Oracle Service Bus

Its a more prevalent requirement to design asynchronous web services when dealing with long running business process and its well known fact that Oracle SOA suite has got a very robust implementation support using WS-Addressing under the hoods.

But there aren't many examples available which depicts how the same can be implemented through technologies like Oracle Service Bus. There are couple of well-defined patterns available like

Using JMS for example to decouple the request and callback


Using SOA-Direct / SB Transport binding - Using WS-Addressing internally.




But there may be situations like:

  • The architecture doesn't allow using proprietary transport bindings 
  • Using JMS is a overkill. 
  • Require better fault management requirement.
For these situation fortunately, we can still use WS-Addressing in OSB to decouple request/response albeit with additional extra work.

The suggested pattern is shown below :



The WS-Addressing has been forwarded to the external system using a customer header to decouple the pattern logic from the external system. I will try to explain the implementation with a much simpler example.
WS-Addressing purpose is to provide a standard way to specify where a message is going and where to send the response (or) the fault back. The below wsdl specifies the contract for Hello World Asynchronous service with the request sent using 'sayHello' operation and the response/fault is retrieved through 'sayHelloResponse' (or) 'sayHelloFaultResponse' respectively.

Schema

<?xml version="1.0" encoding="UTF-8" ?><xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://techsamples.fmw/v1/HelloWorldAsync"
    targetNamespace="http://techsamples.fmw/v1/HelloWorldAsync" elementFormDefault="qualified" xmlns:flt="http://techsamples.fmw/v1/FaultObject">
    <xsd:import schemaLocation="../xsd/techsamples.fmw.cmn.FaultObject.xsd" namespace="http://techsamples.fmw/v1/FaultObject"></xsd:import>
    <xsd:element name="sayHello" type="tns:SayHelloType"/>
    <xsd:complexType name="SayHelloType">
        <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
    
    <xsd:element name="sayHelloResponse" type="tns:SayHelloResponseType"/>
    <xsd:complexType name="SayHelloResponseType">
        <xsd:sequence>
            <xsd:element name="result" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
    
    <xsd:element name="sayHelloFaultResponse" type="tns:SayHelloFaultResponseType"/>
    <xsd:complexType name="SayHelloFaultResponseType">
        <xsd:sequence>
            <xsd:element ref="flt:faultObject"/>
        </xsd:sequence>
    </xsd:complexType></xsd:schema>

WSDL Contract
<?xml version="1.0" encoding="UTF-8" ?><wsdl:definitions targetNamespace="http://techsamples.fmw/v1/HelloWorldAsync" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://techsamples.fmw/v1/HelloWorldAsync" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
    xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:flt="http://techsamples.fmw/v1/FaultObject">
    <wsdl:types>
        <xsd:schema>
            <xsd:import schemaLocation="techsamples.fmw.ebm.HelloWorldAsync.xsd"
                namespace="http://techsamples.fmw/v1/HelloWorldAsync"/>
            <xsd:import schemaLocation="../xsd/techsamples.fmw.cmn.FaultObject.xsd" namespace="http://techsamples.fmw/v1/FaultObject"></xsd:import>            
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="serviceFault">
        <wsdl:part name="serviceFault" element="flt:faultObject"/>
    </wsdl:message>
    <wsdl:message name="sayHello">
        <wsdl:part name="sayHello" element="tns:sayHello"/>
    </wsdl:message>
    <wsdl:message name="sayHelloResponse">
        <wsdl:part name="sayHelloResponse" element="tns:sayHelloResponse"/>
    </wsdl:message>
    <wsdl:message name="sayHelloFaultResponse">
        <wsdl:part name="sayHelloFaultResponse" element="tns:sayHelloFaultResponse"/>
    </wsdl:message>
    
    <wsdl:portType name="HelloWorldAsync.PortType">
        <wsdl:operation name="sayHello">
            <wsdl:input message="tns:sayHello"/>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:portType name="HelloWorldAsyncCallback.PortType">
        <wsdl:operation name="sayHelloResponse">
            <wsdl:input message="tns:sayHelloResponse"/>
        </wsdl:operation>
        <wsdl:operation name="sayHelloFaultResponse">
            <wsdl:input message="tns:sayHelloFaultResponse"/>
        </wsdl:operation>
    </wsdl:portType>
    
    <wsdl:binding name="HelloWorldAsync.Binding" type="tns:HelloWorldAsync.PortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="sayHello">
            <soap:operation soapAction="sayHello" style="document"/>
            <wsdl:input name="sayHello">
                <soap:body use="literal"/>
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    
    <wsdl:binding name="HelloWorldAsyncCallback.Binding" type="tns:HelloWorldAsyncCallback.PortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="sayHelloResponse">
            <soap:operation soapAction="sayHelloResponse" style="document"/>
            <wsdl:input name="sayHelloResponse">
                <soap:body use="literal"/>
            </wsdl:input>
        </wsdl:operation>
        <wsdl:operation name="sayHelloFaultResponse">
            <soap:operation soapAction="sayHelloFaultResponse" style="document"/>
            <wsdl:input name="sayHelloFaultResponse">
                <soap:body use="literal"/>
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HelloWorldAsync.Service">
        <wsdl:port binding="tns:HelloWorldAsync.Binding" name="HelloWorldAsync.Port">
            <soap:address location="http://@@osb.target.address@@/techsamples.fmw/HelloWorldAsync"/>
        </wsdl:port>
    </wsdl:service>
    <wsdl:service name="HelloWorldAsyncCallback.Service">
        <wsdl:port binding="tns:HelloWorldAsyncCallback.Binding" name="HelloWorldAsyncCallback.Port">
            <soap:address location="http://@@osb.target.address@@/techsamples.fmw/HelloWorldAsyncCallback"/>
        </wsdl:port>
    </wsdl:service></wsdl:definitions>

Artefacts

  • HelloWorldAsync.Proxy - Proxy to handle the HelloWorldAsync.Binding(sayHello operation)
  • HelloWorldAsyncCallback.Proxy - Proxy for the callback wsdl binding(sayHelloResponse & sayHelloFaultResponse operations). The expectation is that the response will always flow through this proxy. In this example, there is no external service,so HelloWorldAsync.proxy will directly send the callback response via this proxy. 
  • HelloWorldAsyncClientCallback.biz - Abstract business service representing the actual callback client whose endpoint & the operation(action) are set at runtime using WS-Addressing.
  • HelloWorldAsync Client BPEL Process - Client BPEL Process - The client process which call the HelloWorldAsync.sayHello operation and waiting for the callback.
HelloWorldAsync.Proxy


The SetCallbackURI assign activity in the CommonInitializationPipelinePair determines the recipient for the actual route(ideally this would be that of the external service) but for the simplicity we are setting it to the callback proxy directly. The xquery would look like something below:


if(fn:exists($inbound/ctx:transport/ctx:request/tp:headers/http:Host)) then
(
 <Callback>{fn:concat("http://",$inbound/ctx:transport/ctx:request/tp:headers/http:Host/text()[normalize-space(.)],"/techsamples.fmw/HelloWorldAsyncCallback")}</Callback>
)
else
(
 if($header/*:To/text()[normalize-space(.)]!='') then
 ( 
  <Callback>{fn:concat("http://",fn:substring-before(fn:substring-after($header/*:To/text()[normalize-space(.)],"http://"),"/"),"/techsamples.fmw/HelloWorldAsyncCallback")}</Callback> 
 )
 else
 (
  <Callback/> 
 )
)

the below image shows the routing part which sets the WS-Addressing for the callback to forward the response to the actual client.


Similar to the response callback, if there are any faults in the processing, it will managed by 'MainErrorHandler' and the fault is dispatched to the callback proxy as a fault response:


Also, note that on the call back proxy, you can decide the operational branch selection based on the WS-Addressing as well as shown below: HelloWorldAsyncCallback.Proxy Now the callback's responsibility is to forward the response back to the client that is waiting for the response. Firstly we capture the final destination from the WS-Addressing header:-


The routing below shows the response is forwarded to the HelloWorldAsyncClientCallback business service, which in turn sends the response to the Callback Client using WS-Addressing.


HelloWorldAsyncClientProcess The below client bpel process invokes the 'sayHello' operation in the HelloWorldAsync proxy and waits for the response or fault via the callback ports:



Lets run the client process and see the outcome ...



Lets throw a dummy error from the proxy mimicking a fault from the external service and see how the fault is propagated to the client.

That' all.. In the next instalment I will try to put a sample to implement the same scenario using jaxws web services.

1 comment:


  1. Hello,

    Can you provide me the link to download the project to the implementation?

    Best regards

    ReplyDelete