Announcement: You can find the guides for Commerce 7.5 and later on the new Elastic Path Documentation site. This Developer Center contains the guides for Commerce 6.13.0 through 7.4.1.Visit new site

This version of Elastic Path Commerce is no longer supported or maintained. To upgrade to the latest version, contact your Elastic Path representative.

Request-Scoped Caching

Request-Scoped Caching

Cortex caches by request scope using the Spring AOP framework. Caches live for the duration of the request's lifecycle. Once the request processes, cache entries are evicted; thereby, freeing up the allocated memory. Request-Scoped Caching is controlled by @CacheResult and @CacheRemove annotations.

Enabling Request-Scoped Caching

To enable request-scoped caching in a resource:
  1. Add AspectJ to your resource's Spring configuration in resource_name/src/main/resources/OSGI-INF/blueprint/applicationContext-resource_name-resource.xml
    <!-- This tells Spring to interpret the AspectJ annotations. However, it still uses pure Spring AOP at runtime. -->
    <aop:aspectj-autoproxy />
    
  2. Add the following maven dependencies to your resource's pom.xml:
    <dependency>
    <groupId>com.elasticpath.rest</groupId>
    <artifactId>ep-rest-spi</artifactId>
    <version>0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
    <groupId>com.elasticpath.rest</groupId>
    <artifactId>ep-rest-spi-impl</artifactId>
    <version>0-SNAPSHOT</version>
    <scope>test</scope>
    </dependency>
    
    <dependency>
    <groupId>org.apache.servicemix.bundles</groupId>
    <artifactId>org.apache.servicemix.bundles.aspectj</artifactId>
    </dependency>
    
  3. Add cacheKeyManager, cacheRemoveService, and cacheResultService imports to the resource's blueprint configuration file in resourcename/src/main/resources/OSGI-INF/blueprint/resourcename-blueprint.xml
    <reference id="cacheKeyManager"
    interface="com.elasticpath.rest.cache.CacheKeyManager"/commerce-legacy/>
    
    <reference id="cacheRemoveService"
     interface="com.elasticpath.rest.cache.CacheRemoveService"/commerce-legacy/>
    
    <reference id="cacheResultService"
    interface="com.elasticpath.rest.cache.CacheResultService"/commerce-legacy/>
    
  4. Create CacheRemoveAspect and CacheResultAspect Spring AOP classes in resourcename/src/main/java/*
    CacheResultAspect.java
    import javax.inject.Inject;
    import javax.inject.Named;
    import javax.inject.Singleton;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    import com.elasticpath.rest.cache.CacheResult;
    import com.elasticpath.rest.cache.CacheResultService;
    
    /**
     * This is an AOP Aspect, it adds caching to methods that are annotated with {@link CacheResult}.
     */
    @Singleton
    @Aspect
    @Named("cacheResultAspect")
    public class CacheResultAspect {
    
    	@Named("cacheResultService")
    	private CacheResultService cacheResultService;
    
    	/**
    	 * This method will generate a key from the String parameters and Class name and use it to cache the result of the annotated method.
    	 *
    	 * @param joinPoint   the method that we are decorating.
    	 * @param cacheResult the cache annotation.
    	 * @return the cache hit or result of the method invocation.
    	 * @throws Throwable if the method can not proceed.
    	 */
    	@Around("@annotation( cacheResult ) ")
    	public Object decorateMethodWithCaching(final ProceedingJoinPoint joinPoint, final CacheResult cacheResult) throws Throwable {
    		return cacheResultService.decorateMethodWithCaching(joinPoint, cacheResult);
    	}
    }
    
      CacheRemoveAspect.java
    import javax.inject.Inject;
    import javax.inject.Named;
    import javax.inject.Singleton;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    import com.elasticpath.rest.cache.CacheRemove;
    import com.elasticpath.rest.cache.CacheRemoveService;
    
    /**
     * This is an AOP Aspect, it adds caching to methods that are annotated with {@link com.elasticpath.rest.cache.CacheRemove}.
     */
    @Singleton
    @Aspect
    @Named("cacheRemoveAspect")
    public class CacheRemoveAspect {
    
    	@Named("cacheRemoveService")
    	private CacheRemoveService cacheRemoveService;
    
    	/**
    	 * This method will generate a key from the String parameters and Class name and use it to cache the result of the annotated method.
    	 *
    	 * @param joinPoint   the method that we are decorating.
    	 * @param cacheRemove the cache remove annotation.
    	 * @return the cache hit or result of the method invocation.
    	 * @throws Throwable if the method can not proceed.
    	 */
    	@Around("@annotation( cacheRemove ) ")
    	public Object decorateMethodWithCaching(final ProceedingJoinPoint joinPoint, final CacheRemove cacheRemove) throws Throwable {
    		return cacheRemoveService.decorateMethodWithCaching(joinPoint, cacheRemove);
    	}
    }
    
    Your resource is now caching enabled.

