Why It's Worth Optimizing Local Web Service Invocations

Digg this!

When people typically think of Web Services, they think of remote calls that cross system boundaries and typically networks. Web Services is also typically used with the design philosophy of Service-Oriented Architecture (SOA), which suggests encapsulating a bunch of business logic and function as a callable service with a more granular interface. A huge advantage of Web Services is how is supports a loosely coupled componentization model. This can be a boon for developing flexible, scalable systems that support dynamic selection of function at runtime; Web Services can effectively be used to bind together internal components of the system as well as exposing more granular services. This technique of binding together the system also supports a very flexible packaging model. In J2EE parlance, components can be bundled as multiple WAR files in a single EAR or as a set of EARs, where each EAR contains a single and very self-contained component.

The usual concern with this approach of moving one step down in granularity from the service level is one of performance: in the CPU cost and latency of making the Web Service invocation. This is where local Web Services optimization comes in. Within a cluster of application servers, all components can be distributed to each application server; each application server contains a copy and executes all the code for each components. In this example, each component is packaged in its own EAR -this comes with advantages discussed below. When calling shared components, the fronting service or component is configured with a local endpoint is given to the Web Service runtime: http://localhost/foo/endpoint. Each copy of the service code makes an invocation with the localhost endpoint, thereby hitting local code.

This architecture looks as follows:

... by optimizing local Web Service invocations, the overhead associated with each invocation to the common components, or shared code is dramatically decreased. Note that this invocation still goes through the application server engine and the Web Service is configured to look like a SOAP/HTTP request. The application server must recognize that this is bound for a local destination and optimize the request. Why this approach as opposed to use using Java code or even a Java binding to a WSDL interface? This approach brings several advantages:

  • If in the future scalability is a concern, components can be redistributed to remote servers by merely redeploying the component and configuring the endpoints.
  • Component implementations can easily be substituted at the interface level for different implementations. This opens up several possibilities: i) different functions (following the same interface of course) can be selected at runtime based on the needs of the service or end user. This requires only selecting a different endpoint. In some cases, it allows for smaller, more streamlined components that do just one thing well, rather than a multitude of things., or ii) the application can be easily customized during a service engagement or customer deployment by substituting behind the interface. All that's needed to build this customization is the WSDL and commonly available Web Service tooling.
  • If EAR packaging for the components is used, this model provides easier support for live upgrades and fixes. The "upgrade" EAR can be deployed alongside the old EAR. The system admin can then just "flip the switch" by pointing the service at the new endpoint. Note that this suggests a service model whereby configuration data is kept constant throughout the life of the service, so as not to disrupt existing executing services or sessions.

In order to support this optimization, application servers must consider several factors. The invocation must not go out to the network port but instead be short cut internally by the Web Service stack. The stack must take into account that components may span different class loaders when passing objects. In the J2EE example, different WAR modules and EAR modules have different class loaders. The ideal optimization would provide a pass by reference mechanism. Thread use is also an issue. The invocation must be performed on a single thread. Otherwise, there's a risk of running out of threads and locking up the system, since the fronting service thread if held while various invocations are made to back-end services. If different threads are used to dispatch requests to those services and the app server runs out of threads, then the fronting service will hang, exacerbating the resource problem and incurring deadlock. These optimizations serve to reduce overhead costs and latency.

The WebSphere Application Server supports all these optimizations with the exception of the pass by reference approach, although some steps were taken to cut down the serialization cost. This overhead can further be reduced at design time to sticking to XSD types that correspond directly to Java primitive types, making serialization cheap. This approach was used very successfully in practice in the architecture and design of the Telecom Web Services Server.

What about Apache Tomcat ?

What about Apache Tomcat ?

  • how does it handle local web service invocations ?
  • Are there are any plugins available that can optimize the Web Service invocation especially avoid the XML converts.

agree wholeheartedly but with the caveat that...

...each 'unit' of flexibility comes at the cost of a 'unit' of deployment complexity, and I am not convinced that they are the same cost.

Deploying one EAR can be a pain to get correct. Now multiply the potential for error by the number of independent components. And even that assumes a heterogeneous deployment platform.

While technically elegant I would caution others to examine the potential impact on documentation, training, and support before starting down such a path.

I do love the approach, but I feel that it should be adopted only with caution.

Good point

I agree that it comes with a cost of deployment complexity, and it pays to choose the granularity of the component model wisely to make it worthwhile. However, how much of the complexity is the actual deployment of the EAR itself so much as configuring the dependent resources? Meaning, if you took that function and rolled it up into one big EAR, wouldn't you still have to configure the dependent resources for that function? The counter argument to that is that you might get better resource sharing (of JNDI names, etc.) if it's rolled into a single unit. Providing good EAR deployment defaults goes a long way at reducing this cost; the setup of the resources needs to be done outside the EAR, regardless.