Declarative Caching Services for Spring

by Alex Ruiz
05/19/2006

Abstract

Scalability, reliability, and high performance are must-have requirements in modern J2EE applications. Regardless of the type of client, request processing usually involves actions that have a negative effect on performance, such as information gathering from disparate data sources and execution of complex calculations, to say the least. Caching is one of the most essential practices that improve the performance of enterprise applications. Every application has its own caching requirements that must be constantly adjusted to ensure that no performance degradation occurs. Enterprise applications need an easy way to add and tune caching functionality without touching the application's code. This article introduces a code-free caching framework for Spring-based applications, Declarative Caching Services for Spring.

Caching Overview

In simple terms, caching involves the temporary storage of data that is expensive to retrieve, resulting in quick responses to clients by avoiding additional data retrieval from the original source. For example, caching can produce enormous performance gains in enterprise applications by avoiding calls from the application server to the database, which may involve network roundtrips if they are not co-located. With caching, application response time will be notably better because of reduced database workload and freed network bandwidth.

Distributed systems are excellent targets for caching services. Remote calls are particularly slow and consume precious network bandwidth, introducing performance issues. A call to a remote service starts with the creation of the request by marshalling its parameters into a format that can be transported over the wire, like XML or a byte stream. Once the request is received, it must be unmarshalled on the server side in order to start its processing. A similar procedure applies to the response generated by the service, which adds more overhead. Caching can dramatically increase performance of distributed applications by eliminating, or at least reducing, remote calls.

Regardless of its benefits, caching can add considerable complexity to the design, development, and deployment of enterprise applications. You should not apply caching without clear knowledge of the business requirements, and your decision should be backed up by test results and other solid evidence.

The following are basic guidelines for caching:

  • It is usually worthwhile to add caching to reduce remote calls. Performance is improved by avoiding network roundtrips.
  • Caching read-only, reference data is a must-have optimization. For example, caching the list of U.S. states can eliminate constant retrieval of data that is very unlikely to change.
  • Service responses that always return the same information for a given set of parameter values are good candidates for caching.
  • Caching dynamic data, that is, read/write data, can be done if the application can tolerate stale data. A news Web site is a good example, where the performance improvements that caching introduces justify displaying news a few seconds late.
  • The volume of data to be cached should be controllable, otherwise you will end up using too much memory.
  • Caching dynamic data across a cluster and synchronizing the data in each node is difficult. It is advisable to consider using a third-party caching solution.
  • You should avoid caching real-time data (such as stock market quotes) and sensitive data (such as passwords and social security numbers).
  • Caching can introduce issues related to security and auditing. A cache can defeat security screening by intercepting requests and processing them without any security checks.

Implementing our own cache is not a simple task. You may have to deal with complexity issues like thread management, removal of stale data, and clustering support. Currently there are many high quality, third-party cache implementations, both open source and commercial, that can save you from the burden of creating, and more important, maintaining your own cache.

Declarative Caching Services for Spring

The Spring Framework is an open source application framework that provides, among other things, enterprise services to plain Java objects. Most of the applications created with Spring are multitier, data-centric enterprise applications with multiple concurrent users. Such applications may also integrate with external systems using some kind of remoting technology like remote EJBs, Web services, RMI, or CORBA, just to name a few. Enterprise applications are important to the core business and are expected to deliver high performance.

Declarative Caching Services for Spring provides declarative caching for Spring-powered applications. Declarative caching does not involve any programming and therefore it is a much easier and more rapid way of applying and tuning caching services. Configuration of caching services can be completely done in the Spring IoC container. Declarative caching:

  • Provides support for different cache providers such as EHCache, JBoss Cache, Java Caching System (JCS), OSCache, and Tangosol Coherence.
  • Provides a unified, simpler, easier to use API for programmatic use of caching services than most of these previously mentioned APIs. Note that programmatic use of caching is not recommended unless you have unusual requirements.
  • Declarative Caching Services also offers declarative cache flushing to prevent the storage of stale data.
  • Supports different declarative configuration strategies.
  • Declarative Caching Services may be easily extended to support additional cache providers.

