Cache SOAP services on the client side

Design patterns let you cache SOAP services and improve performance

The Web services revolution has started. New standards and technologies enable and integrate distributed Web services. SOAP (Simple Object Access Protocol), an XML RPC (remote procedure call)/messaging protocol, underlies it all. Simple by name, simple by definition, and simple to use, SOAP lets you request a service using simple XML and receive a simple XML response.

SOAP is proving its mettle in the enterprise application space. Developers employ the protocol to integrate disparate enterprise applications, and to develop new distributed and scalable enterprise applications that use XML for cross-component messaging.

SOAP's creators, in their desire for simplicity, deliberately did not build a complex distributed object model; they left that difficult task to the application developer. However, SOAP's simplicity means a simple SOAP implementation in an enterprise application will be stateless and could fall prey to performance issues. Indeed, repeated SOAP-client calls to access server state can choke a network.

In response, we describe transparent client-side caching for SOAP services using the Business Delegate and Cache Management design patterns, and offer an implementation that completely hides from the client application the lookup, invocation, and caching of Web services. Moreover, we introduce a mechanism for automatic client-cache synchronization with the SOAP service provider.

We assume you have familiarity with SOAP installation and service deployment. We do not detail how SOAP works or how to make it work. See Resources for references to Websites where you can find more information.

Note: You can download this article's complete source code in Resources.

The problem: Web services in an application world

When developing rich Java clients with SOAP-based servers as data providers, you must address three main issues: performance, performance, and performance.

If your client application accesses the same information repeatedly from the server, you'll quickly realize the server's performance, and consequently your application's response time, is not fast enough. Further, when hundreds of client applications hit the server simultaneously, the performance degrades even faster. That's when you remember the lesson from client/server databases development: thou shalt always cache locally! Invoking a SOAP call produces costs similar to running an SQL statement in a relational database; maybe even more so. Indeed, a database likely gets accessed whenever a SOAP call is made. So a SOAP call's cost includes network latency, CPU cycles at the SOAP server, and SQL latency at the database server.

A problem, however, with caching locally: the cache might become inconsistent with the original data source; changes made to the SOAP server after the cache loads go unreflected in the cache (read inconsistency), and changes made to the cache go unreflected at SOAP server (write inconsistency).

The problem domain: stock transactions

Because the problem proves a little complex, we offer an example that highlights the need for fast response without requiring you to learn a new problem domain.

Believing that most Java developers have monitored stock ticker quotes (as spectators, if not as investors) in the last few years, for our example we picked the stock transactions domain. (The stock prices reported in the example code are from December 19, 2001.)

Our simple demonstration application deals with only a StockQuote object and three SOAP service methods. The StockQuote object has three attributes:

  • symbol: The stock symbol.
  • name: The stock's name.
  • value: The stock price's current dollar value.

At the SOAP level, the three service methods comprise:

  • Get Quote: Given a symbol, returns a StockQuote object containing the latest stock price for the symbol.
  • Add Quote: Given a StockQuote object, adds the information to the server-side database. If the database already contains an entry for the stock, the service updates the database with the new information.
  • Get All Quotes: Returns a Hashtable of the StockQuote objects stored in the database.

In our example, a Swing-based Java client displays a list of all stocks and adds or updates individual stock details. The client interacts with a SOAP server to synchronize with a server repository.

The application overview

The Demo application, as seen in Figure 1, features two SOAP services deployed at the SOAP server. First, StockQuoteService handles the stock quote methods, while the NotificationService performs notification between the business services and the clients.

Figure 1. The Demo application

The Demo application client includes two main panels. The top panel displays the stocks available at the server, while the bottom panel lets you add or update the stocks.

When you launch the client application, it registers with the SOAP server for notification updates and loads the stock-quotes table with the Get All Quotes SOAP service method.

When you add or update a stock using the edit panel, the client application updates the data repository at the SOAP server by invoking the Add Quote SOAP service method. That SOAP method call awakens the server-side notification handler, which, in turn, notifies the registered clients about the data change.

When the client receives the notification (for which it registered earlier), it expires the local cache and reloads the data by again invoking the Get All Quotes method.

The framework and assumptions

Next, let's see a high-level description of the stocks package's components, as illustrated in Figure 2. The package contains the code required to run Demo. You may skip this section and return when you're ready to compile the code.

The sub packages include:

  • soapservices: The SOAP services' definitions
  • clientservices: The common classes required by the client for SOAP service invocation and other functions
  • businessdelegates: The business delegate classes for the SOAP services
  • gui: The Swing GUI (graphical user interface) code to demonstrate the example
  • util: The StockQuote class definition
Figure 2. The package hierarchy

Note: The package names relate specifically to the Demo application. In a real-world application, the classes defined here would probably reside in different framework packages.

Let's examine what each package includes.

The soapservices package

The soapservices package includes the following classes:

  • StockQuoteService: Provides the three basic services methods that demonstrate the stock quote application
  • NotificationService: Notifies the client of stock quote changes
  • NotificationHandler: With this singleton class, the StockQuoteService class notifies the NotificationService

The clientservices package

The clientservices package includes the following classes:

  • SOAPServiceFacade: Hides the SOAP server lookup, initialization, and call invocation
  • Cache: Used by business delegates for local caching
  • ClientNotificationHandler: Notifies business delegates about cache expiry
  • CacheExpiredListener: With this interface, the ClientNotificationHandler notifies the business delegate class about cache expiry
  • DataChangeListener: The business delegates employ this interface to forward the notification to other components (like GUI components)

