Client quality reporting for J2EE Web services

Use SOAP attachments to report client response times for Web services

Web services are fast becoming the preferred architecture for implementing client-server systems. The advantage of Web services is that a business can formally define a set of services, and then generate the entire client and server codebase for communications, thus simplifying new client access to legacy Web resources.

However, while Web services ease the building of client-server systems, monitoring service quality is a significant problem. Consider a client application that submits a transaction on a user's behalf. A business transaction usually involves several Web service calls: an initial call to submit a work item, subsequent calls to check for completion, and a final call to get the result. Each call is a distinct HTTP/SOAP (Simple Object Access Protocol) exchange. Put yourself in the position of an IT department responsible for monitoring server load and forecasting future needs. The fundamental question you must answer is, "How well am I serving my clients now, and what will I need to serve them in the future?"

Answering this question is difficult if you have only HTTP logs. Clients care about transactions, but since each transaction consists of several HTTP requests, the best you can do to estimate service quality is to develop custom data-mining software that cursors through HTTP logs and builds a model of user transactions. Even so, the information you have is still limited because it can't reflect network transport or client application overhead.

This article's key idea is that transaction service quality is best measured by the client. The approach adopted here allows the client to record actual transaction response times. A client application uploads response time reports to the server by appending them to the next up-bound transaction request. The server strips off these attachments and queues them for storage and offline analysis.

Architecture

One objective of the client-based metrics-recording architecture is that the recording infrastructure must be lightweight, both in terms of runtime overhead and the ease of adding it to an existing implementation. We also want an architecture that places no constraints on the services offered—we'd like to be able to add it to an existing client-server system that uses Web services as easily as possible.

Another objective of our architecture is that we don't want to make the business application itself less reliable. We'll be introducing some new, lightweight steps into the application process workflow. We must ensure any failures in these new steps are handled because we don't want a business transaction to fail just because we couldn't gather metrics on it.

The following diagram shows a typical J2EE (Java 2 Platform, Enterprise Edition) Web service client-server application. Typical components appear in black; the new components that we will add for metrics gathering appear in red.

J2EE Web services: Metrics-gathering architecture

The "J2EE Application Server" region represents existing server resources. These are the Enterprise JavaBeans (EJB) components that process client requests. A tool automatically generates the Web services package. The EJB components and the associated Web services module deploy to a J2EE server as a J2EE application. When the application deploys, a client can determine available services by invoking the application WSDL (Web Services Description Language) service, which provides a formal definition of the services offered by the application.

The "Application Client" region is composed of an application component and a Web services package. The application component implements business logic and a user interface. The Web services package is generated automatically through a WSDL compiler and the WSDL service provided by the J2EE server application.

Conceptually, the overall system has two layers. The application layer has EJB components on the server side, an application on the client side. The Web services layer has a server implementation and a client implementation, both of which generate automatically.

Typically, a business transaction by a user consists of many server calls. The first initiates a transaction, returning a "handle" to the client. Subsequent calls inquire about transaction completion—the client calls a service with the handle to check if the transaction has finished. Usually a final call obtains a completed transaction's status. Thus, a business model, implemented within the client application, relates a transaction to low-level server calls.

We can add the metrics-gathering components into our standard J2EE Web services architecture. The figure's Payload package deploys on both the server and client. I'll provide more details about this package later, but in an architectural sense, it offers several services. For example, client applications can use beginTransaction() and commitTransaction() to delimit transactions and record elapsed times. The client Web services package uses the Payload package to serialize a metrics report to a SOAP message attachment. The server Web services package uses the Payload package to strip a SOAP attachment from an incoming request, and queue it for logging and reporting purposes.

There is little overhead in this implementation because the client and server exchange no new traffic—a metrics report on a completed transaction rides along with the next client request. The only new processing introduced is some serialization on the client and attachment queuing on the server. The implementation is lightweight because only a single line of code is added to each application Web method, and this code is always the same—it doesn't change if the Web method signature changes.

The last new component introduced is a message-driven EJB component that reads serialized metrics attachments. Typically these would be logged to a database so the enterprise can maintain a historical record of transaction service quality. The enterprise can use this database to relate actual transaction response times to server resource utilization, gaining critical insight into which server components are the key service bottlenecks. Since attachments are queued, the metrics-reader EJB component should run on a different J2EE server instance because we don't want metrics logging to compete for resources with business EJB components.

Implementation

In this section, I show how the metrics code integrates with a simple J2EE client-server application. All code is available for download from Resources; the following section shows how to build and run this code with Sun ONE (Open Network Environment) Application Framework.

Application server prototype

In our example, the server application consists of a single session bean. There is no loss of generality here because the internal server EJB design doesn't affect the metrics-recording architecture. The same metrics approach can be used even if many different EJB components exist in the server.

The XactBean EJB exposes three business methods: submitWork(), checkWork(), and getResult(). Each is a distinct Web method. The client application uses all of them to model a client that submits multiple Web requests to execute a user transaction.

The session bean that represents the server application is shown below:

package TransactionProcessor;
import javax.ejb.*;
import java.rmi.server.*;
import java.util.*;
/**
 * Created May 19, 2003 10:07:39 PM
 * Code generated by the Sun ONE Studio EJB Builder
 * @author Brian Connolly brian@ideajungle.com
 */
