11 Roadmap for Developing Asynchronous Web Service Clients

This chapter presents best practices for developing asynchronous WebLogic Web service clients for Java API for XML Web Services (JAX-WS).

Table 11-1 provides best practices for developing asynchronous Web service clients, and is followed by an example that illustrates the best practices presented. These guidelines should be used in conjunction with the general guidelines provided in Chapter 7, "Roadmap for Developing JAX-WS Web Service Clients."

For best practices when developing reliable Web service clients, see Chapter 13, "Roadmap for Developing Reliable Web Services and Clients."

Note:

In the following table, client instance can be a port or a Dispatch instance.

Table 11-1 Roadmap for Developing Asynchronous Web Service Clients

Best Practice Description

Define a port-based asynchronous callback handler, AsyncClientHandlerFeature, for asynchronous and dispatch callback handling.

Use of AsyncClientHandlerFeature is recommended as a best practice when using asynchronous invocation due to its scalability and ability to survive a JVM restart. It can be used by any client (survivable or not.) For information, see Developing the Asynchronous Handler Interface.

Define a singleton port instance and initialize it when the client container initializes (upon deployment).

Creation of the singleton port:

  • Triggers the asynchronous response endpoint to be published upon deployment.

  • Supports failure recovery by re-initializing the singleton port instance after VM restart.

Within a cluster, initialization of a singleton port will ensure that all member servers in the cluster publish an asynchronous response endpoint.This ensures that the asynchronous response messages can be delivered to any member server and optionally forwarded to the correct server via in-place cluster routing. For complete details, see Clustering Considerations for Asynchronous Web Service Messaging..

If using Make Connection for clients behind a firewall, set the Make Connection polling interval to a value that is realistic for your scenario.

The Make Connection polling interval should be set as high as possible to avoid unnecessary polling overhead, but also low enough to allow responses to be retrieved in a timely fashion. A recommended value for the Make Connection polling interval is one-half of the expected average response time of the Web service being invoked. For more information setting the Make Connection polling interval, see Configuring the Polling Interval.

Note: This best practice is not demonstrated in Example 11-1.

If using the JAX-WS Reference Implementation (RI), implement the AsyncHandler<T> interface.

Use of the AsyncHandler<T> interface is more efficient than the Response<T> interface. For more information and an example, see Using the JAX-WS Reference Implementation.

Note: This best practice is not demonstrated in Example 11-1.

Define a Work Manager and set the thread pool minimum size constraint (min-threads-constraint) to a value that is at least as large as the expected number of concurrent requests or responses into the service.

For example, if a Web service client issues 20 requests in rapid succession, the recommended thread pool minimum size constraint value would be 20 for the application hosting the client. If the configured constraint value is too small, performance can be severely degraded as incoming work waits for a free processing thread.

For more information about the thread pool minimum size constraint, see "Constraints" in Administering Server Environments for Oracle WebLogic Server.


The following example illustrates best practices for developing asynchronous Web service clients.

Example 11-1 Asynchronous Web Service Client Best Practices Example

import java.io.*;
import java.util.*;
 
import javax.servlet.*
import javax.xml.ws.*
 
import weblogic.jws.jaxws.client.ClientIdentityFeature;
import weblogic.jws.jaxws.client.async.AsyncClientHandlerFeature;
import weblogic.jws.jaxws.client.async.AsyncClientTransportFeature;
 
import com.sun.xml.ws.developer.JAXWSProperties;
 
/**
 * Example client for invoking a Web service asynchronously.
 */
