8.6. Building More Robust Web Services

The J2EE 1.4 platform and its Web service technologies conform to the WS-I Basic Profile standards. Conforming to these standards means that the platform uses HTTP as the underlying transport for Web service calls. Unfortunately, from a business communication point of view, the HTTP protocol provides only a low-level degree of robustness. To illustrate, HTTP has no automatic retries to reestablish a connection that disconnects. If the receiving end fails, the entire HTTP call fails and there is no attempt to connect later. This low-level degree of robustness is not necessarily bad, since it means that HTTP can accommodate the participation of all kinds of systems and networks in a distributed Internet environment. HTTP is also a fairly simple protocol that any system can implement with a reasonable amount of effort.

Despite using more reliable hardware and software systems and communication links, enterprises have no guarantee against failures. Failures may occur during a Web service request between a client and a Web service that may leave an application in an ambiguous state. For example, a problem may occur when either party (client or server) fails while in the midst of a JAX-RPC call. Other problems may occur due to lost messages, messages arriving out of order, messages mistakenly seen as duplicates when the same message arrives multiple times, or when the contents of messages are corrupted.

The WS-I Basic Profile does not specify a standard mechanism for handling Web service failures, and hence the J2EE platform standards do not yet include standard support for all such failures. Emerging standards specifications are beginning to enable robust Web service communications using standard interoperable mechanisms. In the meantime, you can achieve more robust Web service communication by adding this functionality to your own application code. It is worth the effort even though you may not be able to handle all types of catastrophic failures.

Writing your own code to make a service more robust may result in a reduction in interoperability. Loss of interoperability can be an unfortunate side effect when implementing solutions ahead of industry standards. In the following sections, we provide some strategies to add some robustness to your Web services. The solutions we propose here are limited to scenarios that involve a single request/reply message exchange between a J2EE client (a servlet or an enterprise bean) and a J2EE Web service endpoint using JAX-RPC as the communication technology.

8.6.1. Use Idempotent Endpoints

Duplication of messages can be a problem in a distributed environment. Different situations can cause duplicate messages, such as sender retries because of communication failures, maliciousness, or bugs in the code. Duplicate messages may also be due to user error, such as when an impatient user presses a submit button more than once. A message receiver must guard against unintended side effects (such as processing an order twice) because of these duplicate messages. Creating idempotent endpoints is one strategy a service can use to handle duplicate messages.

Idempotent refers to a situation where repeated executions of the same event have the same effect as a single execution of the event. Making your endpoints idempotent avoids the problem of a service processing duplicate instances of the same message or request. Even if a client mistakenly—or even maliciously—submits a request such as a purchase order (via a JAX-RPC call) more than once, the effect is the same as if the request was submitted just once.

Service endpoints that only perform read operations are naturally idempotent, since these operations do not have any side effects. For example, the adventure builder application's CRM order tracking Web service is naturally idempotent since it only invokes read operations. For Web services that perform updates or otherwise change state, you need to explicitly build idempotency. One way to do this is to leverage the semantics of the endpoint logic. If you know the effect of a single execution of an operation, you can change the program logic to ensure that multiple executions of the operation have the same result as a single execution.

Let's look at how to design an idempotent endpoint for services that perform updates or change state. First, to detect duplicate requests, you need to assign a correlation identifier to client interactions with a service. (See “Correlating Messages” on page 359.) This correlation identifier can be passed as context information and intercepted and processed by a JAX-RPC handler. (See “Passing Context Information on Web Service Calls” on page 366.) When the request is received, the endpoint should check to see if it is a duplicate request. For example, the order processing center's submitPurchaseOrder method can be made idempotent since we know its operation depends on an order identifier key value. Before executing the business logic, the submitPurchaseOrder method stores the order in the database with the order identifier as its primary key. If the same purchase order is sent again, the second attempt to store the same order identifier in the database results in a duplicate key exception, preventing the order from being processed a second time.

8.6.2. Use Client Retries with Idempotent Endpoints

Idempotent endpoints can help to set up a fault-tolerant Web service interaction. Because multiple service requests have the same effect as a single request, clients of a idempotent service can retry message requests until they are successful, without fear of causing duplicate actions. However, using idempotent endpoints for a fault-tolerant Web service adds to the application's complexity and may adversely affect performance. It is also not interoperable: Specifications for this area are still being designed, and it is doubtful that the standards that ultimately result will work with current custom-built schemes.

With a fault-tolerant design, a client needs to retry sending messages until successful. Typically the client retries only a fixed number of times and then fails. You also design the endpoint to be idempotent. You may also need an acknowledgment message, which can be part of the JAX-RPC reply message or a separate message (if using asynchronous processing).

Often before executing a retry, a client waits briefly so that transient conditions in the network or on the server may clear. When using synchronous Web service calls, the client—especially when it is a real person using the client—may find these waits unacceptable. In cases such as this, consider converting synchronous calls to asynchronous calls. See “Refactoring Synchronous to Asynchronous Interactions” on page 363.

