Edits to Inline & Look-Aside Caching documentation for clarity.

This commit is contained in:
John Blum
2019-08-01 22:28:17 -07:00
parent 59a9d8d1c9
commit 3ee5af7bba
3 changed files with 142 additions and 130 deletions

View File

@@ -8,18 +8,19 @@
:spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api :spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api
:spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html :spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html
:spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api :spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api
:spring-data-website: https://spring.io/projects/spring-data
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference :spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api :spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api
This guide walks you through building a simple Spring Boot application This guide walks you through building a simple Spring Boot application
using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction] using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction]
backed by Apache Geode as the caching provider in a Inline Caching use case. backed by Apache Geode as the caching provider for Inline Caching.
It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's
_Cache Abstraction_ or Apache Geode is required to utilize caching in your Spring Boot applications. _Cache Abstraction_ or Apache Geode is required to utilize caching in your Spring Boot applications.
Additionally, this example builds on the concepts from the link:caching-look-aside.adoc guide. Therefore, it would be Additionally, this example builds on the concepts from the link:caching-look-aside.html[Look-Aside Caching with Spring]
helpful to have read that guide before proceeding through this guide. guide. Therefore, it would be helpful to have read that guide before proceeding through this guide.
Let's begin. Let's begin.
@@ -33,7 +34,7 @@ yields the same results when given the same input. If an expensive operation is
the same input, then it will benefit from caching, especially if the operation is compute intensive, IO bound, the same input, then it will benefit from caching, especially if the operation is compute intensive, IO bound,
such as by accessing data over a network, and so forth. such as by accessing data over a network, and so forth.
Consider a very simple mathematical computation, the _factorial_. A _factorial_ is defined as `factorial(n) = n!`. Consider a very simple mathematical function, the _factorial_. A _factorial_ is defined as `factorial(n) = n!`.
For example, if I call `factorial(5)`, then the computation is `5 * 4 * 3 * 2 * 1` and the result will be `120`. For example, if I call `factorial(5)`, then the computation is `5 * 4 * 3 * 2 * 1` and the result will be `120`.
If I call `factorial(5)` over and over, the result will always be the same. The _factorial_ calculation is If I call `factorial(5)` over and over, the result will always be the same. The _factorial_ calculation is
a good candidate for caching. a good candidate for caching.
@@ -54,12 +55,14 @@ the "_source of truth_" and therefore does not strictly need to be consistent wi
NOTE: Do not take "_inconsistency_" between the cache and database to mean that the application will read stale data. NOTE: Do not take "_inconsistency_" between the cache and database to mean that the application will read stale data.
It simply means there will be a penalty to reload/refresh the data the next time the data is requested. It simply means there will be a penalty to reload/refresh the data the next time the data is requested.
While _Inline Caching_ can take several forms, the form of _Inline Caching_ we present here will be an extension to But, this guide is not about Look-Aside Caching, it is about _Inline Caching_. While _Inline Caching_ can take several
the _Look-Aside Cache_ pattern. This particular form of _Inline Caching_ is useful in cases where: forms, the form of _Inline Caching_ we present here will be an extension to the _Look-Aside Cache_ pattern.
This particular form of _Inline Caching_ is useful in cases where:
1. Consistency between the Cache and Database is important, or... 1. Consistency between the Cache and Database is important, or...
2. Having access to the latest, most up-to-date information from the backend SOR is crucial (e.g. time sensitive). 2. Having access to the latest, most up-to-date information from the backend SOR is crucial (e.g. time sensitive).
3. Multiple, discrete and disparate applications are sharing the same data source (contrary to Microservices design). 3. Multiple, discrete & disparate applications are sharing the same data source (contrary to Microservices design).
4. The application is distributed across multiple sites. 4. The application is distributed across multiple sites.
There maybe other reasons. There maybe other reasons.
@@ -70,13 +73,13 @@ method. The overloaded `Cache.get(..)` method accepts a `Callable` argument, wh
from an external data source, as defined by the `Callable`, on a cache miss. If a value for the given key is not present from an external data source, as defined by the `Callable`, on a cache miss. If a value for the given key is not present
in the cache, then the `Callable` will be invoked to load a value for the given key. in the cache, then the `Callable` will be invoked to load a value for the given key.
This form of _Inline Caching_ is very basic since 1) most application developers are not directly interfacing with This form of _Inline Caching_ is very basic since 1) most application developers are not interfacing with _Spring's
_Spring's Cache Abstraction_ in their application by using the `org.springframework.cache.Cache` API. Most of the time, Cache Abstraction_ in their application by using the `org.springframework.cache.Cache` API directly. Most of the time,
application developers will use the Spring cache annotations (e.g. `@Cacheable`), or alternatively the JCache (JSR-107) application developers will use the Spring cache annotations (e.g. `@Cacheable`), or alternatively, the JSR-107,
annotations, as {spring-framework-docs}/integration.html#cache-jsr-107[documented]. 2) Second, while the JCache API annotations, as {spring-framework-docs}/integration.html#cache-jsr-107[documented]. 2) Second, while
`Cache.get(:Object, :Callable)` satisfies _read-through_ to the backend, external data source, there is no equivalent `Cache.get(..)` satisfies _read-through_ to the backend, external data source, there is no equivalent operation
operation in the `Cache` API for _write-through_, i.e. when using `Cache.put(key, value)` to put a value into the cache in the `Cache` API for _write-through_, i.e. when using `Cache.put(key, value)` to put a value into the cache
in addition to the external data source. in addition to writing back to the external data source.
With _Inline Caching_, the _read & write through_ to/from the backend data source are intrinsic characteristics With _Inline Caching_, the _read & write through_ to/from the backend data source are intrinsic characteristics
of _Inline Caching_. Additionally, on _write-through_, the cache op (i.e. `put(key, value)`) does not succeed of _Inline Caching_. Additionally, on _write-through_, the cache op (i.e. `put(key, value)`) does not succeed
@@ -86,30 +89,30 @@ and therefore consistent.
WARNING: There are still moments when the cache could be observed in an inconsistent state relative to the backend WARNING: There are still moments when the cache could be observed in an inconsistent state relative to the backend
database, such as between a database update and a cache refresh on a cache hit. This means the value was in the cache database, such as between a database update and a cache refresh on a cache hit. This means the value was in the cache
but may not have been the latest value when requested since the database may have been updated by some other means but may not have been the latest value when requested since the database may have been updated by some other means
(e.g. another application not using _Inline Caching_, thus not using a synchronous _write-through_ and instead (e.g. another application updating the database directly, not using _Inline Caching_ with a synchronous _write-through_).
updating the database directly). To keep the cache and database consistent, then all data access operations must To keep the cache and database consistent, then all data access operations must involve the cache. That is, you must
involve the cache. That is, you must strictly adhere to and be diligent in your use of _Inline Caching_. strictly adhere to and be diligent in your use of _Inline Caching_.
_Inline Caching_ can be depicted in the following diagram: _Inline Caching_ can be depicted in the following diagram:
image::../images/Inline-Caching-Pattern.png[] image::../images/Inline-Caching-Pattern.png[]
In the diagram above, there are 2 flows: 1 for _read-through_ (right-side) and another for _write-through_ (left-side). In the diagram above, there are 2 flows: 1 for _read-through_ (right-side) and another for _write-through_ (left-side).
Both can occur in a single operation, on read. Both can occur in a single operation, on a read.
When a client sends a request for data (#6) the request invokes the appropriate application (`@Cacheable`) service method, When a client sends a request for data (*#6*) the request invokes the appropriate application (`@Cacheable`) service
which is immediately forwarded to the cache to determine if the results for the given input have already been computed (#5). method, which is immediately forwarded to the cache to determine if the results for the given input have already been
If the result is cached (a _cache hit_), then the result is simply returned to the caller. However, if a result had not computed (*#5*). If the result is cached (a _cache hit_), then the result is simply returned to the caller. However,
been previously computed, or the result expired, or was evicted, then before the cacheable service method is invoked, if a result had not been previously computed, or the result expired, or was evicted, then before the cacheable service
an additional lookup is performed (#4) to determine whether the computed value may have already been persisted to method is invoked, an additional lookup is performed (*#4*) to determine whether the computed value may have already
the backend database. If the value exists in the database, then it is loaded into the cache and returned to the caller. been persisted to the backend database. If the value exists in the database, then it is loaded into the cache
Only when the computed value is not present in the cache nor exists in the database is the cacheable service method invoked. and returned to the caller. Only when the computed value is not present in the cache nor exists in the database is the
Once the service method finishes and returns the result, the value is cached as part of the contract of `@Cacheable` cacheable service method invoked. Once the service method finishes and returns the result, the value is cached as part
and will also be written through to the backend database. of the contract of `@Cacheable` and will also be written through to the backend database.
During a client request to compute some value regardless of the cache or database state (#1), the service method is During a client request to compute some value regardless of the cache or database state (*#1*), the service method is
always invoked (as specified in the contract for the `@CachePut` annotation). Upon completing the computation, always invoked (as specified in the contract for the `@CachePut` annotation). Upon completing the computation,
the result is cached (#2) and additionally persisted to the database (#3), which describes the _write-through_. the result is cached (*#2*) and additionally persisted to the database (*#3*), which describes the _write-through_.
If the database INSERT/UPDATE is not successful on write, then the cache will not contain the value. If the database INSERT/UPDATE is not successful on write, then the cache will not contain the value.
Now it is time to make all of this a bit more concrete with an example. Now it is time to make all of this a bit more concrete with an example.
@@ -117,7 +120,7 @@ Now it is time to make all of this a bit more concrete with an example.
[[geode-samples-caching-inline-example]] [[geode-samples-caching-inline-example]]
== Example == Example
For our example, we will develop a calculator service that performs basic mathematical functions, such as For our example, we will develop a calculator application that performs basic mathematical functions, such as
`factorial`. Again, not that practical, but a useful and simple demonstration allowing us to focus on `factorial`. Again, not that practical, but a useful and simple demonstration allowing us to focus on
our primary concern, which is to enable and use _Inline Caching_. our primary concern, which is to enable and use _Inline Caching_.
@@ -132,15 +135,15 @@ We start by defining the supported mathematical functions in a `CalculatorServic
include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/service/CalculatorService.java[tags=class] include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/service/CalculatorService.java[tags=class]
---- ----
The `CalculatorService` is annotated with Spring's `@Service` stereotype annotation so it is picked up by The `CalculatorService` is annotated with Spring's `@Service` stereotype annotation so that it will be picked up by
the Spring Container's classpath component scan process, which has been carefully configured by Spring Boot. the Spring Container's classpath component scan process, which has been carefully configured by Spring Boot.
The class also extends the `AbstractCacheableService` base class, inheriting a couple `boolean` methods that signal The class also extends the `AbstractCacheableService` base class, inheriting a couple `boolean` methods that signal
whether cache access resulted in a hit or miss. whether cache access resulted in a hit or miss.
In addition, the `CalculatorService` contains two mathematical functions: `factorial` and `sqrt` (square root). In addition, the `CalculatorService` contains two mathematical functions: `factorial` and `sqrt` (_square root_).
Each method caches the result of the computation using the input (operand) and operator as the key. If the method Each method caches the result of the computation using the input (operand) and operator as the key. If the method
is called 2 or more times with the same input, the cached result will be returned, providing the cache entry has not is called 2 or more times with the same input, the cached result will be returned, providing the cache entry has not
expired or been evicted. We neither configure eviction nor expiration for this example. expired or been evicted. We neither configure eviction nor expiration for this example, however.
Both the `factorial(..)` and `sqrt(..)` methods have been annotated with Spring's `@Cacheable` annotation to demarcate Both the `factorial(..)` and `sqrt(..)` methods have been annotated with Spring's `@Cacheable` annotation to demarcate
these methods with caching behavior. Of course, as explained in SBDG's <<geode-caching-provider,documentation>>, these methods with caching behavior. Of course, as explained in SBDG's <<geode-caching-provider,documentation>>,
@@ -149,10 +152,10 @@ when running in Pivotal CloudFoundry (PCF)) as the caching provider is enabled b
more you need do to start leverage caching in your Spring Boot applications than to annotate the service methods more you need do to start leverage caching in your Spring Boot applications than to annotate the service methods
with the appropriate Spring or JSR-107, JCache API annotations. Simple! with the appropriate Spring or JSR-107, JCache API annotations. Simple!
It is worth noting, however, that we are starting with the same applied pattern for caching as you would when using It is worth noting that we are starting with the same applied pattern of caching as you would when using the
the _Look-Aside Caching_ pattern. This is key to minimizing the invasive nature of _Inline Caching_. There is _Look-Aside Caching_ pattern. This is key to minimizing the invasive nature of _Inline Caching_. There is a subtle
a subtle difference, though, and that will be apparent in the additional configuration we supply as part of our difference, though, and that will be apparent in the additional configuration we supply as part of our Spring Boot
Spring Boot application. application.
Let's look at that next. Let's look at that next.
@@ -174,10 +177,10 @@ SBDG provides the `InlineCachingRegionConfigurer` class used in the bean definit
(a.k.a. as Regions in Apache Geode & Pivotal GemFire terminology) with _Inline Caching_ behavior. (a.k.a. as Regions in Apache Geode & Pivotal GemFire terminology) with _Inline Caching_ behavior.
The Configurer's job is to configure the appropriate Spring Data (SD) _Repository_ used as a Region's `CacheLoader` The Configurer's job is to configure the appropriate Spring Data (SD) _Repository_ used as a Region's `CacheLoader`
mechanism for "_read-through_" behavior as well as configure the same SD _Repository_ for a Region's `CacheWriter` for "_read-through_" behavior as well as configure the same SD _Repository_ for a Region's `CacheWriter` for
mechanism for "_write-through_" behavior. This "_read/write-through_" behavior is the "inlining" component of "_write-through_" behavior. This "_read/write-through_" behavior is the "inlining" component of _Inline Caching_,
_Inline Caching_, i.e. the second lookup opportunity we talked about in the i.e. the second lookup opportunity we talked about in the <<geode-samples-caching-inline-background,Background>>
<<geode-samples-caching-inline-background,Background>> section above. section above.
The `CacheLoader/Writer` also ensures consistency between the cache and the backend data store, such as a database. The `CacheLoader/Writer` also ensures consistency between the cache and the backend data store, such as a database.
@@ -191,9 +194,10 @@ include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/r
NOTE: Spring Data's Repository abstraction is used rather than providing direct access to some `DataSource` for NOTE: Spring Data's Repository abstraction is used rather than providing direct access to some `DataSource` for
the backend data store since 1) Spring Data Repository abstraction {spring-data-website}[supports] a wide-array the backend data store since 1) Spring Data Repository abstraction {spring-data-website}[supports] a wide-array
of backend data stores, uniformly and 2) it is easy to compose multiple Spring Data Repositories as one (using of backend data stores uniformly and 2) it is easy to compose multiple Spring Data Repositories as one (using
the https://en.wikipedia.org/wiki/Composite_pattern[Composite pattern]) if you want to write to multiple backend the https://en.wikipedia.org/wiki/Composite_pattern[Composite pattern]) if you want to write to multiple backend
data stores and 3) has a very consistent and intuitive API, following the https://www.baeldung.com/java-dao-pattern[Data Access Object (DAO) pattern] data stores and 3) Spring Data has a very consistent and intuitive API, based on the
https://www.baeldung.com/java-dao-pattern[Data Access Object (DAO) pattern]
for defining basic CRUD and simple query data access operations. Typically, the `DataSource` must be wrapped for defining basic CRUD and simple query data access operations. Typically, the `DataSource` must be wrapped
by a higher-level API to make use of the backend data store in Java anyway, like JDBC for databases, or even higher, by a higher-level API to make use of the backend data store in Java anyway, like JDBC for databases, or even higher,
such as by using an ORM tool (e.g. JPA with Hibernate). such as by using an ORM tool (e.g. JPA with Hibernate).
@@ -208,66 +212,66 @@ all regions by simply supplying the following `Predicate`:
regionBeanName -> true; regionBeanName -> true;
---- ----
In our case, we only want to target the Regions that have been used as "caches" identified in the service methods In our case, we only want to target the Regions that have been used as "caches" as identified in the service methods
annotated with Spring's `@Cacheable` annotation, to be enabled with and use _Inline Caching_". annotated with Spring's `@Cacheable` annotation, to be enabled with and use _Inline Caching_.
The `Predicate` allows you to target different Regions using different Spring Data _Repositories_, and by extension The `Predicate` allows you to target different Regions using different Spring Data _Repositories_, and by extension
different backend data stores, for different purposes, depending on your application uses cases. different backend data stores, for different purposes, depending on your application uses cases.
For example, you may have a cache Region X containing data that needs to be stored in MongoDB (then simply use For example, you may have a cache Region X containing data that needs to be stored in MongoDB (use https://spring.io/projects/spring-data-mongodb[Spring Data MongoDB]),
https://spring.io/projects/spring-data-mongodb[Spring Data MongoDB's] Repository support), where as another where as another cache Region Y may contain data that needs to be written to Neo4j and represented as a graph (use
cache Region Y may contain data that needs to be written to Neo4j and represented as a graph (cool, then simply use https://spring.io/projects/spring-data-neo4j[Spring Data Neo4j's]), and yet another cache Region Z containing data
https://spring.io/projects/spring-data-neo4j[Spring Data Neo4j's] Repository support), and yet another cache Region Z that needs to be written back to a database (use https://spring.io/projects/spring-data-jdbc[Spring Data JDBC]
containing data that needs to be written back to a database (no problem, use https://spring.io/projects/spring-data-jdbc[Spring Data JDBC]
or https://spring.io/projects/spring-data-jpa[Spring Data JPA]). or https://spring.io/projects/spring-data-jpa[Spring Data JPA]).
This is what makes the Spring Data _Repository_ pattern so ideal. It is very flexible and has a highly consistent This is what makes the Spring Data _Repository_ pattern so ideal. It is very flexible and has a highly consistent
API (i.e. programming model) across a disparate grouping of data stores. And due to that uniformity, it is easy to API across a disparate grouping of data stores. And due to that uniformity, it is easy to "adapt" the GemFire/Geode
"adapt" the GemFire/Geode `CacheLoader`/`CacheWriter` interfaces to use a SD Repository under-the-hood. Indeed, `CacheLoader`/`CacheWriter` interfaces to use a SD Repository under-the-hood. Indeed, that is exactly what SBDG
that is exactly what SBDG has done for you! has done for you!
We will circle back to the `resultKeyGenerator` bean definition after we talk about the application domain model. We will circle back to the `resultKeyGenerator` bean definition after we talk about the application domain model.
Also notice the use of the `@EnableCachingDefinedRegions` annotation. Whenever you use a caching provider like Also notice the use of the `@EnableCachingDefinedRegions` annotation.
Apache Geode or Redis, you must explicitly define or declare your caches in some manner. The inconvenience of this is,
you have basically already done so by declaring the caches required by your application when using Spring's,
or alternatively, the JSR-107, JCache API annotations (e.g. `@Cacheable`). Why should you have to do this again?
Well, with SBDG, you don't. You simply have to declare the `@EnableCachingDefinedRegions` annotation and SBDG Whenever you use a caching provider like Apache Geode or Redis, you must explicitly define or declare your caches
will take care of defining the Apache Geode Regions backing the caches for you. in some manner. This is inconvenient since you have basically already declared the caches required by your application
when using Spring's, or alternatively, the JSR-107, JCache API annotations (e.g. `@Cacheable`). Why should you have to
do this again? Well, using SBDG, you don't. You simply have to declare the `@EnableCachingDefinedRegions` annotation
and SBDG will take care of defining the necessary Apache Geode Regions backing the caches for you.
This is not auto-configured for you because there are many ways to "define" a Region, with different eviction Regions for caches are not auto-configured for you because there are many different ways to "define" a Region, with
and expiration polices, different memory requirements, application callbacks, etc. The Region may already exist different configuration, such as eviction and expiration polices, memory requirements, application callbacks, etc.
and have been created some other way. Either way, you may not want SBDG to auto-configure these Regions for you. The Region may already exist and have been created some other way. Either way, you may not want SBDG to auto-configure
these Regions for you.
TIP: If you have not done so already, you should definitely read about SBDG's support for _Inline Caching_ in the TIP: If you have not done so already, you should definitely read about SBDG's support for _Inline Caching_
<<geode-caching-provider-inline-caching,documentation>>. in the link:../caching.html#geode-caching-provider-inline-caching[].
TIP: To learn more about how Apache Geode's (or Pivotal GemFire's) data loading mechanics work, or specifically, how to TIP: To learn more about how Apache Geode's (or Pivotal GemFire's) data loading functionality works, or specifically,
{apache-geode-docs}/developing/outside_data_sources/sync_outside_data.html["Keep the Cache in Sync with Outside how to {apache-geode-docs}/developing/outside_data_sources/sync_outside_data.html["Keep the Cache in Sync with Outside
Data Sources"] follow the link. You may also learn more by reading the _Javadoc_ for Data Sources"] follow the link. You may also learn more by reading the _Javadoc_ for
{apache-geode-javadoc}//org/apache/geode/cache/CacheLoader.html[`CacheLoader`] {apache-geode-javadoc}//org/apache/geode/cache/CacheLoader.html[`CacheLoader`]
and {apache-geode-javadoc}/org/apache/geode/cache/CacheWriter.html[`CacheWriter`]. and {apache-geode-javadoc}/org/apache/geode/cache/CacheWriter.html[`CacheWriter`].
TIP: To learn more about `@EnableCachingDefinedRegions`, see the Spring Data for Apache Geode TIP: To learn more about `@EnableCachingDefinedRegions`, see the Spring Data for Apache Geode
{spring-data-geode-docs-html}/#bootstrap-annotation-config-caching[documentation]. {spring-data-geode-docs}/#bootstrap-annotation-config-caching[documentation].
[[geode-samples-caching-inline-example-calculator-configuration]] [[geode-samples-caching-inline-example-calculator-configuration]]
=== Backend Data Source Configuration === Backend DataSource Configuration
While we used Spring Data's Repository abstraction as the way to access data in the backend data store used for While we used Spring Data's Repository abstraction as the way to access data in the backend data store used for
_Inline Caching_, we have not shown how the data source for the backend data store was configured. _Inline Caching_, we have not shown how the data source for the backend data store was configured.
Obviously, the data source connecting the application to the backend data store varies from data store to data store. Obviously, the data source connecting the application to the backend data store varies from data store to data store.
Clearly, when using a database, you would configure a `javax.sql.DataSource` using the JDBC API. That `DataSource` is Clearly, when using a database, you would configure a `javax.sql.DataSource` using the JDBC API. That `DataSource` is
then plugged into a higher-level data access API like JDBC, or Spring's `JdbcTemplate`, or JPA to perform data access. then plugged into a higher-level data access API like JDBC, or Spring's `JdbcTemplate`, or JPA, to perform data access.
With MongoDB or Redis, again you would configure the data source, or connection factory, appropriately and plug that With MongoDB or Redis, again you would configure the data source, or connection factory, appropriate for those stores
into the data access API of choice (e.g. Spring Data MongoDB or Spring Data Redis). and plug that into the data access API of your choice (e.g. Spring Data MongoDB or Spring Data Redis).
Though it is not immediately apparent, in our example, we simply 1) used an embedded databased (i.e. HSQLDB) Though it is not immediately apparent in our example, we simply 1) used an embedded, in-memory database (i.e. HSQLDB)
and 2) relied on Spring Boot's _auto-configuration_ to bootstrap the embedded database on startup. and 2) relied on Spring Boot's _auto-configuration_ to bootstrap the embedded database on startup.
TIP: To learn more about {spring-boot-docs-html}/boot-features-sql.html#boot-features-embedded-database-support[bootstrapping] TIP: To learn more about {spring-boot-docs}/boot-features-sql.html#boot-features-embedded-database-support[bootstrapping]
an embedded database and the embedded databases that can be _auto-configured_ by Spring Boot, follow the link. an embedded database and the embedded databases that can be _auto-configured_ by Spring Boot, follow the link.
In a nutshell, we only need to declare a dependency on `spring-jdbc` and the embedded database we want to use In a nutshell, we only need to declare a dependency on `spring-jdbc` and the embedded database we want to use
@@ -288,43 +292,42 @@ as the backend data store for _Inline Caching_, like so:
---- ----
The `spring-jdbc` dependency is transitively pulled in by `org.springframework.boot:spring-boot-starter-data-jpa`, The `spring-jdbc` dependency is transitively pulled in by `org.springframework.boot:spring-boot-starter-data-jpa`,
which also pulls in Spring Data JPA. Therefore, we are using JPA, and specifically the Hibernate JPA provider, which also pulls in Spring Data JPA. Therefore, we are using JPA, and specifically, the Hibernate JPA provider,
to back our Spring Data Repository (i.e. `CalculatorRepository`) in this example. to back our Spring Data Repository (i.e. `CalculatorRepository`) in this example.
With these dependencies declared on the application classpath, Spring Boot auto-configures a `DataSource` to With these dependencies declared on the application's classpath, Spring Boot _auto-configures_ a `DataSource` to
an embedded HSQLDB database, which bootstraps, finds our application `CalculatorRepository` interface declaration, an embedded HSQLDB database, bootstraps HSQLDB, finds our application `CalculatorRepository` interface declaration,
and backs it with a Spring Data JPA implementation using Hibernate as the provider. Very powerful! and backs it with a Spring Data JPA implementation using Hibernate as the provider. Very powerful!
Additionally, we configure the embedded, in-memory HSQLDB database by including a SQL script with a DDL statement Additionally, we configure our embedded HSQLDB database by including a SQL script with DDL statements to initialize
to initialize the schema (i.e. create the "CALCULATIONS" table): the schema (i.e. create the "CALCULATIONS" table):
.Schema.sql .schema.sql
[source,sql] [source,sql]
---- ----
include::{samples-dir}/caching/inline/src/main/resources/schema.sql[] include::{samples-dir}/caching/inline/src/main/resources/schema.sql[]
---- ----
As well as include a SQL script with DML to to pre-populate the database with some existing data We also include a SQL script containing DML statements to populate the database with some existing data
(i.e. mathematical calculations) in order to simulate cache hits: (i.e. mathematical calculations) in order to simulate cache hits:
.Data.sql .data.sql
[source,sql] [source,sql]
---- ----
include::{samples-dir}/caching/inline/src/main/resources/data.sql[] include::{samples-dir}/caching/inline/src/main/resources/data.sql[]
---- ----
Simply by including the `schema.sql` and complimentary `data.sql` files in the classpath of the application, Spring Boot By simply including `schema.sql` and the complimentary `data.sql` files in the classpath of the application, Spring Boot
will automatically detect these files and apply them to the database during startup. will automatically detect these files and apply them to the database during startup.
TIP: To learn more about embedded, in-memory database initialization applied by Spring Boot, see TIP: To learn more about embedded, in-memory database initialization applied by Spring Boot, see
{spring-boot-docs-html}/howto-database-initialization.html[here]. {spring-boot-docs}/howto-database-initialization.html[here].
[[geode-samples-caching-inline-example-calculator-model]] [[geode-samples-caching-inline-example-calculator-model]]
=== Application and Data Modeling === Application and Data Modeling
The final component of our application up for discussion is the application domain model (as compared to The final component of our application up for discussion is the application domain model (as compared to
the data model). There is not a whole lot of difference (i.e. the structure and mapping is relatively 1-to-1), but it the data model). There is not a whole lot of difference; the structure and mapping is relatively 1-to-1.
is worth noting.
The results from the mathematical calculations are captured in an instance of the `ResultHolder` class: The results from the mathematical calculations are captured in an instance of the `ResultHolder` class:
@@ -336,10 +339,11 @@ include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/m
This class uses https://projectlombok.org/[Project Lombok] to simplify the implementation. This class uses https://projectlombok.org/[Project Lombok] to simplify the implementation.
It is also a (JPA) persistent entity as designated by the `javax.persistence.Entity` annotation. It is also a JPA persistent entity as designated by the `javax.persistence.Entity` annotation.
We also define a composite, primary key, i.e the `ResultHolder.ResultKey` class, which consists of the `operand` to We also define a composite, primary key (i.e. `ResultHolder.ResultKey`), which consists of the `operand` to
the mathematical function along with the `Operator`, which we have defined as an enumerated type. the mathematical function along with the `Operator`, which has been defined as an enumerated type and is
the mathematical function being computed (e.g. _factorial_).
This is also why, as briefly alluded to back in the section on <<geode-samples-caching-inline-example-calculator-configuration>>, This is also why, as briefly alluded to back in the section on <<geode-samples-caching-inline-example-calculator-configuration>>,
the `resultKeyGenerator` bean definition was important: the `resultKeyGenerator` bean definition was important:
@@ -350,7 +354,7 @@ the `resultKeyGenerator` bean definition was important:
include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/config/CalculatorConfiguration.java[tags=key-generator] include::{samples-dir}/caching/inline/src/main/java/example/app/caching/inline/config/CalculatorConfiguration.java[tags=key-generator]
---- ----
Where the custom `KeyGenerator` was applied in the caching annotations of the service method like so: This custom `KeyGenerator` was applied in the caching annotations of the service method like so:
.Result KeyGenerator use .Result KeyGenerator use
[source,java] [source,java]
@@ -366,15 +370,17 @@ class CalcultorService ... {
---- ----
Basically, the keys between the cache and the database (i.e. the primary key) must match. This is because the cache key Basically, the keys between the cache and the database (i.e. the primary key) must match. This is because the cache key
is used as the identifier in all data access operations against the backend database using the `CalculatorRepository` is used as the identifier in all data access operations performed against the backend database using the
(e.g. `calculatorRepository.findById(cacheEntry.getKey())`, in the cache loader's (i.e. _read-through_) case). `CalculatorRepository` (e.g. `calculatorRepository.findById(cacheEntry.getKey())`, specifically in the cache loader's
(i.e. the _read-through_) case).
If a custom `KeyGenerator` had not been provided, then the "key" would have been the service method parameter only, If a custom `KeyGenerator` had not been provided, then the "key" would have been the `@Cacheable` service method
and as I already state, the primary key in the database table is a composite key consisting of the operand and operator. parameter only (i.e. the integer number or operand in the mathematical function), and as I already stated, the primary
This was deliberate because... key in the database table is a composite key consisting of both the operand and the operator. This was deliberate
because...
The most fundamental difference between the application domain model and the database model is that while The most fundamental difference between the application domain model and the database model is that while
the application keeps the mathematical calculations in separate, 2 distinct caches (Regions), as seen the application keeps the mathematical calculations in 2 separate, distinct caches (Regions), as seen
in the `@Cacheable annotation on the individual service methods: in the `@Cacheable annotation on the individual service methods:
.Declared caches .Declared caches
@@ -392,16 +398,17 @@ class CalcultorService ... {
} }
---- ----
The database, on the other hand, stores all mathematical calculations in the same table. That is, both _factorial_ The database, on the other hand, stores all mathematical calculations in the same table. That is, both _factorials_
and _square root_ calculations are stored together in the "CALCULATIONS" table. and _square roots_ are stored together in the "CALCULATIONS" table.
This is also why the `operand` cannot be used as the primary key by itself. If a user of our Calculator application This is also why the `operand` cannot be used as the primary key by itself. If a user of our Calculator application
performed both `factorial(4) = 24` and `sqrt(4) = 2`, how do we know which result the user wants just by looking performed both `factorial(4) = 24` and `sqrt(4) = 2`, how do we know which result the user wants just by looking
at the operand when performing the cache lookup. You can't. You need to know the `operator`, too. at the operand when performing the cache lookup. You dons't. You need to know the `operator`, too.
While the individual `CalculatorService` methods for the mathematical functions determine which `operator` is in play, While the individual `CalculatorService` methods for the mathematical functions determine which `operator` is in play,
and even the results of the calculations are kept separately in distinct caches, and therefore, there can only be 1 and even while the results of the calculations are kept separately in distinct caches, and therefore, there can only be
result per entry (i.e. `operand`) in the individual caches, the database table is not like the cache or the application. one result per entry (i.e. `operand`) in the individual caches, the database table is not like the cache
or the application.
Again, this design was very deliberate in order to show the flexibility you have in modeling your application, Again, this design was very deliberate in order to show the flexibility you have in modeling your application,
your cache and your database, independently of each other. After all, you may be building a new application for your cache and your database, independently of each other. After all, you may be building a new application for
@@ -418,13 +425,13 @@ Now, it is time for us to run the example.
WARNING: The observant reader will have noticed that the `CalculatorService` uses `int` as the data type for the input WARNING: The observant reader will have noticed that the `CalculatorService` uses `int` as the data type for the input
and output of the mathematical functions. You should never use `int` to implement any mathematical calculations for any and output of the mathematical functions. You should never use `int` to implement any mathematical calculations for any
enterprise applications, ever. Instead, you should use either `java.math.BigDecimal` or `java.math.BitInteger`. enterprise applications, ever. Instead, you should use either `java.math.BigDecimal` or `java.math.BitInteger`.
One of many reasons for this, especially in _factorial's_ case, is that it is very easy to "overflow" the allowed values One of many reasons for this, especially in _factorial's_ case, is that it is very easy to "_overflow_" the allowed
of an `int` type, which is 32 bits. In fact, with `factorial(13)` you exceed the range of allowed integer values values of an `int` type, which is 32 bits. In fact, with `factorial(13)` you exceed the range of allowed integer values
represented by an `int`,. Even `long` is not sufficient in most cases. Therefore, the `CalculatorService` is very represented by an `int`. Even `long` is not sufficient in most cases. Therefore, the `CalculatorService` is very
limited in its utility. `int` was used primarily to minimize type conversions between store types and keep the example limited in its utility. `int` was used primarily to minimize type conversions between store types and keep the example
as simple as possible. as simple as possible.
The Calculator application includes a `CalculatorController`, which is a Spring Web MVC `@RestController` The Calculator application includes a `CalculatorController`, which is a Spring Web MVC `@RestController`,
containing the following web service endpoints: containing the following web service endpoints:
.Calculator Web Service Endpoints .Calculator Web Service Endpoints
@@ -438,44 +445,47 @@ containing the following web service endpoints:
|=== |===
Keep in mind that the following data has been loaded into the backend database already, which is indirectly treated as Keep in mind that the following data set has been loaded into the backend database already, which is indirectly treated
"cached" data: as "cached" data:
.Data.sql .data.sql
[source,sql] [source,sql]
---- ----
include::{samples-dir}/caching/inline/src/main/resources/data.sql[] include::{samples-dir}/caching/inline/src/main/resources/data.sql[]
---- ----
For example, if we call `http://localhost:8080/caculator/factorial/4`, you will see the following output: If you call `http://localhost:8080/caculator/factorial/4`, you will see the following output:
image::../images/factorial-of-four-before.png[] image::../images/factorial-of-four-before.png[]
The output shows the result of `factorial(4)` was `24`, that the calculation too 3096 milliseconds and the operation The output shows the result of `factorial(4)` is *24*, that the calculation took *3096 _milliseconds_* and the operation
resulted in a _cache miss_. However, now that we computed the the `factorial(4)`, the value ended up in the "cache" resulted in a *_cache miss_*. However, now that we computed `factorial(4)`, the result was put into the "cache"
as well as the backend (embedded, in-memory HSQLDB) database. So, if we run the operation again, the `latency` as well as INSERTED into the backend (embedded, in-memory HSQLDB) database. So, if we run the operation again,
drops to zero (and `cacheMiss` is *false*): the `latency` drops to zero (and *_cacheMiss_* is *_false_*):
image::../images/factorial-of-four-after.png[] image::../images/factorial-of-four-after.png[]
That is because the result (i.e. `24`) of `factorial(4)` is "cached" in Apache Geode (as well as persisted to That is because the result (i.e. *24*) of `factorial(4)` is "cached" in Apache Geode (as well as persisted to
the database, i.e. _write-through_) and therefore, the `CaculatorService.factorial(:int)` method is *not* called. the database; _write-through_) and therefore, the `CaculatorService.factorial(:int)` method is *not* called.
The result, however, is pulled from the cache, not the database. The result, however, is pulled from the cache, not the database.
To see the effects of the `factorial(:int)` method involving the database as part of the inline cache lookup, we can To see the effects of the `factorial(:int)` method involving the database as part of the inline cache lookup, you can
call `http://localhost:8080/caculator/factorial/5`. *5* is stored in the database, but is not currently present call `http://localhost:8080/caculator/factorial/5`. *5* is stored in the database, but is not currently present
in the cache: in the cache:
image::../images/factorial-of-five-before.png[] image::../images/factorial-of-five-before.png[]
If you hit refresh in your browser now, the application will get the result of `factorial(5)` from the cache While the latency is much better than invoking the _factorial_ function, it is still not as fast as pulling the result
since the result was loaded from the database and put into the cache (_read-through_), and we see that the latency from the cache.
drops from 12 ms to 0 ms. However, in both cases, the `cacheMiss` was *false* because the value was found without
invoking the `CalculatorService.factorial(:int)` method: Now, if you hit refresh in your browser, the application will get the result of `factorial(5)` from the cache since
the result was loaded from the database and put into the cache (_read-through_) during the first request. Therefore,
we see that the latency drops from *12 ms* to *0 ms*. However, in both cases, the *_cacheMiss_* was *_false_*
because the value was found (in the database) without invoking the `CalculatorService.factorial(:int)` method:
image::../images/factorial-of-five-after.png[] image::../images/factorial-of-five-after.png[]
You can play around with the _square root_ operation as well and see the same effects. You can play around with the _square root_ operation to see the same effects of _Inline Caching_.
[[geode-samples-caching-inline-example-test]] [[geode-samples-caching-inline-example-test]]
== Tests == Tests
@@ -483,18 +493,19 @@ You can play around with the _square root_ operation as well and see the same ef
The Calculator application includes an Integration Test class with tests asserting the behavior demonstrated above The Calculator application includes an Integration Test class with tests asserting the behavior demonstrated above
in the example. The test class is available here: in the example. The test class is available here:
{samples-dir}/caching/inline/src/test/java/example/app/caching/inlineCalculatorApplicationIntegrationTests.java link:{samples-dir}/caching/inline/src/test/java/example/app/caching/inline/CalculatorApplicationIntegrationTests.java[]
[[geode-samples-caching-inline-summary]] [[geode-samples-caching-inline-summary]]
== Summary == Summary
_Inline Caching_ is a powerful caching pattern when you have an external, backend data store that might also be serving _Inline Caching_ is a powerful caching pattern when you have an external, backend data store that doubles as the
as the application's _System of Record_ (SOR) and you need to keep the cache and database relatively in-sync application's _System of Record_ (SOR) and you need to keep the cache and database relatively in-sync with each other.
with each other.
_Inline Caching_ enables immediate _read-through_ and _write-through_ behavior that keeps the cache _Inline Caching_ enables immediate _read-through_ and _write-through_ behavior that keeps the cache
and database consistent. While the database can serve as a fallback option to priming the cache, the cache and database consistent. While the database can serve as a fallback option for priming the cache, the cache
will serve an important role in reducing the contention and load the backend database. will serve an important role in reducing the contention and load on the backend database.
As you have seen in this guide, configuration of _Inline Caching_ is very simple to do with SBDG when using Spring's As you have seen in this guide, the configuration of _Inline Caching_ is very simple to do with Spring Boot
Cache Abstraction along with Apache Geode as the caching provider. for Apache Geode (SBDG) when using Spring's Cache Abstraction along with Apache Geode as the caching provider.
link:../index.html#geode-samples[Back]

View File

@@ -13,7 +13,7 @@
This guide walks you through building a simple Spring Boot application This guide walks you through building a simple Spring Boot application
using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction] using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction]
backed by Apache Geode as the caching provider in a Look-Aside Caching use case. backed by Apache Geode as the caching provider for Look-Aside Caching.
It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's
_Cache Abstraction_ or Apache Geode is required to utilize caching in your Spring Boot applications. _Cache Abstraction_ or Apache Geode is required to utilize caching in your Spring Boot applications.

View File

@@ -14,7 +14,7 @@ with their Apache Geode / Pivotal GemFire, Spring Boot applications.
| link:guides/boot-configuration.html[Spring Boot Auto-Configuration for Apache Geode/Pivotal GemFire] | link:guides/boot-configuration.html[Spring Boot Auto-Configuration for Apache Geode/Pivotal GemFire]
| Explains what auto-configuration is provided by SBDG out-of-the-box and what the auto-configuration is doing. | Explains what auto-configuration is provided by SBDG out-of-the-box and what the auto-configuration is doing.
| {github-samples-url}/boot/configuration[Boot Auto-configuration] | {github-samples-url}/boot/configuration[Boot Auto-Configuration]
| link:guides/boot-actuator.html[Spring Boot Actuator for Apache Geode/Pivotal GemFire] | link:guides/boot-actuator.html[Spring Boot Actuator for Apache Geode/Pivotal GemFire]
| Explains how to use Spring Boot Actuator for Apache Geode and how it works. | Explains how to use Spring Boot Actuator for Apache Geode and how it works.
@@ -26,6 +26,7 @@ with their Apache Geode / Pivotal GemFire, Spring Boot applications.
| link:guides/caching-inline.html[Inline Caching with Spring's Cache Abstraction and Apache Geode] | link:guides/caching-inline.html[Inline Caching with Spring's Cache Abstraction and Apache Geode]
| Explains how to enable and use the Spring Cache Abstraction with Apache Geode as the caching provider for Inline Caching. | Explains how to enable and use the Spring Cache Abstraction with Apache Geode as the caching provider for Inline Caching.
This sample builds on the *_Look-Aside Caching_* sample above.
| {github-samples-url}/caching/inline[Inline Caching] | {github-samples-url}/caching/inline[Inline Caching]
|=== |===