Benefits of Declarative Caching Services

To better understand declarative caching and its benefits, let's look at the following code snippet. It is a simple implementation of a customer manager that retrieves customer information from a data source given the customer's ID. To improve performance, the Customer object retrieved from the data access layer is programmatically stored in a Coherence cache. In addition, to prevent the storage of stale data, the cache containing customer information is flushed when the information of a single customer is updated:

public class CustomerManager {

  private CustomerDao customerDao;

  private CacheKeyGenerator keyGenerator;    

  

  public Customer load(long customerId) {

     
                         validateCustomerId(customerId);

    Customer customer = null;

    

    Serializable key = keyGenerator.generateKey(customerId);

    NamedCache cache = getCache();

    customer = (Customer) cache.get(key);

    if (customer == null) {

       
                         customer = customerDao.load(customerId);

      cache.put(key, customer);

    }

     
                         return customer;

  }



  public void update(Customer customer) {

     
                         customerDao.update(customer);  

    Serializable key = keyGenerator.generateKey(customer.getId());

    NamedCache cache = getCache();

    cache.remove(key);

  }       

  

  private NamedCache getCache() {

    return CacheFactory.getCache("customerCache");

  }

  // rest of class implementation

}
                      

Although the use of caching has improved the performance of the application, it also has added extra (and unnecessary) complexity. Programmatic use of caching raises the following issues:

  • Code is harder to understand and the core functionality is a lot harder to see. In our example, only the lines in bold implement the real job of the method.
  • Code is harder to maintain. Calls to the cache provider are embedded and spread across the application. As a result, any maintenance work involving caching is very likely to affect the core functionality of the system. In addition, any change to the caching functionality involves visiting every copy of code fragment and, to make things worse, at that time we probably will not know where they are.
  • Code is harder to test. In our example, we need a Coherence CacheFactory, or at least a mock object simulating it, to test the business logic of the method.
  • Code is harder to reuse. It will be difficult (or impossible) to reuse the CustomerManager class in applications that have different caching needs or no caching needs at all.

Declarative caching encapsulates how caching is performed and eliminates any dependencies on the cache implementation from our Java code. After introducing declarative caching, the method from the previous example implements only its core requirement:

public class CustomerManager {

  private CustomerDao customerDao;

  

  public Customer load(long customerId) {

    validateCustomerId(customerId);

    return customerDao.load(customerId);

  }

  

  public void update(Customer customer) {

    customerDao.update(customer);    

  }  

  // rest of class implementation

}

The following XML fragment illustrates how caching services can be configured for the CustomerManager instance in the Spring IoC container, rather than in our Java code:

<bean id="
                         customerDaoTarget"

  class="org.springmodules.cache.samples.dao.CustomerDao" />

  <!-- Properties -->

</bean>



<coherence:proxy id="customerDao" refId="
                         customerDaoTarget">

  <coherence:caching methodName="load" cacheName="customerCache" />

  <coherence:flushing methodName="update" cacheNames="customerCache" />

</coherence:proxy>



<bean id="customerManager"

  class="org.springmodules.cache.samples.business.CustomerManager" />

  <property name="customerDao" ref="customerDao" />

</bean>
                      

Declarative caching configuration effectively separates the caching functionality from the core requirements of our application, solving the problems mentioned above and providing the following benefits:

  • Cleaner separation of responsibilities. Modules in a system only take responsibility of their core requirements and are no longer responsible for caching functionality. This results in improved traceability.
  • Higher modularization. Separation of caching functionality from the core modules results in much less duplicated code (supporting the principle of " once and only once") and helps avoid code clutter too.
  • Late binding of design decisions. With declarative caching, developers can delay making decisions about caching implementation and tuning. Developers can focus instead on the current core requirements of the application. Declarative configuration supports YAGNI (you aren't gonna need it) by allowing developers to add caching to their applications only when they truly need it, without making system-wide changes.

Pages: 1, 2, 3, 4

Next Page »