public class BestPracticeAsyncClient
  extends GenericServlet {
 
  private static final String MY_PROPERTY = "MyProperty";
 
  private BackendServiceService _service;
  private WebServiceFeature[] _features;
  private BackendService _singletonPort;
 
  private static String _lastResponse;
  private static int _requestCount;
 
  @Override
  public void init()
    throws ServletException {
 
    // Only create the Web service object once as it is expensive to create repeatedly.
    if (_service == null) {
      _service = new BackendServiceService();
    }
 
    // Best Practice: Use a stored list of features, including client ID, to create client 
    // instances.
    // Define all features for the Web service client instance, including client ID, so that they
    // are consistent each time the client instance is created. For example: 
    // _service.getBackendServicePort(_features);
 
    List<WebServiceFeature> features = new ArrayList<WebServiceFeature>();
 
    // Best Practice: Explicitly define the client ID.
    ClientIdentityFeature clientIdFeature =
      new ClientIdentityFeature("MyBackendServiceAsyncClient");
    features.add(clientIdFeature);
 
    // Asynchronous endpoint
    AsyncClientTransportFeature asyncFeature =
      new AsyncClientTransportFeature(getServletContext());
    features.add(asyncFeature);
 
    // Best Practice: Define a port-based asynchronous callback handler,
    // AsyncClientHandlerFeature, for asynchronous and dispatch callback handling.
    BackendServiceAsyncHandler handler =
      new BackendServiceAsyncHandler() {
        // This class is stateless and should not depend on
        // having member variables to work with across restarts.
        public void onDoSomethingResponse(Response<DoSomethingResponse> res) {
          // ... Handle Response ...
          try {
            DoSomethingResponse response = res.get();
            res.getContext();
            _lastResponse = response.getReturn();
            System.out.println("Got async response: " + _lastResponse);
            // Retrieve the request property. This property can be used to 
            // 'remember' the context of the request and subsequently process
            // the response.
            Map<String, Serializable> requestProps =
              (Map<String, Serializable>)
                res.getContext().get(JAXWSProperties.PERSISTENT_CONTEXT);
            String myProperty = (String)requestProps.get(MY_PROPERTY);
            System.out.println("Got MyProperty value propagated from request: "+
                               myProperty);
          } catch (Exception e) {
            _lastResponse = e.toString();
            e.printStackTrace();
          }
        }
      };
    AsyncClientHandlerFeature handlerFeature =
      new AsyncClientHandlerFeature(handler);
    features.add(handlerFeature);
 
    // Set the features used when creating clients with
    // the client ID "MyBackendServiceAsyncClient".
 
    _features = features.toArray(new WebServiceFeature[features.size()]);

    // Best Practice: Define a singleton port instance and initialize it when 
    // the client container initializes (upon deployment).
    // The singleton port will be available for the life of the servlet.
    // Creation of the singleton port triggers the asynchronous response endpoint to be published
    // and it will remain published until our container (Web application) is undeployed.
    // Note, the destroy() method will be called before this.
    // The singleton port ensures proper/robust operation in both
    // recovery and clustered scenarios.
    _singletonPort = _service.getBackendServicePort(_features);
  }
 
  @Override
  public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {
 
    // TODO: ... Read the servlet request ...
 
    // For this simple example, echo the _lastResponse captured from 
    // an asynchronous DoSomethingResponse response message.
 
    if (_lastResponse != null) {
      res.getWriter().write(_lastResponse);
      _lastResponse = null; // Clear the response so we can get another
      return;
    }
 
    // Set _lastResponse to NULL to to support the invocation against
    // BackendService to generate a new response.
 
    // Best Practice: Synchronize use of client instances.
    // Create another client instance using the *exact* same features used when creating _
    // singletonPort. Note, this port uses the same client ID as the singleton port 
    // and it is effectively the same as the singleton
    // from the perspective of the Web services runtime. 
    // This port will use the asynchronous response endpoint for the client ID, 
    // as it is defined in the _features list.
    BackendService anotherPort =
      _service.getBackendServicePort(_features);
 
    // Set the endpoint address for BackendService.
    ((BindingProvider)anotherPort).getRequestContext().
      put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
          "http://localhost:7001/BestPracticeService/BackendService");
 
    // Add a persistent context property that will be retrieved on the
    // response. This property can be used as a reminder of the context of this
    // request and subsequently process the response. This property will *not*
    // be passed over the wire, so the properties can change independent of the
    // application message.
    Map<String, Serializable> persistentContext =
      (Map<String, Serializable>)((BindingProvider)anotherPort).
        getRequestContext().get(JAXWSProperties.PERSISTENT_CONTEXT);
    String myProperty = "Request " + (++_requestCount);
    persistentContext.put(MY_PROPERTY, myProperty);
    System.out.println("Request being made with MyProperty value: " +
                       myProperty);
 
    // Make the asychronous invocation. The asynchronous handler implementation (set 
    // into the AsyncClientHandlerFeature above) receives the response.
    String request = "Dance and sing";
    System.out.println("Invoking DoSomething asynchronously with request: " +
                       request);
    anotherPort.doSomethingAsync(request);
 
    // Return a canned string indicating the response was not received
    // synchronously. Client will need to invoke the servlet again to get
    // the response.
    res.getWriter().write("Waiting for response...");
 
    // Best Practice: Explicitly close client instances when processing is complete.
    // If not closed explicitly, the port will be closed automatically when it goes out of scope.
    ((java.io.Closeable)anotherPort).close();
  }
 
  @Override
  public void destroy() {
 
    try {
      // Best Practice: Explicitly close client instances when processing is complete.
      // Close the singleton port created during initialization. Note, the asynchronous
      // response endpoint generated by creating _singletonPort *remains*
      // published until our container (Web application) is undeployed.
      ((java.io.Closeable)_singletonPort).close();

      // Upon return, the Web application is undeployed, and the asynchronous
      // response endpoint is stopped (unpublished). At this point,
      // the client ID used for _singletonPort will be unregistered and will no longer be
      // visible from the Administration Console and WLST.
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}