When you think about implementing Web services with SOAP, you probably have synchronous request-response-style operations in mind. A true Service-Oriented Architecture (SOA), however, is capable of embracing a much broader range of messaging patterns and design strategies. In this tip I begin a short series that focuses on the application of basic Web application design patterns to Web services applications. The patterns presented are not new -- in fact they've been used for more traditional Web applications for many years -- however, many developers either might not be aware of how to implement such strategies within the Web services realm or might not have fully considered how to apply those strategies. My goal is to present a collection of simple, straightforward design alternatives to the request-response model. In order to follow the example it helps to have at least a basic understanding of how to implement Web services in a J2EE environment.
To kick things off, I explore the implementation of asynchronous request and response operations for the purpose of breaking up long-running operations to avoid time-outs or long hang-ups in code execution. The pattern that I implement is a familiar one if you have experience implementing asynchronous queries in traditional HTTP and HTML Web applications.
Figure 1. The asynchronous query pattern
The flow of this pattern is simple:
- The requester submits a request to the Service Provider, who queues up the message and returns a correlation ID that the Requester can use later to check on the status of the request.
- The request processor dequeues the request and processes it. Typically the processing of the request is long-running. Once the processing is complete, the Processor queues a response message.
- At some indeterminate point in the future, the Requester asks the Service Provider if a response to the request is ready. If the response has been queued, the Provider returns that response back to the requester. If the response is not available, the Provider reports such to the Requester, who can choose to either cancel the request or continue to wait, polling the Provider at some selected interval until the response is available.
Kyle Brown has written an excellent article (see Resources) on the application of this pattern in J2EE Servlet applications for Java Ranch, a Java development resource Web site. In his article, Kyle adequately discusses the motivations and basic design issues involved in the basic implementation of this pattern. Not surprisingly, the implementation does not change very much in a Web services implementation of the pattern.
To illustrate the asynchronous query pattern, I implement a simple example Web service that has absolutely no practical value beyond illustrating the concepts of the pattern. All the service does is transform three lowercase String input values to upper case after forcing a 10 second pause in the execution to simulate a long running process.
To implement this service, two Web service operations are exposed: submitRequest and checkResponse. What each of the operations does is self-explanatory. Listing 1 shows the WSDL describing the service interface.
Listing 1. AsyncService.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
targetNamespace="http://one.wspattern.developerworks.ibm.com"
xmlns:impl="http://one.wspattern.developerworks.ibm.com"
xmlns:intf="http://one.wspattern.developerworks.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema
targetNamespace="http://one.wspattern.developerworks.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://one.wspattern.developerworks.ibm.com"
xmlns:intf="http://one.wspattern.developerworks.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="ResponseCheck">
<sequence>
<element name="correlationID" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="ResponseCheck" nillable="true" type="impl:ResponseCheck"/>
<complexType name="Response">
<sequence>
<element name="type" type="xsd:int"/>
<element name="correlationID" nillable="true" type="xsd:string"/>
<element name="refresh" type="xsd:int"/>
<element name="a" nillable="true" type="xsd:string"/>
<element name="b" nillable="true" type="xsd:string"/>
<element name="c" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="Response" nillable="true" type="impl:Response"/>
<complexType name="Request">
<sequence>
<element name="a" nillable="true" type="xsd:string"/>
<element name="b" nillable="true" type="xsd:string"/>
<element name="c" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="Request" nillable="true" type="impl:Request"/>
</schema>
</wsdl:types>
<wsdl:message name="submitRequestRequest">
<wsdl:part name="request" type="intf:Request"/>
</wsdl:message>
<wsdl:message name="checkResponseResponse">
<wsdl:part name="checkResponseReturn" type="intf:Response"/>
</wsdl:message>
<wsdl:message name="checkResponseRequest">
<wsdl:part name="check" type="intf:ResponseCheck"/>
</wsdl:message>
<wsdl:message name="submitRequestResponse">
<wsdl:part name="submitRequestReturn" type="intf:Response"/>
</wsdl:message>
<wsdl:portType name="AsyncService">
<wsdl:operation name="checkResponse" parameterOrder="check">
<wsdl:input
message="intf:checkResponseRequest"
name="checkResponseRequest"/>
<wsdl:output
message="intf:checkResponseResponse"
name="checkResponseResponse"/>
</wsdl:operation>
<wsdl:operation name="submitRequest" parameterOrder="request">
<wsdl:input
message="intf:submitRequestRequest"
name="submitRequestRequest"/>
<wsdl:output
message="intf:submitRequestResponse"
name="submitRequestResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="AsyncServiceSoapBinding" type="intf:AsyncService">
<wsdlsoap:binding
style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="checkResponse">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="checkResponseRequest">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:input>
<wsdl:output name="checkResponseResponse">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="submitRequest">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="submitRequestRequest">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:input>
<wsdl:output name="submitRequestResponse">
<wsdlsoap:body
namespace="http://one.wspattern.developerworks.ibm.com"
use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="AsyncServiceService">
<wsdl:port binding="intf:AsyncServiceSoapBinding" name="AsyncService">
<wsdlsoap:address
location="http://localhost:9080/WSPattern1/services/AsyncService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
There are a couple of things to note about this interface:
- The service uses an RPC/Literal encoding style.
- Both the
submitRequestandcheckResponseoperations return an object calledResponse. There are two flavors ofResponse, identified by the type property. A type property value of 0 references a Refresh Response, and a type property value of 1 references a Request Response. Refresh Response indicates that a response is not yet available and that the requester should submit a newcheckResponseoperation no sooner than the value specified by the refresh property (equivalent to theHTTP META Refreshmechanism discussed in Kyle Brown’s article referenced above). A Request Response contains the three upper-cased input strings and represents the completion of the request processing. - The Refresh Response includes a
Correlation IDproperty whose value is used as the input for thecheckResponseoperation. This identifier is the only means for the client to correlate its initial request with the response. There are other ways to implement this, as I discuss a bit later.
Listing 2 shows a typical message exchange for this service.
Listing 2. AsyncService message exchange
Initial Request
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<m:submitRequest
xmlns:m="http://one.wspattern.developerworks.ibm.com">
<request>
<a>String</a>
<b>String</b>
<c>String</c>
</request>
</m:submitRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
submitRequest Response
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:submitRequestResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<submitRequestReturn>
<type>0</type>
<correlationID>1097517621904</correlationID>
<refresh>10000</refresh>
<a xsi:nil="true"/>
<b xsi:nil="true"/>
<c xsi:nil="true"/>
</submitRequestReturn>
</p155:submitRequestResponse>
</soapenv:Body>
</soapenv:Envelope>
Initial checkResponse attempt, no response available, submitted after 10000 miliseconds
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:checkResponseResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<checkResponseReturn>
<type>0</type>
<correlationID>1097517621904</correlationID>
<refresh>10000</refresh>
<a xsi:nil="true"/>
<b xsi:nil="true"/>
<c xsi:nil="true"/>
</checkResponseReturn>
</p155:checkResponseResponse>
</soapenv:Body>
</soapenv:Envelope>
Second checkResponse attempt, submitted after 10000 miliseconds
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<p155:checkResponseResponse
xmlns:p155="http://one.wspattern.developerworks.ibm.com">
<checkResponseReturn>
<type>1</type>
<correlationID xsi:nil="true"/>
<refresh>0</refresh>
<a>STRING</a>
<b>STRING</b>
<c>STRING</c>
</checkResponseReturn>
</p155:checkResponseResponse>
</soapenv:Body>
</soapenv:Envelope>
|
Implementing the asynchronous query pattern service is a straightforward application of the Java Messaging Service. For the purposes of this example, I use the OpenJMS open source JMS provider implementation (see Resources) and the IBM® WebSphere® Application Server V5 (Application Server). I use the default OpenJMS configuration and implement a JSR-109-compliant J2EE Web service. I wrote the code for the example using WebSphere Studio Application Developer (Application Developer) V5.1, which you can download from developerWorks (see Resources). I also provide an EAR file (see Resources) in case you don't have access to Application Developer.
There are two server-side components that need to be implemented: the request processor and the Web service implementation. The request processor is tasked with the job of pulling requests off the queue and executing the "long running" 10 second capitalization process. The service implementation is tasked with receiving requests from Web services clients and queuing them up for processing and for delivering responses to clients following a checkResponse operation.
In a typical J2EE application, the request processor would be implemented as a Message Driven Bean according to the JMS specification. In this example, I use a simple HTTP Servlet that implements the JMS MessageListener interface. The servlet is configured to init on server startup, making the listener available for all requests that might come in. As soon as a request is queued, it is delivered to the listening servlet.
Listing 3. JNDIListenerServlet.java
package com.ibm.developerworks.wspattern.one.helper;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class JNDIListenerServlet
extends HttpServlet
implements Servlet, MessageListener {
private Context context;
private QueueConnection connection;
private QueueSession session;
private Queue queue;
private QueueReceiver receiver;
public void init()
throws ServletException {
super.init();
try {
context = JNDIHelper.getInitialContext();
connection = JNDIHelper.getConnection(context);
session = JNDIHelper.getSession(connection);
queue = JNDIHelper.getQueue(context);
receiver = JNDIHelper.getQueueReceiver(session, queue);
receiver.setMessageListener(this);
System.out.println("Listener servlet is Listening");
} catch (Exception e) {}
}
public void destroy() {
try {
connection.close();
} catch (Exception e) {}
}
public void onMessage(Message message) {
try {
System.out.println("Processing message " + message.getJMSCorrelationID());
Thread.sleep(10 * 1000); // sleep for ten seconds
Queue responseQueue = JNDIHelper.getResponseQueue(context);
QueueSender sender = JNDIHelper.getQueueSender(session,responseQueue);
MapMessage request = (MapMessage)message;
MapMessage response = session.createMapMessage();
response.setJMSCorrelationID(request.getJMSCorrelationID());
for (Enumeration e = request.getMapNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
try {
response.setString(
name,
request.getString(name).toUpperCase());
} catch (Exception ex) {}
}
sender.send(response);
} catch (Exception e) {
System.out.println("==================");
try {
System.out.println(
"THERE WAS AN ERROR PROCESSING THE MESSAGE! " +
message.getJMSCorrelationID());
} catch (Exception ex) {}
e.printStackTrace(System.out);
System.out.println("==================");
}
}
}
|
The JNDIListenerServlet and the service implementation both use a simple helper class created for this application that hides away the details of initializing the JMS connection and session.
Listing 4. JNDIHelper.java
package com.ibm.developerworks.wspattern.one.helper;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JNDIHelper {
private static Context context;
public static Context getInitialContext()
throws NamingException {
if (context == null) {
Hashtable properties = new Hashtable();
properties.put(
Context.INITIAL_CONTEXT_FACTORY,
"org.exolab.jms.jndi.InitialContextFactory");
properties.put(
Context.PROVIDER_URL,
"rmi://localhost:1099");
context = new InitialContext(properties);
}
return context;
}
public static QueueConnection getConnection(
Context context)
throws NamingException,
JMSException {
QueueConnectionFactory factory =
(QueueConnectionFactory) context.lookup(
"JmsQueueConnectionFactory");
QueueConnection connection = factory.createQueueConnection();
connection.start();
return connection;
}
public static QueueSession getSession(QueueConnection connection)
throws JMSException {
QueueSession session =
connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
return session;
}
public static Queue getQueue(Context context)
throws NamingException {
Queue queue = (Queue) context.lookup("queue1");
return queue;
}
public static Queue getResponseQueue(Context context)
throws NamingException {
Queue queue = (Queue) context.lookup("queue2");
return queue;
}
public static QueueSender getQueueSender(
QueueSession session,
Queue queue)
throws JMSException {
QueueSender sender = session.createSender(queue);
return sender;
}
public static QueueReceiver getQueueReceiver(
QueueSession session,
Queue queue)
throws JMSException {
QueueReceiver receiver = session.createReceiver(queue);
return receiver;
}
public static QueueReceiver getQueueReceiver(
QueueSession session,
Queue queue,
String selector)
throws JMSException {
QueueReceiver receiver = session.createReceiver(queue, selector);
return receiver;
}
}
|
Once the servlet is created, edit the Web application’s web.xml so that the servlet is configured to initialize on server startup. When the servlet initializes, it opens a JMS connection and registers itself as a listener on OpenJMS’s default message queue.
The second step is to create the service implementation. This is where working with Application Developer comes in handy because of the various artifacts that need to be generated in order to get a working JSR-109 Web service. Here I focus strictly on the service implementation class. Please refer to the code that you can download by clicking on the Code icon at the top or bottom of this tip to see the other various Java and XML configuration files that Application Server requires.
Listing 5. AsyncService.java
package com.ibm.developerworks.wspattern.one;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import com.ibm.developerworks.wspattern.one.helper.JNDIHelper;
public class AsyncService {
public Response submitRequest(Request request) {
Response response = null;
try {
Context context = JNDIHelper.getInitialContext();
QueueConnection connection = JNDIHelper.getConnection(context);
QueueSession session = JNDIHelper.getSession(connection);
Queue queue = JNDIHelper.getQueue(context);
QueueSender sender = JNDIHelper.getQueueSender(session,queue);
MapMessage message = session.createMapMessage();
String corrID = Long.toString(System.currentTimeMillis());
message.setJMSCorrelationID(corrID);
message.setString("one", request.getA());
message.setString("two", request.getB());
message.setString("three", request.getC());
sender.send(message);
response = new Response();
response.setType(Response.TYPE_REFRESH);
response.setCorrelationID(corrID);
response.setRefresh(10 * 1000);
return response;
} catch (Exception e) {
response = new Response();
response.setType(Response.TYPE_RESPONSE);
response.setA(e.getMessage());
}
return response;
}
public Response checkResponse(ResponseCheck check) {
String corrID = check.getCorrelationID();
Response response = null;
try {
Context context = JNDIHelper.getInitialContext();
QueueConnection connection = JNDIHelper.getConnection(context);
QueueSession session = JNDIHelper.getSession(connection);
Queue queue = JNDIHelper.getResponseQueue(context);
String selector = "JMSCorrelationID = '" + corrID + "'";
QueueReceiver receiver = JNDIHelper.getQueueReceiver(session, queue, selector);
Message message = receiver.receiveNoWait();
if (message == null) {
response = new Response();
response.setType(Response.TYPE_REFRESH);
response.setRefresh(10 * 1000);
response.setCorrelationID(corrID);
} else {
MapMessage resp = (MapMessage)message;
response = new Response();
response.setType(Response.TYPE_RESPONSE);
response.setA(resp.getString("one"));
response.setB(resp.getString("two"));
response.setC(resp.getString("three"));
}
} catch (Exception e) {}
return response;
}
}
|
There really shouldn’t be any surprises here. The submitRequest method prepares a JMS MapMessage based on the input parameters. This map message consists of three string values. The message is then sent to the queue and a Refresh Response containing the correlation ID is prepared and returned to the client.
The checkResponse operation grabs the correlation ID from the input parameters and opens a connection to the response queue, asking it to deliver any messages with the appropriate correlation ID. If no message exists, the operation does not wait around for one; it simply prepares another Refresh Response with a new refresh interval period and returns that back to the caller. If a message is delivered, the operation prepares and returns an appropriate Request Response.
Deploy the Web service, start your OpenJMS and WebSphere servers, and your asynchronous query pattern Web service will be up and running.
Central to the success of the asynchronous query pattern is the ability for the Web services client and service provider to correlate requests and responses. In the example presented here, you can invent a simple, yet one-off correlation ID and refresh timer mechanism that is specific to this single example application. It would be possible, however, to use some combination of WS-* specifications to achieve the same result. A WS-Addressing Endpoint Reference or a WS-Transaction Coordination Context could have easily contained the correlation ID and refresh timer values. Regardless, use of this pattern is specific to your particular applications, and regardless of whether or not you use standard SOAP header elements and various WS-* specifications, the behavior that each of your operations implements must be well-defined and well-documented.
Further, the example implemented here uses a traditional SOAP request-response messaging pattern to submit requests and receive responses. Alternatively, it would be possible to use a REST-style model in which requests are HTTP POSTed to a servlet and responses are retrieved using an HTTP GET request. Either approach is equally valid and has its relative strengths and weaknesses compared to one another and should be selected depending on the unique needs of your application. For instance, should your checkResponse operation require the use of WS-Security-based authentication, using a REST-style interaction pattern would not make sense.
Finally, you could easily imagine extending the scope of this example to allow requesters to submit detailed status queries on long-running operations or even to cancel operations that are in progress. Illustrating these various possibilities is beyond the scope of this article so I leave it as an exercise for you to explore on your own.
| Description | Name | Size | Download method |
|---|---|---|---|
| WebSphere deployable EAR file | ws-tip-altdesign1ear.ear | 701 KB | HTTP |
| Example program source files | ws-tip-altdesign1code.zip | 719 KB | HTTP |
Information about download methods
- Read the other installments of the "Learn simple, practical Web services design patterns" series:
- Part 2: Encapsulate business logic with a command facade pattern
- Part 3: Creating flexible Web service implementations with the Router pattern
- Part 4: Understand and implement the message bus pattern
- Kyle Brown's article, "Asynchronous Queries in J2EE," provides the design foundation for the discussion about the asynchronous query pattern presented here, published at Java Ranch.
- Download WebSphere Studio Application Developer, which I used to write the example application.
- You can also get more information and downloads for the open source OpenJMS provider.
- See James Snell's developerWorks blog for more discussion about Emerging Technology topics including (but not limited to) Web services.
- Browse for books on these and other technical topics.
- 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.

James Snell is a member of the IBM Emerging Technologies Toolkit team and has spent the past few years focusing on emerging Web services technologies and standards. He maintains a weblog on developerWorks focused on emerging technologies.