Caching Method Responses

To cache method responses, annotate the method with the @CacheResult annotation.
@CacheResult
public Customer getCustomer(CustomerId customerId){}

Invalidating Caches on Methods

To invalidate cached values when running update or delete methods, annotate the methods with the @CacheRemove annotation. @CacheRemove requires either an object of the class to be invalidated or an array of class objects. The value to invalidate equals the return type of the corresponding get method.
@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(CustomerId customerId) {}

@CacheRemove(typesToInvalidate = Customer.class)
public void deleteAllCustomers() {}  
For nested generics, this class should be the unchecked simplest form. For ExecutionResult return type, the wrapper class should be ignored and the nested type used. The following translation table applies:
Return-type typesToInvalidate Description
Store Store.class No generic type used thus class type can be directly derived from the return type.
ExecutionResult<Store> Store.class ExecutionResult wrapper is omitted, the type-hint rule for the declared generic type applies.
Map<String, List<String>> Map.class Nested generic type definitions are flatten and class type.
ExecutionResult<List<String>> List.class When defining ExecutionResult in combination with a nested generic type omitting ExecutionResult and generic type flattening applies.

Tuning Request-Scoped Caching Performance

To achieve the best request-scoped caching performance, use the tuning techniques described below.

Defining Key Variants

Important: Key Variants Must be Unique

Key variants have to be unique otherwise inconsistencies might occur.

Domain objects may have multiple key variants. A call to a @CacheResult annotated method caches the returned domain object under all its key variants. A subsequent call to another @CacheResult annotated method, which has input parameters matching a key variant, will result in a cache hit and the cached value being returned. For example:
class Customer {
	String storeCode,userId; //key variant - unique identifier
	String guid //key variant - unique identifier
}

@CacheResult//first call goes here
public Customer findCustomerByUserId(String storeCode, String userId) {}

@CacheResult//second call goes here, leverages key variant caching
public Customer findCustomerByGuid(String guid) {}
To enable the cache to use key variant optimization, register the domain object and its key variants by implementing the CacheKeyVariants interface and registering this interface with the CacheKeyManager. For example:
@Singleton
@Named
public final class CustomerCacheKeyVariants implements CacheKeyVariants<Customer> {

	@Inject
	public CustomerCacheKeyVariants(
			@Named("cacheKeyManager")
			final CacheKeyManager cacheKeyManager) {
    //register the CacheKeyVariant to use it
		cacheKeyManager.register(this);
	}

	@Override
	public Collection<Object[]> get(final Customer customer) {
		return Arrays.asList(
        // the keys have be unique identifier, otherwise inconsistencies might occur
				new Object[]{customer.guid},
				new Object[]{customer.storeCode, customer.userId}
		);
	}

	@Override
	public Class<Customer> getType() {
		return Customer.class;
	}
}

How Cache Invalidation Works

Request-scoped caches are divided into cache regions, each region represents the class that instantiated the cache. Cached values are only available to the class that originated the cache. The same applies for invalidation, only the originating class can invalidate the cache.

Getting the Best Cache Invalidation Performance

We achieve the best performance results by invalidating the least amount of cache entries as possible. Below is a list of some important cache invalidation approaches.

First Way: Parameter Equality Matching

We match the invalidation method parameters, so only cached entries with the same input parameters are removed from the cached.

@CacheResult()
public Customer getCustomerById(CustomerId customerId){}
@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(CustomerId customerId){}
//both method have the same input parameter
//when calling deleteCustomer(CustomerId(123)) only the
//value associated to CustomerId(123) will be removed
Second Way: Partial Parameter Caching

We use partial parameter matches, so invalidation method parameters are a subset of the cached method parameters.