public class XactBean implements javax.ejb.SessionBean {
    private javax.ejb.SessionContext context;
    
    private int mRandom;    
    
    /**
     * @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
     */
    public void setSessionContext(javax.ejb.SessionContext aContext) {
        context=aContext;
    }
    
    
    /**
     * @see javax.ejb.SessionBean#ejbActivate()
     */
    public void ejbActivate() {
        
    }
    
    
    /**
     * @see javax.ejb.SessionBean#ejbPassivate()
     */
    public void ejbPassivate() {
        
    }
    
    
    /**
     * @see javax.ejb.SessionBean#ejbRemove()
     */
    public void ejbRemove() {
        
    }
    
    
    /**
     * See section 7.10.3 of the EJB 2.0 specification
     */
    public void ejbCreate() {
        Random r = new Random();
        
        mRandom = r.nextInt(10000);
    }
    
    public java.lang.String SubmitWork(java.lang.String Work) {
        return new Integer(mRandom).toString();
    }
    
    public boolean CheckWork(java.lang.String Xact) {
        return true;
    }
    
    public java.lang.String GetResult(java.lang.String Xact) {
        return new Integer(mRandom).toString();
    }
    
}

These three methods model a client transaction. In submitWork(), we generate a handle as a random number—in reality this would be a unique transaction identifier. checkWork() always returns true. In a real system, the client would pass a transaction identifier, and this method would check with a backend transaction manager to see if the transaction was finished. Similarly, in a real system, getResult() would return a complex transaction completion record.

Server Web services package

The server Web services package generates automatically. In Sun ONE Studio, a Web module can be created by selecting a set of EJB Java methods, and the Web services package classes can generate from this module.

The package consists of many classes and interfaces. The key item here is the <ServiceName>ServantInterface_Tie class, where <ServiceName> is the name of the service. The Tie class is the Web services module's uppermost stack; it binds the incoming service invocation to the EJB component it was generated from. We need to modify only this generated class to add metrics recording.

Tie contains many methods, but the only ones we modify are the ones associated with the EJB business methods: invoke_<X>, where <X> is the name of the EJB business method. We add an import Payload.*; to Tie, and we make a small modification to each business method. Let's look at the invoke_SubmitWork() method:

/*
  * This method does the actual method invocation for operation: SubmitWork
  */
 private void invoke_SubmitWork(StreamingHandlerState state) throws Exception {
    TransactionService.XactServiceGenServer.
      XactServiceServantInterface_SubmitWork_RequestStruct 
         myXactServiceServantInterface_SubmitWork_RequestStruct = null;
    Object myXactServiceServantInterface_SubmitWork_RequestStructObj =
    state.getRequest().getBody().getValue();
   
    /* Line added to generated method: */
    Serializer.queueFirstAttachmentText(state.getMessageContext());
    if (myXactServiceServantInterface_SubmitWork_RequestStructObj 
      instanceof SOAPDeserializationState) {
       myXactServiceServantInterface_SubmitWork_RequestStruct =
         (TransactionService.XactServiceGenServer.
            XactServiceServantInterface_SubmitWork_RequestStruct)
               ((SOAPDeserializationState)
                  myXactServiceServantInterface_SubmitWork_RequestStructObj)
                     .getInstance();
    } else {
       myXactServiceServantInterface_SubmitWork_RequestStruct =
       (TransactionService.XactServiceGenServer.
         XactServiceServantInterface_SubmitWork_RequestStruct)
            myXactServiceServantInterface_SubmitWork_RequestStructObj;
    }
    java.lang.String result =
    ((TransactionService.XactServiceGenServer.XactServiceServantInterface) 
      getTarget()).SubmitWork
         (myXactServiceServantInterface_SubmitWork_RequestStruct.getString_1());
    TransactionService.XactServiceGenServer.
      XactServiceServantInterface_SubmitWork_ResponseStruct 
         myXactServiceServantInterface_SubmitWork_ResponseStruct =
            new TransactionService.XactServiceGenServer
               .XactServiceServantInterface_SubmitWork_ResponseStruct();
    SOAPHeaderBlockInfo headerInfo;
    myXactServiceServantInterface_SubmitWork_ResponseStruct.setResult(result);
    SOAPBlockInfo bodyBlock = new SOAPBlockInfo
      (ns1_SubmitWork_SubmitWorkResponse_QNAME);
    bodyBlock.setValue(myXactServiceServantInterface_SubmitWork_ResponseStruct);
    bodyBlock.setSerializer
      (myXactServiceServantInterface_SubmitWork_ResponseStruct_SOAPSerializer);
    state.getResponse().setBody(bodyBlock);
 }

We added a single line to invoke_SubmitWork():

Serializer.queueFirstAttachmentText(state.getMessageContext());

getMessageContext() returns an object implementing the javax.xml.rpc.handler.soap.SOAPMessageContext interface. This object provides access to the current SOAP message. We pass the object implementing the SOAPMessageContext interface to a static method in Payload.Serializer. The static method gets the XML string from the first message attachment and queues it for the metrics-handler EJB component.

We make identical changes to each of the invoke_<X> methods.

The Payload package

The Payload package is used on both the client and server. It consists of three classes: ClientReport, CurrentReport, and Serializer.

The ClientReport represents a client metrics report:

1 2 3 4 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more