1. Caching using Apache Geode or Pivotal GemFire

One of the quickest, easiest and least invasive ways to get started using Apache Geode or Pivotal GemFire in your Spring Boot applications is to use either Apache Geode or Pivotal GemFire as a {spring-framework-docs}/integration.html#cache-store-configuration[caching provider] in {spring-framework-docs}/integration.html#cache[Spring’s Cache Abstraction]. SDG {spring-framework-docs}/integration.html#cache-store-configuration-gemfire[enables] Apache Geode or Pivotal GemFire to function as a caching provider in Spring’s Cache Abstraction.

See the Spring Data for Apache Geode Reference Guide for more details on the {spring-data-geode-docs-html}/#apis:spring-cache-abstraction[support] and {spring-data-geode-docs-html}/#bootstrap-annotation-config-caching[configuration] of Apache Geode or Pivotal GemFire as a caching provider in Spring’s Cache Abstraction.
Make sure you thoroughly understand the {spring-framework-docs}/integration.html#cache-strategies[concepts] behind Spring’s Cache Abstraction before you continue.
You can also refer to the relevant section on {spring-boot-docs-html}/#boot-features-caching[Caching] in Spring Boot’s Reference Guide. Spring Boot even provides auto-configuration support for a few, simple {spring-boot-docs-html}/#_supported_cache_providers[caching providers] out-of-the-box.

Indeed, caching can be a very effective software design pattern to avoid the cost of invoking a potentially expensive operation when, given the same input, the operation yields the same output every time.

Some classic examples of caching include, but are not limited to: looking up a customer by name or account number, looking up a book by ISBN, geocoding a physical address, caching the calculation of a person’s credit score when the person applies for a financial loan.

If you need the proven power of an enterprise-class caching solution, with strong consistency, high availability and multi-site (WAN) capabilities, then you should consider Apache Geode, or alternatively Pivotal GemFire. Additionally, Pivotal Software, Inc. offers Pivotal GemFire as a service, known as Pivotal Cloud Cache (PCC), when deploying and running your Spring Boot applications in Pivotal Cloud Foundry (PCF).

Spring’s {spring-framework-docs}/integration.html#cache-annotations[declarative, annotation-based caching] makes it extremely simple to get started with caching, which is as easy as annotating your application service components with the appropriate Spring cache annotations.

Spring’s declarative, annotation-based caching also {spring-framework-docs}/integration.html#cache-jsr-107[supports] JCache (JSR-107) annotations.

For example, suppose you want to cache the results of determining a person’s eligibility when applying for a financial loan. A person’s financial status is not likely to change in the time that the computer runs the algorithms to compute a person’s eligibility after all the financial information for the person has been collected and submitted for review and processing.

Our application might consist of a financial loan service to process a person’s eligibility over a given period of time:

@Service
class FinancialLoanApplicationService {

    @Cacheable("EligibilityDecisions", ...)
    EligibilityDecision processEligility(Person person, Timespan timespan) {
        ...
    }
}

Notice the @Cacheable annotation on the processEligibility(:Person, :Timespan) method of our service class.

When the FinancialLoanApplicationService.processEligibility(..) method is called, Spring’s caching infrastructure first consults the “EligibilityDecisions” cache to determine if a decision has already been computed for the given person within the given span of time. If the person’s eligibility in the given time frame has already been determined, then the existing decision is returned from the cache. Otherwise, the processEligibility(..) method will be invoked and the result of the method will be cached when the method returns, before returning the value to the caller.

Spring Boot for Apache Geode/Pivotal GemFire auto-configures Apache Geode or Pivotal GemFire as the caching provider when either one is declared on the application classpath, and when no other caching provider (e.g. Redis) has been configured.

If Spring Boot for Apache Geode/Pivotal GemFire detects that another cache provider has already been configured, then neither Apache Geode nor Pivotal GemFire will function as the caching provider. This allows users to configure, another store, e.g. Redis, as the caching provider and use Apache Geode or Pivotal GemFire as your application’s persistent store, perhaps.

The only other requirement to enable caching in a Spring Boot application is for the declared caches (as specified in Spring’s or JSR-107’s caching annotations) to have been created and already exist, especially before the operation, on which caching has been applied, is invoked. This means the backend data store must provide the data structure serving as the "cache". For Apache Geode or Pivotal GemFire, this means a Region.