@CacheResult()
public Customer getCustomerByFullName(String name,String preName){}
@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(String preName){}
//parameters of delete are subset of the get method
//when calling deleteCustomer("Albert") all Customers with the preName Albert will be removed
Third Way: Using Unique Identifiers
We use unique identifiers to avoid method signature collisions.
@CacheResult(uniqueIdentifier="customerId")
public Customer getCustomerById(CustomerId customerId){}
@CacheRemove(typesToInvalidate = Customer.class, uniqueIdentifier="customerId")
public void deleteCustomer(CustomerId customerId){}
//all Customer objects registered with the defined unique identifier will be removed
Least Performant Way

By not using any of the above strategies, all cached entries in a region can be invalidated and evicted, causing performance hit.

@CacheResult()
public Customer getCustomerById(String storeCode,String userId){}
@CacheRemove(typesToInvalidate = Customer.class)
public void removeCustomer(String customerName){}
//In this case all cached Customer objects will be removed from the cache.

Avoiding Cache Inconsistencies

Treat Cache Values as Immutable Objects

Modifying cached values is a bad practice that can lead to inconsistencies. Either use immutable types as values, which is the preferred way, or treat cached values as if they were immutable and create new objects on mutation.

Avoiding Method Signature Collision

Caches could have methods with the same return type and input parameters. To avoid collision, use a uniqueIdentifier with the @CacheResult annotation. The following example shows how to apply a uniqueIdentifier.
@CacheResult(uniqueIdentifier = "default-customer-address")
public Address getDefaultCustomerAddress(CustomerId customerId) {}

@CacheResult(uniqueIdentifier = "shipping-address")
public Address getShippingAddress(CustomerId customerId) {}

Only use CacheKeyVariants on Unique Identifiers

Using CacheKeyVariants is an essential rule for performance optimization.

Limitations of Annotation Based Request-Scoped Caching

Situations where request-scoped caching should not be used:
  • Annotations won't work when calling the annotated methods from other methods inside the class applying the cache. The same rules applies when using inheritance.
    @CacheResult
    public Integer myMethod() {}
    
    public Integer myMethodDelegate() {
    return myMethod();
    }
    // call to myMethodDelegate will not result in calling the cached Version of myMethod!
    
  • Mixed usage of either wrapping the return object in an ExecutionResult or not is not possible.
    @CacheResult
    public ExecutionResult<Customer> getCustomerById(CustomerId customerId) {}
    
    @CacheResult
    public CEDomainObject method2(final String customerCachKeyVariant) {}
    

Manual Caching

In cases where annotation caching isn't appropriate, you can cache manually. To use manual caching, inject an instance of the cacheKeyBasedRequestCache bean.

@Named("cacheKeyBasedRequestCache")
private TypedRequestCache<CacheKey> requestCache

public void method() {
String value = "cache me";
requestCache.putCacheEntry(new CacheKey(this.getClass(),"my-cache-entry", String.class), value);
//do some operations
requestCache.invalidateCacheEntries(new CacheKey(this.getClass(),"my-cache-entry", String.class))}

Logging Caching

To activate cache logging, add the following to the logback.xml located in cortex/relos/logback-config/src/main/filtered-resources/
<logger name="com.elasticpath.rest.resource.integration.epcommerce.repository" level="TRACE"/commerce-legacy/>
Cache logging dramatically decreases cache performance as it will log a lot of information.
To see the ExternalOperationId associated with cache operations, add the following to the logback.xml.
<logger name="com.elasticpath.rest.cache.guava.impl" level="TRACE"/commerce-legacy/>

Configuring Request-Scoped Caching

By default, the request-scoped cache is configured to evict cached entries after 60 sec and when the number of cached entries reaches 2147483647. Parameters are configurable via the RelOS Request Cache OSGi configuration in the Apache Felix Web Console. Caches are destroyed and rebuilt when configuration values are updated through the Felix console.

Cache Metrics Monitoring

Request-scoped cache metrics are exposed as MBeans over Cortex's JMX API. The metrics Mbeans are identified by the naming pattern EP-RelOS.*.RequestCache.*.

Metrics available to monitor:
  • evictionCount – number of times entries have been evicted
  • hitCount – number of times entries have been looked up and found
  • hitRate – ratio of cache request which were hits
  • loadCount – number of times entries have been looked up
  • missCount – number of times entries have been looked up and missed
  • missRate – ratio of cache requests which weren't hits
  • requestCount – number of times entries have been looked up