The businessdelegates package

The businessdelegates package includes the following classes:

  • StockQuoteServiceBusinessDelegate: The business delegate for the StockQuoteService SOAP service
  • NotificationServiceBusinessDelegate: The business delegate for the NotificationService SOAP service

The gui package

For its part, the gui package features the following classes:

  • Demo: Launches the demo client application
  • DemoFrame: The Swing frame container for all panels
  • DemoViewPanel: The view panel that displays the stock table
  • StockQuoteDataSource: The table model for the JTable component in DemoViewPanel
  • DemoEditPanel: Displays, updates, or adds selected stocks

The Demo application's services do not interact with a database; they work over a simple Hashtable that you can easily replace with a JDBC (Java Database Connectivity) interface. Moreover, to keep things simple and brief, the Demo application's code doesn't handle many exceptions. As a further caveat, this approach for handling SOAP services at the client side may prove difficult to apply in a dynamic Web services environment.

The business delegate

According to the J2EE (Java 2 Platform, Enterprise Edition) Patterns Catalog, a business delegate can "reduce coupling between presentation-tier clients and business services. The business delegate hides the underlying implementation details of the business service, such as lookup and access details of the EJB (Enterprise JavaBeans) Architecture."

The Business Delegate design pattern perfectly applies to our application in which a rich Java client interfaces with business services over SOAP. Although a business delegate for each service may seem overly complex, the pattern helps hide implementation, lookup, invocation, and caching details from the client application.

The SOAP StockQuoteService service provides stock quote services for SOAP clients. Every SOAP client, however, must handle the SOAP lookup, invocation, and caching by itself. The StockQuoteServiceBusinessDelegate, as obvious from the name, is the client-side delegate for the server-side StockQuoteService. It provides direct method-level accessors to the StockQuoteService service methods, internally performing SOAP lookup, invocation, caching, and notification.

The StockQuoteServiceBusinessDelegate class initializes a SOAPServiceFacade instance to handle all SOAP-related activities and initializes a static Cache shared by all instances of itself for local caching. The StockQuoteServiceBusinessDelegate's hook registers a client component that implements the DataChangeListener interface, enabling the client component updates when the local cache updates. The class also implements the CacheExpiredListener interface so that ClientNotificationHandler can register the business delegate for notification.

Here's the StockQuoteServiceBusinessDelegate's initializer:

public StockQuoteServiceBusinessDelegate()  throws Exception 
{
   soap = new SOAPServiceFacade();
   soap.init(
      serviceName,                   // Service name
      serviceName,                   // Registry mapping for SQ
      "stocks.util.StockQuote",      // Stock quote object name
      stocks.util.StockQuote.class   // Stock quote class
   );
   if (cache == null)
      cache = new Cache();
}

Next, you see a sample SOAP service-method wrapper in the StockQuoteServiceBusinessDelegate business delegate:

public boolean addQuote(StockQuote stockQuote) 
{
   boolean status = false;
   if(stockQuote != null) 
   {
      synchronized(cache)
      {
         System.out.println( new Date() + "> Stock Quote
            Service Business Delegate :: Add Quote -> ["
            + stockQuote + "]");
         Vector params = new Vector();
         params.addElement(new Parameter("stockQuote", 
            StockQuote.class, stockQuote, null));
         Boolean bool = (Boolean) soap.invoke("addQuote", params);
         if (bool == null) 
         {
            status = false;
         }
         else
         {
            status = bool.booleanValue();
         }
         // Store the data in the cache
         if (status == true)
         {
            System.out.println( new Date() + "> Stock Quote
               Service Business Delegate :: Updating Cache for
               [" + stockQuote.getSymbol() + "] ...");
            cache.addObject(stockQuote.getSymbol(), stockQuote);
         }
      }
   }
   return (status);
}

Compare the above code to the client code that accesses the SOAP service using the BusinessDelegate, as seen here:

// Create a Stock Quote object from data in GUI components.
StockQuote stockQuote = getData(); 
if(stockQuote != null) 
{
   try
   {
      getStockQuoteServiceBusinessDelegate().addQuote(stockQuote);
   }
   catch (Exception e) { e.printStackTrace(); } 
}

Figure 3's sequence diagram shows how the editing action ripples across the components.

Figure 3. The editing sequence diagram. Click on thumbnail to view full-size image.

Using a business delegate frees you, the client application developer, from worrying about the server's location, the invoked service type, how to invoke it, or whether a remote service exists at all. Instead, you focus on the client application's features.

Keep in mind that these business delegates do not require a one-to-one mapping with the Web services. They could provide business service views customized for the specific client application requirements.

The cache

The business delegate class initializes the cache in its constructor. The cache loads when the getQuote() and getAllQuote() services invoke, and updates when a StockQuote is added or updates. Moreover, the cache expires when the ClientNotificationHandler receives an expiry notification from the server.

As per Patterns in Java author Mark Grand, the Cache Management pattern's cache includes two methods: addObject() and fetchObject(). A CacheManager manages the cache using ObjectKey handles. Again, the pattern's power resides in its simplicity. In our implementation, the business delegate acts as the CacheManager since each business delegate possesses its own cache. The ObjectKey is the StockQuote object. Finally, the Cache's internal data structure is a hash table.

1 2 Page 1