To configure the necessary Regions backing the caches declared in Spring’s cache annotations, this is as simple as using Spring Data for Apache Geode or Pivotal GemFire’s {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableCachingDefinedRegions.html[@EnableCachingDefinedRegions] annotation.

The complete Spring Boot application looks like this:

package example.app;

import ...;

@SpringBootApplication
@EnableCachingDefinedRegions
class FinancialLoanApplication {

    public static void main(String[] args) {
        SpringApplication.run(FinancialLoanApplication.class, args);
    }
}
The FinancialLoanApplicationService is picked up by Spring’s classpath component scan since this class is annotated with Spring’s @Service stereotype annotation.
You can set the DataPolicy of the Region created through the @EnableCachingDefinedRegions annotation by setting the clientRegionShortcut to a valid enumerated value.
Spring Boot for Apache Geode/Pivotal GemFire does not recognize nor apply the spring.cache.cache-names property. Instead, you should use SDG’s @EnableCachingDefinedRegions on an appropriate Spring Boot application @Configuration class.

1.1. Look-Aside Caching, Near Caching and Inline Caching

Three different types of caching patterns can be applied with Spring when using Apace Geode or Pivotal GemFire for your application caching needs.

The 3 primary caching patterns include:

  • Look-Aside Caching

  • Near Caching

  • Inline Caching

1.1.1. Look-Aside Caching

The caching pattern demonstrated in the example above is a form of Look-Aside Caching.

Essentially, the data of interest is searched for in the cache first, before calling a potentially expensive operation, e.g. like an operation that makes an IO or network bound request resulting in either a blocking, or a latency sensitive computation.

If the data can be found in the cache (stored in-memory to reduce latency) then the data is returned without ever invoking the expensive operation. If the data cannot be found in the cache, then the operation must be invoked. However, before returning, the result of the operation is cached for subsequent requests when the the same input is requested again, by another caller resulting in much improved response times.

Again, typical Look-Aside Caching pattern applied in your application code looks similar to the following:

Look-Aside Caching Pattern Applied
@Service
class CustomerService {

  private final CustomerRepository customerRepository;

  @Cacheable("Customers")
  Customer findByAcccount(Account account) {

    // pre-processing logic here

    Customer customer = customerRepository.findByAccoundNumber(account.getNumber());

    // post-processing logic here

    return customer;
  }
}

In this design, the CustomerRepository is perhaps a JDBC or JPA/Hibernate backed implementation accessing the external data source (i.e. RDBMS) directly. The @Cacheable annotation wraps, or "decorates", the findByAccount(:Account):Customer operation to provide caching facilities.

This operation may be expensive because it might validate the Customer’s Account before looking up the Customer, pull multiple bits of information to retrieve the Customer record, and so on, hence the need for caching.

1.1.2. Near Caching

Near Caching is another pattern of caching where the cache is collocated with the application. This is useful when the caching technology is configured using a client/server arrangement.

We already mentioned that Spring Boot for Apache Geode & Pivotal GemFire provides an auto-configured, ClientCache instance, out-of-the-box, by default. The ClientCache instance is most effective when the data access operations, including cache access, is distributed to the servers in a cluster accessible by the client, and in most cases, multiple clients. This allows other cache client applications to access the same data. However, this also means the application will incur a network hop penalty to evaluate the presence of the data in the cache.

To help avoid the cost of this network hop in a client/server topology, a local cache can be established, which maintains a subset of the data in the corresponding server-side cache (i.e. Region). Therefore, the client cache only contains the data of interests to the application. This "local" cache (i.e. client-side Region) is consulted before forwarding the lookup request to the server.

To enable Near Caching when using either Apache Geode or Pivotal GemFire, simply change the Region’s (i.e. the Cache in Spring’s Cache Abstraction) data management policy from PROXY (the default) to CACHING_PROXY, like so:

@SpringBootApplication
@EnableCachingDefinedRegions(clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY)
class FinancialLoanApplication {

    public static void main(String[] args) {
        SpringApplication.run(FinancialLoanApplication.class, args);
    }
}
The default, client Region data management policy is {apache-geode-javadoc}/org/apache/geode/cache/client/ClientRegionShortcut.html#PROXY[ClientRegionShortcut.PROXY]. As such, all data access operations are immediately forwarded to the server.
Also see the Apache Geode documentation concerning {apache-geode-docs}/developing/events/how_client_server_distribution_works.html[Client/Server Event Distribution] and specifically, "Client Interest Registration on the Server" when using local, client CACHING_PROXY Regions to manage state in addition to the corresponding server-side Region. This is necessary to receive updates on entries in the Region that might have been changed by other clients accessing the same data.

1.1.3. Inline Caching

The final pattern of caching we’ll discuss is Inline Caching.

When employing Inline Caching and a cache miss occurs, the application service method may still not be invoked since the a Region can be configured to invoke a loader to load the missing entry from an external data source.

With Apache Geode and Pivotal GemFire, the cache, or using Apache Geode/Pivotal GemFire terminology, the Region, can be configured with a {apache-geode-javadoc}/org/apache/geode/cache/CacheLoader.html[CacheLoader]. This CacheLoader is implemented to retrieve missing values from some external data source, which could be an RDBMS or any other type of data store (e.g. another NoSQL store like Apache Cassandra, MongoDB or Neo4j).

See the Apache Geode User Guide on {apache-geode-docs}/developing/outside_data_sources/how_data_loaders_work.html[Data Loaders] for more details.

Likewise, an Apache Geode or Pivotal Gemfire Region can be configured with a {apache-geode-javadoc}/org/apache/geode/cache/CacheWriter.html[CacheWriter]. A CacheWriter is responsible for writing any entry put into the Region to the backend data store, such as an RDBMS. This is referred to as a "write-through" operations because it is synchronous. If the backend data store fails to be written to then the entry will not be stored in the Region. This helps to ensure some level of consistency between the backing data store and the Apache Geode or Pivotal GemFire Region.

It is also possible to implement Inline-Caching using an asynchronous, write-behind operation by registering an {apache-geode-javadoc}/org/apache/geode/cache/asyncqueue/AsyncEventListener.html[AsyncEventListener] on an {apache-geode-javadoc}/org/apache/geode/cache/asyncqueue/AsyncEventQueue.html[AEQ] tied to a server-side Region. You should consult the Apache Geode User Guide for more {apache-geode-docs}/developing/events/implementing_write_behind_event_handler.html[details].
Since SBDG is currently focused on the client-side, async, write-behind behavior is not currently covered with extensive, convenient support, although, it is still very much possible to do.

The typical pattern of Inline Caching when applied to application code looks like the following:

Inline Caching Pattern Applied
@Service
class CustomerService {

  private CustomerRepository customerRepository;

  Customer findByAccount(Account account) {

      // pre-processing logic here

      Customer customer = customerRepository.findByAccountNumber(account.getNumber());

      // post-processing locic here.

      return customer;
  }
}

The main difference is, there are no Spring or JSR-107 caching annotations applied to the service methods and the CustomerRepository is accessing Apache Geode or Pivotal GemFire directly and NOT the RDBMS.

Implementing CacheLoaders, CacheWriters for Inline Caching

You can use Spring to configure a CacheLoader or CacheWriter as a bean in the Spring ApplicationContext and then wire it to a Region. Given the CacheLoader or CacheWriter is a Spring bean like any other bean in the Spring ApplicationContext, you can inject any DataSource you like into the Loader/Writer.

While you can configure client Regions with CacheLoaders and CacheWriters, it is typically more common to configure the corresponding server-side Region; for example:

@SpringBootApplication
@CacheServerApplication
class FinancialLoanApplicationServer {

    public static void main(String[] args) {
        SpringApplication.run(FinancialLoanApplicationServer.class, args);
    }

	@Bean("EligibilityDecisions")
	PartitionedRegionFactoryBean<Object, Object> eligibilityDecisionsRegion(
            GemFireCache gemfireCache, CacheLoader decisionManagementSystemLoader,
            CacheWriter decisionManagemenSystemWriter) {

        PartitionedRegionFactoryBean<?, EligibilityDecision> eligibilityDecisionsRegion =
            new PartitionedRegionFactoryBean<>();

        eligibilityDecisionsRegion.setCache(gemfireCache);
        eligibilityDecisionsRegion.setCacheLoader(decisionManagementSystemLoader);
        eligibilityDecisionsRegion.setCacheWriter(decisionManagementSystemWriter);
        eligibilityDecisionsRegion.setClose(false);
        eligibilityDecisionsRegion.setPersistent(false);

        return eligibilityDecisionsRegion;
    }


    @Bean
    CacheLoader<?, EligibilityDecision> decisionManagementSystemLoader(
            DataSource dataSource) {

        return new DecisionManagementSystemLoader(dataSource);
    }

    @Bean
    CacheWriter<?, EligibilityDecision> decisionManagementSystemWriter(
            DataSource dataSource) {

        return new DecisionManagementSystemWriter(dataSource);
    }

    @Bean
    DataSource dataSource(..) {
      ...
    }
}

Then, you would implement the {apache-geode-javadoc}/org/apache/geode/cache/CacheLoader.html[CacheLoader] and {apache-geode-javadoc}/org/apache/geode/cache/CacheWriter.html[CacheWriter] interfaces as appropriate:

DecisionManagementSystemLoader
class DecisionManagementSystemLoader implements CacheLoader<?, EligibilityDecision> {

  private final DataSource dataSource;

  DecisionManagementSystemLoader(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public EligibilityDecision load(LoadHelper<?, EligibilityDecision> helper) {

     Object key = helper.getKey();

     // Use the configured DataSource to load the value from an external data store.

     return ...
   }
}
SBDG provides the org.springframework.geode.cache.support.CacheLoaderSupport @FunctionalInterface to conveniently implement application CacheLoaders.

If the configured CacheLoader still cannot resolve the value, then the cache lookup operation results in a miss and the application service method will then be invoked to compute the value.

DecisionManagementSystemWriter
class DecisionManagementSystemWriter implements CacheWriter<?, EligibilityDecision> {

  private final DataSource dataSource;

  DecisionManagementSystemWriter(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public void beforeCreate(EntryEvent<?, EligiblityDecision> entryEvent) {
    // Use configured DataSource to save (e.g. INSERT) the entry to the backend data store
  }

  public void beforeUpdate(EntryEvent<?, EligiblityDecision> entryEvent) {
    // Use the configured DataSource to save (e.g. UPDATE or UPSERT) the entry in the backend data store
  }

  public void beforeDestroy(EntryEvent<?, EligiblityDecision> entryEvent) {
    // Use the configured DataSource to delete (i.e. DELETE) the entry from the backend data store
  }

  ...
}
SBDG provides the org.springframework.geode.cache.support.CacheWriterSupport interface to conveniently implement application CacheWriters.
Of course, your CacheWriter implementation can use any data access technology to interface with your backend data store (e.g. JDBC, Spring’s JdbcTemplate, JPA/Hibernate, etc). It is not limited to only using a javax.sql.DataSource. In fact, we will present another, more useful and convenient approach to implementing Inline Caching in the next section.
Inline Caching using Spring Data Repositories.

Spring Boot for Apache Geode & Pivotal GemFire (SBDG) now offers dedicated support and configuration of Inline Caching using Spring Data Repositories.

This is very powerful because it allows you to:

  1. Access any backend data store supported by Spring Data (e.g. Redis for Key/Value or other data structures, MongoDB for Documents, Neo4j for Graphs, Elasticsearch for Search, and so on).

  2. Use complex mapping strategies (e.g. ORM provided by JPA/Hibernate).

It is our belief that users should be putting data where it is most easily accessible. If you are accessing and processing Documents, then most likely MongoDB (or Couchbase or another document store) might be the most logical choice to manage your application’s Documents.

However, that does not mean you have to give up Apache Geode or Pivotal GemFire in your application/system architecture. You can leverage each data store for what it is good at. While MongoDB is good at Document handling, Apache Geode is a highly valuable choice for consistency, high availability, multi-site, low-latency/high-throughput scale-out Use Cases.

As such, using Apache Geode and Pivotal GemFire’s CacheLoader/CacheWriter mechanism provides a integration point between itself and other data stores to best serve your Use Case and application requirements/needs.

And now, SBDG just made this even easier.

EXAMPLE

Let’s say you are using JPA/Hibernate to access (store and retrieve) data in a Oracle Database.

Then, you can configure Apache Geode to read/write-through to the backend Oracle Database when performing cache (Region) operations by delegating to a Spring Data (JPA) Repository.

The configuration might look something like:

Inline Caching configuration using SBDG
@SpringBootApplication
@EntityScan(basePackageClasses = Customer.class)
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@EnableJpaRepositories(basePackageClasses = CustomerRepository.class)
class SpringBootOracleDatabaseApacheGeodeApplication {

  @Bean
  InlineCachingRegionConfigurer<Customer, Long> inlineCachingForCustomersRegionConfigurer(
      CustomerRepository customerRepository) {

    return new InlineCachingRegionConfigurer<>(customerRepository, Predicate.isEqual("Customers"));
  }
}

Out-of-the-box, SBDG provides the InlineCachingRegionConfigurer<ENTITY, ID> interface.

Given a Predicate to express and match the target Region by name along with a Spring Data CrudRepository, the InlineCachingRegionConfigurer will configure and adapt the Spring Data CrudRepository as a CacheLoader and CacheWriter for the Region (e.g. "Customers"), i.e. it enables the Region to use Inline Caching.

You simply only need to declare InlineCachingRegionConfigurer as a bean in the Spring application context and make the association between the Region (by name) and the appropriate Spring Data CrudRepository.

In this example, we used JPA and Spring Data JPA to store/retrieve the data in the cache (Region) to/from a backend database. But, you can inject any Spring Data Repository for any data store (e.g. Redis, MongoDB, etc) that supports the Spring Data Repository abstraction.

If you only want to support oneway data access operations when using Inline Caching, then you can use either the RepositoryCacheLoaderRegionConfigurer for reads or the RepositoryCacheWriterRegionConfigurer for writes, instead of the InlineCachingRegionConfigurer, which supports both reads and writes.
To see a similar implementation of Inline Caching using a Database (In-Memory, HSQLDB Database) in action, have a look at this test class from the SBDG test suite. A dedicated sample will be provided in a future release.

1.2. Advanced Caching Configuration

Both Apache Geode and Pivotal GemFire support additional caching capabilities to manage the entries stored in the cache.

As you can imagine, given the cache entries are stored in-memory, it becomes important to monitor and manage the available memory wisely. After all, by default, both Apache Geode and Pivotal GemFire store data in the JVM Heap.

Several techniques can be employed to more effectively manage memory, such as using {apache-geode-docs}/developing/eviction/chapter_overview.html[Eviction], possibly {apache-geode-docs}/developing/storing_data_on_disk/chapter_overview.html[overflowing to disk], configuring both entry Idle-Timeout (TTI) as well as Time-To-Live (TTL) {apache-geode-docs}/developing/expiration/chapter_overview.html[Expiration policies], configuring {apache-geode-docs}/managing/region_compression.html[Compression], and using {apache-geode-docs}/managing/heap_use/off_heap_management.html[Off-Heap], or main memory.

There are several other strategies that can be used as well, as described in {apache-geode-docs}/managing/heap_use/heap_management.html[Managing Heap and Off-heap Memory].

While this is well beyond the scope of this document, know that Spring Data for Apache Geode & Pivotal GemFire make all of these {spring-data-geode-docs-html}/#bootstrap-annotation-config-regions[configuration options] simple.

1.3. Disable Caching

There may be cases where you do not want your Spring Boot application to cache application state with {spring-framework-docs}/integration.html#cache[Spring’s Cache Abstraction] using either Apache Geode or Pivotal GemFire. In certain cases, you may be using another Spring supported caching provider, such as Redis, to cache and manage your application state, while, even in other cases, you may not want to use Spring’s Cache Abstraction at all.

Either way, you can specifically call out your Spring Cache Abstraction provider using the spring.cache.type property in application.properties, as follows:

Use Redis as the Spring Cache Abstraction Provider
#application.properties

spring.cache.type=redis
...

If you prefer not to use Spring’s Cache Abstraction to manage your Spring Boot application’s state at all, then do the following:

Disable Spring’s Cache Abstraction
#application.properties

spring.cache.type=none
...

See Spring Boot {spring-boot-docs-html}/boot-features-caching.html#boot-features-caching-provider-none[docs] for more details.

It is possible to include multiple providers on the classpath of your Spring Boot application. For instance, you might be using Redis to cache your application’s state while using either Apache Geode or Pivotal GemFire as your application’s persistent store (System of Record).
Spring Boot does not properly recognize spring.cache.type=[gemfire|geode] even though Spring Boot for Apache Geode/Pivotal GemFire is setup to handle either of these property values (i.e. either “gemfire” or “geode”).