An idealized requirement in Service-Oriented Architecture (SOA)-based system design is that of decoupling between service consumers and service providers. Decoupling can take various forms ranging from static, where decoupling is enabled by requester-side stubs at compile time, up to full dynamic decoupling, where decoupling is completely encapsulated and established at run-time in all service invocations. Clearly, full dynamic decoupling is a stringent demand. If a service is dynamically decoupled, then changes in the service characteristics leads to a modification in that service implementation, but all other system elements, especially invocation mechanics, remain constant. The advantages of such a design are intuitively clear.
The increasing expectations and demands for greater functionality and capabilities from SOA and Web services often result in upsurged software complexity. The reality of today?s SOA implementations is that, although functionality modularization was planned with services, those have often been coupled to a single middleware or communication protocol (such as MOM-based JMS, HTTP/SOAP, and so on). In most organizations, developers focus, to some degree, on static decoupling (often referred to as binding) when extending functionality in the applications, and that forces the creation of separate clients for every Web service, instead of having a single client capable of accessing multiple services at one time.
Limitations derived from static binding are significant -- static binding in the code prevents reuse of services across the enterprise in different applications. In short, even through the need to abstract and encapsulate the different middleware and protocols used to perform the interactions between service components involved in the service consumer and service provider interactions was popularized in literature, architectures with encapsulated Web services interfaces have not been widely implemented. One of the primary reasons for this situation is that, although several mechanisms for dynamic binding have been designed to meet this limitation, considerable misconceptions about actual techniques associated with dynamic binding still exist.
Anatomy of Web services invocation
Volumes have been written about one of the most important conceptual notions of SOA -- service interfaces (in other words, invoked functions) and how they should be located and invoked at run-time. In other words, Web service invocation ought to be of late binding. It is necessary to pin-point that Web services invocation is engineered as the traditional method of Remote Procedure Call (RPC) that allows one application to invoke a function published by a second application. RPC works as follows (as depicted in Figure 1):
- The function from the invoked application appears as a local function to the invoking application.
- Localization transparency is achieved through providing a function stub to the invoking application.
- The function stub, when called, accesses a middleware layer that transports the call and its associated data to the invoked application.
Figure 1. RPC-based Web services invocations
In the case of Web services, the generation of stub code is typically automated and is based on consuming an interface description of the invoked function, expressed through Web Services Description Language (WSDL), and creating the stub shell. Moreover, typically, the stubs are generated during the coding stage of application development because the stubs are predominantly called directly from the application code, and, as a result, have to be resolved at compile time (in other words, the so-called early binding).
Most application developers, and mainly Java developers, believe that such usage of stubs is a necessity because it is not clear to them how to handle complex data types returned from the invoked service. Sure, the data coming from the invoked application function must be separated from the SOAP message, and, when using Java, it is obligatory to have a corresponding (and compatible) class that implements the serializable interface. Obviously, the same stub generation tool is used to generate those required classes.
The first moral in this paper is that, broadly speaking, as often it is the case, automation is a "two-faced" story. On the one side, automation allowed a speedy adaptation of Web services in the industry because, with automation tools, the activities of most application developers do not occur at the level of WSDL itself. On the other side, unfortunately, the marketplace has yet to yield a predominate tool that allows you to choose which form of invocation is needed -- explicit stubbing (the stubs are resolved at compile time) or implicit stubbing (the stubs are resolved at run-time). Implicit stubbing is often referred to as stubless invocation of Web services.
Most Java-based Web services tools provide some capability for late, run-time binding to Web services interfaces. Two examples include the Glue product from webMethods or the JAX-RPC package from the J2EE Web Services Developer toolkit. However, such attempts are mostly work-arounds that result in software that is deeply ingrained with vendor-specific logic. Moreover, the dependency on the vendor-specific code is not the only problem. In many cases, vendor proprietary solutions are creating substantial manageability and maintainability issues.
Let?s re-iterate the cited points by reviewing, for example, the webMethods Glue approach for late binding. These days, Glue is a widely used tool. It might seem that Glue is doing everything right, since it is using a proxy for the Web services interface. However, just using a proxy by itself does not eliminate the problems. The following Java code snippets illustrate a typical negative situation in their case:
Listing 1. lateBindingGlue
public class lateBindingGlue
{
public Document serviceInvocationGlue()
throws Throwable {
String wsdlName = "http://?../Service.wsdl?WSDL";
String operation = "??..";
String args[] = { };
/* first, Glue creates a SOAP interceptor */
SOAPInterceptor responseHandler = new SOAPInterceptor();
/* second, Glue registers the SOAP interceptor to catch incoming responses */
ApplicationContext.addInboundSoapResponseInterceptor(
(ISOAPInterceptor)responseHandler );
try {
/* third, Glue gets a proxy to the Web service through its WSDL */
IProxy proxy = Registry.bind( wsdlName );
/* here, service's operation is invoked through a proxy */
proxy.invoke( operation, args );
} catch( java.rmi.UnmarshalException e ) {
// Glue is catching the UnmarshalException that is perfectly expected
}
/* forth, Glue generates an XML document containing the SOAP body, */
/* and passes the whole document for parsing */
return new Document( responseHandler.getResponse() );
}
}
class SOAPInterceptor implements ISOAPInterceptor {
private Element soapBody;
public void intercept( SOAPMessage message,
Context messageContext ) {
try {
soapBody = message.getBody();
}
catch( Exception e ){
System.err.println( e.toString());
}
}
public Element getResponse(){
return soapBody;
}
}
|
You can learn a number of things from the example above. First, it is clear that the code introduced by Glue is a two-layer code. One layer "wipes out" the negative effect of SOAP-separating by intercepting this particular application exception using Java exception handling and a specialized component -- SOAP interceptor. The second layer delivers results of the service invocation to the invoking application as a whole XML document. Then, the XML document is parsed out using something like a DOM structure. Evidently, such a multi-tiered approach can?t be ported "safely" to other Web services tools. But, more importantly, although the technique of intercepting application exceptions is well-known, is it really appropriate in case of Web services invocations?
Generally speaking, exceptions are an extremely useful feature of the Java language, especially as a means to have an easy way of detecting locations of erroneous, unplanned programming actions like using a null pointer. Using this technique as a planned action of control instead of writing a piece of if-then logic that tests whether a pointer actually was null significantly complicates manageability and maintainability of the code. Clearly, SOAP separating is not the same as a null pointer. You can?t test it out with a simple if-then code. Nevertheless, the negative affect is the same -- throwing an exception and catching it is a very expensive technique, especially if you need to obtain a rate of thousands of Web services invocations per second. In other words, run-time exceptions should be reserved for the unexpected cases exclusively as a "line of defense" against software bugs.
I could go on and point out many more reasons why the SOAP-separating issue should not be handled through a Java Exception API. But, let?s move on to another issue highlighted by the cited example. Since the invoking application needs to parse the XML directly, it has to have access to the XSD definition for the response message, and that, in turn, must be part of the provided WSDL file (and consumed by the stub-generating tool). The end result here is that even some form of late binding exists in the cited example; the tight coupling between Web services partners is still present. In other words, the client of a Web service must have access to the WSDL file, and, in most cases, it has to be a complete (not a partial) WSDL file.
There is one more issue that relates to using JAX-RPC APIs. It is very important to remember that in Java language, since data types in XML schema do not map exactly to the data types in the Java language, the service endpoint interface generated from the WSDL at the invoked application's side could be different from the interface that the JAX-RPC compiler generated the WSDL from, at the invoking application?s side. Also, the generated service interface will depend on which SOAP encoding style is used. For example, using the document-literal encoding imposes wrapped versions of each method class, and that, in turn, creates additional manageability and maintainability issues.
With all that was said so far, you can easily think in terms of using any kind of dynamic invocation techniques (such as dynamic proxies for late binding) like, unfortunately, many developers do. One of the most common misconceptions in the area of Web services invocations is that the benefits of using dynamic invocations instead of generated static stubs are not so great, and it's probably best to stick with generated stubs as the primary method for Web service invocations.
At last, our discussion has arrived at the second, and the most important, moral in this paper -- it is not actual programming techniques that ensure that there are no depencies on the service programming artifacts (like WSDL), but instead, the value is in high-level design. This is necessary for reaping real benefits of SOA because as the service evolves, the service consumer code won't be impacted at all. In spite of everything, SOA-based applications have to be more concentrated on providing resilient architecture and less concentrated on the common communication protocols, such as SOAP. The boundary between services and applications should not be rigid. An application to one end-user can be viewed as a service to another end-user. The entire enterprise IT environment should be designed to be highly modular, allowing developers to pick and choose a collection of services and applications that suits their needs.
In a good SOA design, the application logic of the Web services consumer can be completely decoupled from the service artifacts using two fundamental architectural principles:
- "Stay adaptable" -- create a way to support multiple interface inheritance (in theory, this would be unlimited) while exploiting a limited number of concrete implementations (in object-oriented design, it's often the case that an anonymous inner class is used as the object adapter, allowing customization of behavior with respect to the context, actually, of being embedded within a subclass).
- "Use the so-called Hollywood principle: Don't call us, we'll call you" -- the Web services invocation model should only specify discovery through the dynamic proxy framework and assume that the client has no prior knowledge of the services; therefore, Web services providers should be able advertise their services in XML documents at run-time by asserting stub-like information into those documents. That actually provides a way to enable invocation in addition to discovery (in other words, well beyond UDDI), in an open and extensible way.
As a result, the ultimate design goal is providing to the Web services consumer application a set of classes that make up a reusable adapter layer engineered according to the principles cited above. Such an adapter layer encapsulates code that uses the stub and its generated classes. The public API of the adapter does not expose any of the stub classes; it instead maps them to classes that the Web service consumer application understands.
To achieve the maximum level of flexibility and extensibility for the adapter, the overall class structure of the adapter is constructed by using a composition of the following design patterns:
- Dynamic Proxy is a structural pattern that defines a set of classes and method invocations which are dispatched to an invocation handler that is specified when the dynamic proxy classes are generated. Using dynamic proxies is the key to simulating multiple implementation inheritance within Java programming. With dynamic proxies, a custom InvocationHandler is constructed with a set of classes that represent the superclasses of the subclass to be synthesized; the interface(s) of that subclass will be the union of the interfaces that these superclasses implement.
- Adapter is a structural pattern that influences the creation of a class hierarchy (as inheritance hierarchies) by decoupling an abstraction from the implementation so the two can vary independently. Such decoupling avoids a permanent binding between an abstraction and its implementation, which allows the implementation to be selected at run-time. The invoking application's class, which acts as the Web services client, deals only with the abstraction.
- Service Configurator is a behavioral pattern that lets you alter the capabilities of the adapter over time, and to add or remove additional functionalities, thus changing the specification of the invocation framework. For example, if a Web services provider brings a new protocol (such as SOAP over RMI) it would need only to "advertise" the new transport capabilities. Therefore, the adapter instantiates this new service capability and any implementation of this invocation functionality. Using some meta data representation as a result, there must be a way of referring to an interface that provides a class of functionality independent of particular specification and implementation.
- Factory Method is a structural pattern that lets a class defer instantiation to subclasses. In the case of Web services invocations, both the local and remote implementation classes have to be subclasses in order to realize their service-specific implementations. The invoking application that wants to access the service will get a handle to this factory and makes a call to the service. Therefore, the factory encapsulates the knowledge of which implementation it has to use to access the service, depending on the information from the Service Configurator.
- Decorator is a structural pattern that defines a wrapper for caching, publishing, and exchanging service assertions used for connecting to appropriate services and re-utilizing proxy classes. By using the Decorator pattern, it is simple to separate the code that performs invocations from the code that provides caching itself.
Figure 2 illustrates the conceptual model of a "Hollywood"-type adapter, engineered using the above cited design principles. Figure 3 is a sequence diagram that shows essential interactions when using the adapter.
Figure 2. Conceptual model of the Web services adapter
Figure 3. Sequence diagram involving the Web services adapter
Creating a service using the adapter includes creating a service class with the service descriptor that represents the SOAP service. This descriptor is included as a member of the service class. The service class is deployed as a SOAP service, for example, using Axis from Open Source Foundation.
The service descriptor can contain:
- Class Name: an element containing the fully qualified class name of the service implementation.
- Name: an element containing the name for the service.
- Version: an element containing the version number of the service.
- Creator: an element containing the name of the creator of the service.
- AssertURI: an element containing the URI that points to an XML document that includes service assertions (specifications).
- Desc: an element containing a description of the service.
Now, a couple notes about Axis. Using Axis as a SOAP engine is very helpful for implementing loose coupling of Web services for the following reasons:
- Because Axis defines a message-processing node that can be packaged for use by both service requestors (clients) and service providers (servers), the adapter can use a message processing node to deploy the SOAP service using a deployment descriptor as a Web Services Deployment Descriptor (WSDD).
- Services in Axis are typically instances of the SOAP service, which could contain the request and response chains, but must contain a provider that is the actual service class. As a result, the processing of the SOAP service is done by passing a message context to the message-processing node.
- The Axis client-side processing can be used to construct a call object by creating an instance of the call factory with the service descriptor and the XML assertion document, which contains the service details. The properties of the call object are set to use the associated target service. The call object is then created by invoking the Service.createCall factory method. Once the call is set-up, the
Call.SetOperationis specified with the method name to be invoked. Then,Call.invokeis called with the associated request message, which drives theAxisClient.invoke, processes the resultant message context, and returns the response message to the client.
In the design of the adapter, special considerations have to be given to performance. Using dynamic proxy classes has performance implications. Direct method calls on objects are faster than method calls on proxy classes. However, it should not be a choice between robust architecture and performance. This is why the presented adapter implements caching wrappers. With caching, the performance difference between using static stubs and adapter-based solutions is fairly minor. In terms of how to implement caching, one of the potential solutions -- memoization -- has to be mentioned. Memoization is a technique widely used in functional programming languages like Lisp, Python, and Perl for giving functions a memory of previously computed values. Memoizing a function adds a transparent caching wrapper to the function, so that function values that have already been obtained are returned from a cache rather than being rebuilt each time. Memoization can provide significant performance gains for dynamic proxy calls.
Wrapping up our discussion about the adapter described here, it is very important to highlight that such a design allows for supporting both local and remote service implementations. A service class that will be local to the adapter and the remote Web service are becoming substitutes for each other, because both a service class and a proxy class to access the remote Web service implement the same interface. The local service class will implement the single method getFunction() similar to the Web services remote implementation returning the result of a function. The following Java snippets illustrate this point further:
Listing 2. Local service class and its interfaces
public class LocalServiceImpl implements IService
{
/* get the service results */
public ?.. getFunction()
{
???..
return ????;
}
}
|
The proxy class that implements the IService takes a single method getFunction(), but overrides the method with the code that needed to access the remote Web service. This code represents the proxy code needed to access any Web service deployed within the adapter.
Listing 3. Remote Service
public class RemoteServiceImpl implements IService
{
/* get the service outputs */
/* outputs are from the web service running in the location */
/* mentioned in the serviceURI */
public ?.. getFunction()
{
adapter.examples.Function service = null;
String serviceURI = "http://??../Function/";
String wsdlURI = "http://localhost:8080/Function/";
Try
{
// init the lookup
WebServiceLookup lookup = (WebServiceLookup)
???.
// get the instance of the Web services interface
// from the lookup
service = (adapter.examples.Function)
lookup.lookup(wsdlURI,
adapter.examples.Function.class,
serviceURI);
}
catch (Exception e)
{
e.printStackTrace();
}
// now, call the methods on your Web services interface
return service.getFunction();
}
|
Clearly, using static stubs and early binding of Web services is the easiest form of Web services invocations. But with simplicity come significant drawbacks. Unlike a tightly coupled approach, using the adapter approach presented in this paper leaves you with Web services invocation code that is highly reusable and configurable. Because Web services invocations are all channeled through a common adapter in which service descriptors are deployed, you could decide dynamically what service is invoked -- doing this in the deployed code or even at run-time.
Also, organizations that might look for a commercial product as an alternative to custom solutions for loosely coupled invocations of Web services should investigate Enterprise Service Bus (ESB) technologies (see Resources) that offer options similar to the capabilities cited above for connecting Web services-enabled applications within and across enterprises, with a set of features enabling management and monitoring of interactions between connected applications.
-
Singh, I., Brydon, S., Murray, G., Ramachandran, V., Violleau, T., Stearns, B., "Designing Web Services with the J2EE(TM) 1.4 Platform: JAX-RPC, SOAP, and XML Technologies"
(Sun Microsystems, 2004) is a must-read for Java
developers involved with Web Services.
-
Read Remoting Patterns -- Foundations of Enterprise, Internet, and Realtime Distributed Object Middleware if you are interested in architecture patterns that deal with dynamic decoupling.
- Learn more about the Enterprise Service Bus (ESB) in the article "Understand Enterprise Service Bus scenarios and solutions in Service-Oriented Architecture" (developerWorks, June 2004).
- Browse for books on these and other technical topics.
- Get involved in the developerWorks community by participating in
developerWorks blogs.
- Want more? The developerWorks SOA and Web services zone hosts hundreds of informative articles and introductory, intermediate, and advanced tutorials on how to develop Web services applications.

Mark M. Davydov, Ph.D., is an internationally-known expert in software engineering and systems architecture, including SOA. Dr. Davydov is the author of numerous highly acclaimed articles in computer-related publications. His 2001 book Corporate Portals and e-Business Integration -- A Manager's Guide, McGraw-Hill Professional Publishing, introduced many ideas that influenced the progression of Service-Oriented Architecture and the Web services model.