While you design your logic, remember that a failure can occur at any point during a JAX-RPC interaction—when the client makes a call or the service receives it, while the service processes the call, when the service sends a reply or the client receives it, or when the client processes the reply. To recover from such failures, you may want the application code to log interaction state on the client as well as the endpoint. If a failure occurs, you can use the log to recover and finish the interaction, deleting the log when complete or otherwise marking it as finished.

How does a client effectively use a service with this type of robustness? You need to formulate a contract—a set of understood interaction rules—between the client and the service endpoint. These rules might specify that retries are allowed and how many, the overall protocol, and so forth. Generally, the client makes the call and can repeat the call if it is not notified of success within a certain time limit. The service is responsible for detecting duplicate calls.

8.6.3. Handling Asynchronous Interaction Error Conditions

Application design should include handling recoverable and irrecoverable exception conditions in a user-friendly manner. However, error handling is more complicated with distributed systems. Asynchronous interactions add further to application error handling complexity, principally because the interaction requestor is not available to receive an error report. In addition, human intervention may be needed to resolve the error condition.

Let's look at how the adventure builder application handles exception conditions. For its Web service interactions, the adventure builder application gives the interaction requestor a correlation identifier for the submitted request. The service endpoint implementation catches any exceptions that occur when the request is preprocessed. Exceptions might be an invalid XML document or XML parsing and translation errors. For asynchronous interactions, the service endpoint implementation uses JMS to send requests to the processing layer. In those cases, it may also encounter JMS exceptions. For these errors, the endpoint passes a service-specific exception to the requestor. For more information on designing this portion of the exception handling mechanism, see “Handling Exceptions” on page 80.

In adventure builder, exceptions may occur as follows:

  1. Case 1: During any Web service interaction between the Web site and the order processing center or between the order processing center and the external suppliers. These Web service interaction exceptions are synchronous in nature and thus easier to handle than exceptions that arise in cases 2 and 3.

  2. Case 2: Within the order processing center, while processing an order during any stage of the workflow management operation.

  3. Case 3: During the order processing center interaction with partners, such as the suppliers and the credit card service.

Code Example 8.8, which illustrates Case 1, shows a portion of the code for the order processing center Web service interface that receives purchase orders from the Web module. The interface throws two kinds of service-specific exceptions: InvalidPOException, which it throws when the received purchase order is not in the expected format, and ProcessingException, which is throws when there is an error submitting the request to JMS. Note that the platform throws RemoteException when irrecoverable errors, such as network problems, occur.

Code example 8.8. Handling Exceptions in Web Service Calls
public interface PurchaseOrderIntf extends Remote {
   public String submitPurchaseOrder(PurchaseOrder poObject)
          throws InvalidPOException, ProcessingException,
          RemoteException;
}

Exceptions that occur when processing an order either within the order processing center or during the interactions with partners (Cases 2 and 3) require a different handling approach. Since request processing is asynchronous, the client that placed the request is not waiting to receive any exceptions that you might throw. You also need to differentiate exceptions that require a client's intervention from those that may be temporary and possibly resolved on a retry. If an exception requires a client's intervention, you must inform the client in some way. For exceptions that may be temporary, you can designate a certain number of retries before giving up and informing the client.

For example, consider the order processing workflow of the order processing center. After the endpoint receives the purchase order from the client and successfully puts it in the workflow manager's queue, it returns a correlation identifier to the client. The client then goes about its own business. At this point, the workflow manager takes the order through the various workflow stages. Different exceptions can occur in each stage. These exceptions can be broadly categorized as those that require immediate client intervention and those that require an application retry.

Let's look first at exception conditions that require human intervention, such as notifying the customer or an administrator. Examples of such exceptions might be an incoming purchase order that needs to be persisted to the data store but the database table does not exist. Or, the credit card agency might not grant credit authorization for an order because of an invalid credit card number. When these exception conditions occur, it is impossible to continue purchase order processing without human intervention. The adventure builder application informs the customer via e-mail when such conditions happen.

Now let's look at handling exceptions that require the application to retry the operation. As an order progresses through the workflow stages, various exception conditions of a temporary nature may occur. There may be an error placing the order in a JMS queue, the database connection may be busy, a service may not be available, and so forth. For example, the workflow manager might try to invoke the supplier or bank Web service while that latter service is down. Or, the order processing center database may be temporarily unavailable due to a back-up operation. Since temporary error conditions often resolve themselves in a short while, asking the customer to intervene in these situations does not make for the best customer experience.

A strategy for handling temporary error conditions involves keeping a status indicator to signal retrying a failed step at a later time. The adventure builder application workflow manager, as it moves a purchase order through various stages, tracks the status of the order at each stage. If an error occurs, the workflow manager flags the order's status to indicate an error. By using the timer bean mechanism available in the J2EE 1.4 platform, the workflow manager can periodically examine orders with an error status and attempt to move these orders to their next logical stage in the flow. The timer bean activates after a specified period of time, initiating a check of orders flagged with errors and prompting the workflow manager to retry the order's previously failed operation. The order status, in addition to keeping an error flag, tracks the number of retry attempts. The workflow manager seeks human intervention when the number of retry attempts exceeds the fixed number of allowable retries.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset