donderdag 11 april 2013

SOA Suite PS6 (11.1.1.7); Service loose coupling and tokens

Oracle SOA Suite Patchset 6 (11.1.1.7) has been released (https://blogs.oracle.com/SOA/entry/new_release_of_oracle_soa). Several new features are available such as the implementation of configuration tokens. This could be an interesting new feature, since it could potentially allow configuration plans to become obsolete, so I decided to try it out in order to determine it's usefulness.

Using tokens

Edwin Biemond has described in his post on http://biemond.blogspot.nl/2013/04/token-configurations-in-oracle-soa.html how tokens can be used in configurations. He describes the following limitation; "Important to know this only works on the location attribute of the binding.ws element of the composite.xml file.". So if you use references in other locations to for example concrete WSDL's , you can't replace them by using tokens.

Service Loose Coupling

One of the general principles of Service Orientation is Service Loose Coupling (http://serviceorientation.com/serviceorientation/service_loose_coupling); "This principle advocates the creation of a specific type of relationship within and outside of service boundaries, with a constant emphasis on reducing ("loosening") dependencies between the service contract, its implementation, and its service consumers.". On the following blog, a method is described to reduce the dependency between Oracle SOA composite services by placing abstract WSDL's in the MDS; https://blogs.oracle.com/aia/entry/aia_11g_best_practices_for_dec. You can find a clear description of what an abstract WSDL is and how you can create one at; http://sathyam-soa.blogspot.nl/2012/10/abstract-wsdl-vs-concrete-wsdl.html. In the AIA 11g best practice blog post it is mentioned that in the composite.xml file there is a reference to an abstract and a concrete WSDL. The concrete WSDL is referenced in the binding.ws location attribute, which is exactly the attribute which can be replaced with tokens!

An example of a reference to an abstract WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl

An example of a reference to a concrete WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL

Implementation

I created a HelloWorld process and a CallHelloWorld process. The CallHelloWorld process is a proxy to the HelloWorld process.

When creating the process, the following composite.xml was generated for the CallHelloWorld process (indicated in bold the references to the HelloWorld process);

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">

      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>


This composite.xml refers to a concrete WSDL; http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL. It also refers to the abstract WSDL at http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl.These WSDL's however cannot be accessed if the HelloWorld process has not been deployed. For compilation and loading of the process in the SOA runtime, only the abstract WSDL's are required. Therefore, putting the abstract WSDL in the MDS resolves some dependency issues which are for example observed during server start (since there is no guarantee to the order in which services are started).

Putting the abstract WSDL in the MDS

MDS usage in Oracle SOA Suite 11g could in my opinion be improved. You can deploy to the MDS by using scripts; http://biemond.blogspot.nl/2009/11/soa-suite-11g-mds-deploy-and-removal.html or you can create a ZIP-file and upload it. See http://www.oracle.com/technetwork/articles/soa/fonnegra-storing-sca-metadata-1715004.html for an overview of what Oracle provides to interact with the MDS. When developing from JDeveloper though I recommend using a local file based MDS from a version controlled workarea. There are several arguments in support of this but since it is not the focus of this post, I'll not go into details here. For simplicity, I've created a ZIPfile with the following structure;

/apps/hello/wsdl/HelloWorld.wsdl
/apps/hello/xsd/HelloWorld.xsd

And uploaded it using the MDS Configuration page (Import)


Updating the composite.xml from CallHelloWorld
 
In order to use the files put in the MDS, the composite.xml from the CallHelloWorld process needs to be updated. Indicated in bold are the parts I've changed.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="oramds:/apps/hello/wsdl/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="oramds:/apps/hello/wsdl/HelloWorld.wsdl">

    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">
      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>



Configuring the MDS connection (JDeveloper)

For the compilation of the process, make sure you have a local file based MDS configured; In the Application Resources tab under Applications there is a Connections folder. Here you can create the MDS connection.


Make sure the connection is correctly present in the adf-config.xml of your application. My MDS was in /dev/HelloWorld/mds. My adf-config.xml was as followed (in bold what I needed to add to make the oramds references work correctly);

<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
            xmlns:config="http://xmlns.oracle.com/bc4j/configuration"
            xmlns:adf="http://xmlns.oracle.com/adf/config/properties"
            xmlns:sec="http://xmlns.oracle.com/adf/security/config">
  <adf-adfm-config xmlns="http://xmlns.oracle.com/adfm/config">
    <defaults useBindVarsForViewCriteriaLiterals="true"/>
    <startup>
      <amconfig-overrides>
        <config:Database jbo.locking.mode="optimistic"/>
      </amconfig-overrides>
    </startup>
  </adf-adfm-config>
  <adf:adf-properties-child xmlns="http://xmlns.oracle.com/adf/config/properties">
    <adf-property name="adfAppUID" value="HelloWorld.ms.testapp.soa.utils"/>
  </adf:adf-properties-child>
  <sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
    <CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
                            credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
  </sec:adf-security-child>
  <adf-mds-config xmlns="http://xmlns.oracle.com/adf/mds/config">
    <mds-config xmlns="http://xmlns.oracle.com/mds/config">
      <persistence-config>
        <metadata-namespaces>
          <namespace path="/soa/shared" metadata-store-usage="mstore-usage_1"/>
          <namespace path="/apps" metadata-store-usage="mstore-usage_3"/>
        </metadata-namespaces>
        <metadata-store-usages>
          <metadata-store-usage id="mstore-usage_1">
            <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
              <property name="metadata-path"
                        value="${oracle.home}/integration"/>
              <property name="partition-name" value="seed"/>
            </metadata-store>
          </metadata-store-usage>
          <metadata-store-usage id="mstore-usage_3">
                  <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
                     <property value="D:\dev\HelloWorld"
                      name="metadata-path"/>
                     <property value="mds" name="partition-name"/>
                  </metadata-store>
               </metadata-store-usage>

        </metadata-store-usages>
      </persistence-config>
    </mds-config>
  </adf-mds-config>
</adf-config>


Next I undeployed HelloWorld and compiled/deployed CallHelloWorld to confirm they were loosely coupled. I got the following warning; Warning(28,72): Failed to Find Binding "Service1":"{http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld}HelloWorld_pt" in WSDL Manager

This is correct since the binding is still;
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


I could create the JAR file and deploy it. Even without the HelloWorld process.

Tokens

As you might have noticed, the binding is the only concrete coupling between the endpoint of HelloWorld and the CallHelloWorld. Here tokens come in. We can use a token to specify the endpoint and thus become completely independant of configuration plans for the specification of the endpoint to use!

    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


How to use tokens has been described on http://docs.oracle.com/cd/E28280_01/dev.1111/e10224/sca_bindingcomps.htm#CIHFJFJC. I will not go into detail here. The steps are as followed;

Use from the Composite Editor the Binding URL tokenizer

The binding.ws entry is replaced in my example by;

<binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="${helloworld_protocol}://${helloworld_host}:${helloworld_port}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">


Update the tokens on the server


On the server, there is now a file mdm-url-resolver.xml present. In my example this file could be found in; /u01/app/Middleware/user_projects/domains/base_domain/config/fmwconfig/mdm-url-resolver.xml. This file is an example token file;

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="helloworld_host">localhost</entry>
<entry key="helloworld_protocol">http</entry>
<entry key="helloworld_port">8001</entry>
</properties>


You can place the token file in the MDS so it is easily available to other developers. I deployed the HelloWorld process and called the CallHelloWorld service with the following result;


After restarting the (Admin)Server, it worked.

Predefined token ${serverURL} 

There is one predefined token; the ${serverURL} token. This token uses the soa-infra server URL as it's value. When I changed my binding.ws location entry to ${serverURL}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL and tested the service, it worked.This means no token configuration on the server is required if you want to call processes on the same server.

You can download the sample code here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldTokens.zip. It does not require server configuration since the serverURL token is used. Keep in mind though that you will need an 11.1.1.7 (PS6) SOA Suite installation for this to work.

Conclusion

Tight coupling between services by using references to WSDL's and/or XSD's which are part of another process, should be avoided. This can be done by putting abstract WSDL's in the MDS.

When the above has been implemented, the loose coupling between processes can be extended by using tokens in the composite.xml file to set the correct binding.ws location attribute. When changing tokens/token values, the server needs to be restarted though. Since only one property can be replaced, it's use is limited to the concrete WSDL used to obtain the binding in the composite.xml file. When calling processes on the same server, the serverURL token can be used which is always available and does not require specific definition of tokens on the server.

With the above in place; loose coupling to WSDL's/services and tokens, one can argue that configuration plans become obsolete. When however variables/properties in the composite.xml are used, you still might need a mechanism to make these values differ per environment and configuration plans provide a (deploy time) solution to that. By using the MBean browser, these properties can be updated on runtime (see http://beatechnologies.wordpress.com/2011/11/04/persisting-component-preferences-in-oracle-soa-suite-11g/). This would not be needed if global variables could be used. I'm however not aware of an out of the box (GUI supported, little coding) implementation for deploytime and runtime global variables in Oracle SOA Suite 11g. Of course a database can be used, Server MBeans or plain old property files, but you have to build your own implementation.