Caching Capabilities of the SAP Cloud SDK for Java

Caching may improve your application's performance and responsiveness to your consumers. The SAP Cloud SDK for Java provides abstractions for caching that make it easy to integrate caching into an application with almost no configuration overhead. Also it integrates with other SDK features and most importantly is tenant aware out of the box. The following article describes how to enable caching with the Cloud SDK for Java in your application.

Using the Resilience API for Caching#

Adding a JCache Provider#

The Cloud SDK relies on the JCache SPI to create and manage cache instances. JCache is a Service Provider Interface which requires an implementation of this interface to be present at runtime. Be sure to that such an implementation is present on your classpath. We recommend Caffeine which you can use by simply adding the below dependency to your application pom.xml:

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>jcache</artifactId>
<version>2.8.5</version>
<scope>runtime</scope>
</dependency>
caution

Caching only works when a service provider or implementation for JCache is available at runtime. Otherwise you will face a runtime exception.

Creating a Cache configuration#

As described in our Resilience section the Cloud SDK allows to run any code in the context of one or more resilience patterns. Caching is also a resilience pattern so you can leverage the same API to build and apply a CacheConfiguration.

CacheConfiguration cacheConfig = CacheConfiguration
.of(Duration.ofDays(1))
.withoutParameters();

This builds a simple cache that will store values for one day and does not require any additional parameters. The builder pattern requires at least a duration and whether or not the cache requires parameters. The configuration options are explained further below.

This configuration must then be added to a ResilienceConfiguration:

ResilienceConfiguration resilienceConfig = ResilienceConfiguration
.empty(resilienceId)
.cacheConfiguration(cacheConfig);

The above creates an otherwise empty ResilienceConfiguration and thus only enabling caching. Of course you may use other resilience patterns as well.

Applying a Cache configuration#

Since the cache configuration is part of a resilience configuration it is applied in the same way:

ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);
ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);

The above code will invoke operation() only once since the result of the first invocation will be cached. Only if the first call fails the second one would still happen.

Note that a specific cache instance is not yet created when configuration is created. Instead it is only created when an operation is decorated or executed by the ResilienceDecorator.

tip

The cache functionality is tenant aware by default. That means that by default cache entries created under a specific tenant will not be shared with other tenants. See the section on multi tenancy below.

Configuring the Caching Strategy#

The cache configuration offers 3 options:

  1. Cache Duration (Required)
  2. Expiration Strategy (Optional)
  3. Parameters (Optional)

Each cache configuration must at least specify a duration after which a cache expires. How this timeout is applied can be configured through the Expiration Strategy. By default a cache entry is cleared once the configured duration has passed since the entry was created.

Finally, parameters can be listed that further refine the caching functionality. Parameters values will be attached to cache key. When the cache is accessed only entries with matching parameter values will be returned.

// Set a parameter value
cacheConfig = CacheConfiguration
.of(Duration.ofDays(1))
.withParameters("value1");
resilienceConfig.cacheConfiguration(cacheConfig);
// Cache is empty, so this will be a cache miss
ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);
// Cache is hit, cached value will be returned
ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);
// Set a different parameter value
cacheConfig = CacheConfiguration
.of(Duration.ofDays(1))
.withParameters("value2");
resilienceConfig.cacheConfiguration(cacheConfig);
// Cache is not hit because the existing cache entry has a different parameter value
ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);
cacheConfig = CacheConfiguration
.of(Duration.ofDays(1))
.withParameters("value1");
resilienceConfig.cacheConfiguration(cacheConfig);
// The cached value from the very first invocation is returned as the parameter is "value1" again
ResilienceDecorator.executeSupplier(() -> operation(), resilienceConfig);

So you can use parameters to separate cache entries as you see fit. Please note that there is no need to do this to achieve multi tenancy. The SDK is tenant aware by default as described below.

Multi Tenancy#

By default this caching functionality is tenant aware. If a tenant is available the SDK will create cache entries only for that specific tenant. Other tenants will not see the value cached for that specific tenant.

For this example assume that config is a ResilienceConfiguration that has a CacheConfiguration in place:

cachedOperation = ResilienceDecorator.decorateSupplier(() -> operation(), config)
// cachedOperation() will be evaluated
TenantAccessor.executeWithTenant(tenantA, () -> cachedOperation());
// cached value will be taken
TenantAccessor.executeWithTenant(tenantA, () -> cachedOperation());
// cachedOperation() will be evaluated because the cached value is only accessible for tenantA
TenantAccessor.executeWithTenant(tenantB, () -> cachedOperation());
// cached value for tenantB will be taken
TenantAccessor.executeWithTenant(tenantB, () -> cachedOperation());

This isolation is configured via the isolation mode of the resilience configuration. So you can disable tenant aware caching by setting the isolation mode to NO_ISOLATION. See the section on isolation levels under Resilience for more details.

Manipulating the State of Caches#

You may manually clear caches by:

// invalidate
ResilienceDecorator.invalidateCache(resilienceConfig);

This currently clears all cache entries associated with the resilience configuration irrespective of the current tenant and principal.

To get hold of the created cache instance and to use it for advanced operations like for e.g registering event listeners you can use JCache API:

final Cache<SomeCacheKey, SomeResult> cache = Caching.getCachingProvider()
.getCacheManager()
.getCache(resilienceConfig.identifier()) //Returns the cache created using the resilienceId passed
.registerCacheEntryListener(SimpleCacheEntryListener); //Registers the event listener

where SimpleCacheEntryListener is a class that implements the interfaces of the required events w.r.t cache defined in EventType Note that the identifier of the resilience configuration is also the identifier of the cache associated with it.

Last updated on by KavithaSiva