diff --git a/spring-geode-docs/src/docs/asciidoc/guides/caching-inline-async.adoc b/spring-geode-docs/src/docs/asciidoc/guides/caching-inline-async.adoc index 9894910b..26550556 100644 --- a/spring-geode-docs/src/docs/asciidoc/guides/caching-inline-async.adoc +++ b/spring-geode-docs/src/docs/asciidoc/guides/caching-inline-async.adoc @@ -46,30 +46,32 @@ link:../index.html#geode-samples[Back to Samples] == Background In _Synchronous Inline Caching_, data is immediately read from or written to the primary data source, (a.k.a. the -_System of Record_ (SOR)), before the cache is modified, thereby guaranteeing a degree of consistency. The "synchronous" -arrangement of the _Inline Caching_ pattern is commonly referred to as "_Read/Write-Through_". +_System of Record_ (SOR)), before the cache is modified, thereby guaranteeing a degree of consistency between the cache +and the backend data source. The "synchronous" arrangement of the _Inline Caching_ pattern is commonly referred to as +"_Read/Write-Through_". With _Asynchronous Inline Caching_, data changes are written to the primary data source asynchronously, after the cache has already been modified. The "asynchronous" arrangement of the _Inline Caching_ pattern is commonly referred to as -"_Write-Behind_". The cache entry is modified, then, and only then, will the primary data source possibly reflect the -changes sometime later. +"_Write-Behind_". The cache entry is modified, then, and only then, will the primary data source reflect the changes +sometime later. -It is possible for the primary data source (i.e. _System of Record_ (SOR)) and the cache to get out-of-sync. Clearly, -the primary data source may contain information that the cache does not. Another application may be updating the primary -data source and not using the cache. The cache entry change may not be promptly written to the primary data source until -the "_Write-Behind_" operation is triggered, which is often implementation dependent. The data change might violate -a database constraint, fail to commit and be rolled back. All sorts of reasons can cause the primary data source -and the cache to become out-of-sync, or inconsistent. +Due to the asynchronous nature of _Async Inline Caching_, it is possible for the primary data source (i.e. _System +of Record_ (SOR)) and the cache to get out-of-sync. Additionally, the primary data source may contain information that +the cache does not. That is to say, another application may be updating the primary data source and not using the cache. +The cache entry change may not be promptly written to the primary data source until the "_Write-Behind_" operation is +triggered, which is often implementation dependent. A data change could violate a database constraint, fail to commit +and be rolled back. All sorts of reasons can cause the primary data source and the cache to become out-of-sync, +or inconsistent. -For this reason, throughput and latency are the primary applications concerns, rather than consistency, for when to use -_Asynchronous Inline Caching_. +For this reason, throughput and latency are the primary application concerns, rather than consistency, when using +the _Asynchronous Inline Caching_ pattern. The general pattern of Inline Caching is depicted as follows: image::{images-dir}/Inline-Caching-Overview.png[] -The layer in the application/system architecture involving _Inline Caching_ logic sits between the cache and the primary -data source: +The layer in the application/system architecture involving the _Inline Caching_ logic sits between the cache +and the primary data source: image::{images-dir}/Inline-Caching-Layer.png[] @@ -85,24 +87,379 @@ IMPLEMENTATION As readers should know, the application cache is backed by an {apache-geode-name} Region. -In _Synchronous_, _Read-Through_ and/or _Write-Through_, _Inline Caching_, a `CacheLoader` configured for the Region -and used to "_Read-Through_" to the backend/primary data source on a cache miss. When a cache entry is written, then -a configured `CacheWriter` for the Region is invoked to "_Write-Through_" to the backend/primary data source. The cache -is only modified if the `CacheWriter` was successful in modify the backend/primary data source. +In _Synchronous_, _Read-Through_ and/or _Write-Through_, _Inline Caching_, a `CacheLoader` is configured for the Region +and used to "_Read-Through_" to the backend/primary data source on a cache miss. When a cache entry is written, a +configured `CacheWriter` for the Region is invoked to "_Write-Through_" to the backend/primary data source. The cache +is only modified if the `CacheWriter` was successful in modifying the backend/primary data source. -Both the `CacheLoader` and `CacheWriter` are optional. That is, you can configure one side of -_Synchronous Inline Caching_, either the "_Read-Through_", or the "_Write-Through_", both, or neither. +Both the `CacheLoader` and `CacheWriter` are optional. That is, you can configure just one side of +_Synchronous Inline Caching_ or the other, either "_Read-Through_" or "_Write-Through_", both, or neither. With _Asynchronous, Write-Behind, Inline Caching_, you (may) configure the Region with an associated `AsyncEventQueue` (AEQ) and registered `AsyncEventListener`. When the cache is written to, the entry event is then forwarded and stored -on the AEQ, where at sometime later, the registered `AsyncEventListener` for the AEQ will be invoked, which can then -asynchronously modify the backend/primary data source. +on the AEQ, where at sometime later, the registered `AsyncEventListener` for the AEQ will be invoked to process the +(batch of) `AsyncEvents`, which can then asynchronously modify the backend/primary data source. Unlike _Synchronous Inline Caching_, _Asynchronous Inline Caching_ does not have an equivalent for "_Read-Through_", -such as "_Read-Behind_". +such as "_Read-Behind_", particularly in a Reactive sense. NOTE: At some point later, we may consider the development of "_Read-Behind_" with with use of Reactive Programming and the Reactive Spring Data Repository abstraction. link:../index.html#geode-samples[Back to Samples] +[[geode-samples-caching-inline-asynchronous-example]] +== Example + +For our example, we have built a Golf Tournament application that runs a simulation with a set of professional golfers +playing at _The Masters_. The (12) golfers play 18 holes of golf in pairs and proceed from hole 1 to hole 18 in under +a minute. For each hole played, their score of the whole is calculated. At the end of the round, each golfers final +score is calculated relative to par for the course (72). + +The Golf Tournament application is a Spring Boot application using {apache-geode-name} to persist the golfers score in +realtime as the players complete each hole. However, to make the play "official", the golfer's score is recorded to a +backend database, asynchronously using _Asynchronous_, _Write-Behind_, _Inline Caching_. It is assumed that there is +additional validation required (e.g. such as signing scorecards, etc) that goes on before the final score is accepted +and recorded to the _System of Record_ (SOR), in the "history books", so to speak. + +Now that the problem context is established, let's review a few of the classes. + +NOTE: Each of the application domain classes are code snippets or a preview of the actual class, and not actual code. +See the actual Sample code for more detail. + +We start by defining our Golf Tournament application domain model types, starting with the `Golfer` class. Essentially, +the `Golfer` class models a person who plays golf and is defined as: + +.`Golfer` class. +[source,java] +---- +@Entity +@Table(name = "golfers") +public class Golfer implements Comparable { + + @javax.persistence.Id @Id + private String name; + + private Integer hole = 0; + + private Integer score = 0; + +} +---- + +The `Golfer` class has been annotated with JPA's `@Entity` annotation making it a proper (persistent) entity class. +The `Golfer` class is also annotated with `@Table` to persist instances of `Golfer` into the "_golfers_" table +of the database. + +The application also defines a non-entity, `GolfCourse` class to model the golf course, which requires a name +and `List` of pars for each hole (all 18 holes) of the golf course: + +.`GolfCourse` class +[source,java] +---- +class GolfCourse { + + private final String name; + + private final List parForHole = new ArrayList<>(18); + +} +---- + +Next, a non-entity, `GolfTournament` class has been defined to model the golf tournament being played. It expects a name +for the tournament, the golf course` where the tournament is held and played, and a `Set` of `Golfers` (players) +registered to play. + +Additionally, the `GolfTournament` class contains an inner class, the `Pairing` class, to group the registered players +into pairs to play a round. + +.`GolfTournament` class +[source,java] +---- +class GolfTournament implements Iterable { + + private final String name; + + private GolfCourse golfCourse; + + private final List pairings = new ArrayList<>(); + + private final Set players = new ArrayList<>(); + + public static class Pair { + + private final Golfer playerOne; + private final Golfer playerTwo; + + } +} +---- + +The `GolfTournament.Pairing` class serves as a composite acting on both players in the pair, such as to advance +the hole of play. + +The `GolfTournament` class has additional builder methods to register players, build pairings, enable the tournament +to be played and determine when the tournament is finished (i.e. when all pairs complete all 18 holes of play). + +There is a `GolferRepository` interface extending the `JpaRepository` interface to persist the state of each `Golfer` +to the backend database: + +.`GolferRepository` interface +[source,java] +---- +interface GolferRepository extends JpaRepository { } +---- + +NOTE: While `GolferRepository` extends from the `JpaRepository` interface directly, it is recommended to extend +the `CrudRepository` interface instead, keeping your application SD _Repositories_ agnostic from the underlying +data store. The reason `GolferRepository` extends from the `JpaRepository` interface directly, is to make it absolutely +clear that the `Golfer` state will be persisted to a backend database (RDBMS) using JPA along with Hibernate as the +provider. + +The `GolferRepository` will be used by SBDG's _Asynchronous Inline Caching_ framework and infrastructure components. + +The _Repository_ is injected into and used by the `AsyncEventListener` registered on the AEQ attached to the "Golfers" +Region to perform asynchronous, _Write-Behind_, _Inline Caching_, operations to the backend database +and _System of Record_ (SOR). + +We'll see in a moment how this association is made and how _Asynchronous Inline Caching_ is setup, made simple by SBDG. + +To encapsulate the application logic and provide a (possibly transactional) facade to the `Golfer's` state, +a `GolferService` class has been defined: + +.`GolferService` class +[source,java] +---- +@Service +class GolferService { + + @CachePut(cacheNames = "Golfers", key = "#golfer.name") + public Golfer update(Golfer golfer) { + return golfer; + } + + public List getAllGolfersFromCache() { + // Use SDG GemfireTemplate to access the "Golfers" Region + } + + public List getAllGolfersFromDatabase() { + // Use the GolfersRepository to access the "Golfers" stored in the database. + } +} +---- + +The `GolferService` class has been marked as a application service using Spring's `@Service` stereotype annotation. + +Along with the `GolferService` the application uses a `PgaTourService` class to manage and run a (single) +`GolfTournament`. Its primary method used to run a `GolfTournament` is the `play()` method: + +.`PgaTourService` class, `play()` method +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/PgaTourService.java[tags=play] +---- + +This is a Spring `@Scheduled` service method called every 2.5 seconds after an initial delay of 5 seconds. Essentially, +the service method runs through the pairings, where each `Golfer` plays all 18 holes, their scores are calculated +and recorded for each hole until the round is completed, whether the players score is then calculated relative to par +for course and recorded to the cache, and eventually the database. + +To get everything started, a Spring Boot application class (i.e. a class annotated with the `@SpringBootApplication` +annotation) is used to bootstrap the application. + +.`BootGeodeAsyncInlineCachingClientApplication` class +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java[tags=class] +---- + +The `GolfTournament` is kicked off in the `ApplicationRunner`. + +.`ApplicationRunner` bean in the `GolfApplicationConfiguration` class +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java[tags=application-configuration] +---- + +As the golf tournament progresses (in the `@Scheduled`, PgaTourService.play()` service method), the updates to the +`Golfers` in the pairs are written to the "_Golfers_" cache (i.e. "_Golfers_" Region) by calling +the `GolferService.update(:Golfer)` service method: + +.`GolferService` class, `update(:Golfer)` method +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/GolferService.java[tags=cache-put] +---- + +This method simply "puts" the `Golfer` in the cache (i.e. "_Golfers_" Region) mapped to the `Golfer's name` (as a +key/value cache entry). + +The cache/Region entry `put` operation results in cache event being added to the AEQ, which will eventually trigger +the SBDG framework-provided `AsyncEventListener` with our injected `GolferRepository` to write the `Golfer's` state +to the backend database. + +The configuration of the "_Golfers_" Region (cache) with an AEQ and listener using the `GolferRepository` is defined +as follows: + +.`AsyncInlineCachingConfiguration` class +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java[tags=class] +---- + +The Spring `@Configuration` class used to enable _Async Inline Caching_ consists of 2 different AEQ configuration +arrangements and bean definitions. + +The first is a AEQ configured with a "preference" for being triggered on the *batch size*, i.e. the number of events +present in the AEQ: + +.`AsyncInlineCachingConfiguration` class +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java[tags=queue-batch-size] +---- + +The second AEQ configuration uses a "preference" for being triggered based on a *batch time interval*, i.e. after +a period of time has elapsed, such as 5 seconds. + +NOTE: The default AEQ _batch time interval_ in {apache-geode-name} is *5 milliseconds* (5 ms). However, to demonstrate +the asynchronous nature of the cache to database update, a much longer delay was used. Likewise, the default AEQ +_batch size_ is *100*. + +In both AEQ configurations and bean definitions, the _batch size_ and _batch time interval_ have been set (overriding +the {apache-geode-name} defaults) in order to show the effects of each AEQ settings independently. As you can imagine, +particularly in a highly concurrent and transactional application with frequent updates, it would be hard to determine +whether the AEQ event processing (and listener) was triggered by the _batch time interval_ or the _batch size_. And, +with a default *5 millisecond* _batch time interval_, it is hard to witness the asynchronous nature of the cache +to database updates to begin with. + +We will have more to say on the AEQ configuration below. + +The final class in the golf application is a `GolferController` class annotated with Spring's `@RestController` +annotation in order to expose our golf application functionality as an API in a REST-ful interface: + +.`GolferService` class, `update(:Golfer)` method +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/web/GolferController.java[tags=class] +---- + +The Spring Web MVC `@RestController` class exposes two REST-ful API web service endpoints returning JSON data: + +* http://localhost:8080/api/golf/tournament/cache - used to get the current state of the golfers from the cache +* http://localhost:8080/api/golf/tournament/database - used to get the current state of the golfers from the database + +Both web service endpoints are consumed by the `golf-tournament-view.html` page, which uses jQuery and AJAX to make +periodic HTTP requests to refresh the page. + +[[geode-samples-caching-inline-asynchronous-example-run]] +== Run the Example + +To run the example, there are few more configuration details we need to cover. + +While it is possible to run this example using an {apache-geode-name} client/server topology, we keep things simple +by running the example using a single Spring Boot application class, namely the +`BootGeodeAsyncInlineCachingClientApplication` along with a peer cache configuration. + +That is, in our `BootGeodeAsyncInlineCachingClientApplication` class, we also apply the +`PeerCacheApplicationConfiguration` by enabling the Spring Profile, "_peer-cache_": + +.`PeerCacheApplicationConfiguration` class +[source,java] +---- +include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java[tags=peer-cache-configuration] +---- + +It should be noted that an AEQ can only be created and registered on a Region existing on the server-side of +an {apache-geode-name} system. That is, you cannot add an AEQ to a client-side Region. Therefore, in all your +_Async Inline Caching_ Uses Cases (UC), synchronous or asynchronous, it will be the servers in an {apache-geode-name} +cluster that are responsible for _Write-Behind_ functionality to the backend data store, not a Spring Boot, +{apache-geode-name} client application. + +However, for demonstration purposes, we override SBDG's default, auto-configuration providing a `ClientCache` instance +by simply enabling the "_peer-cache_" Spring Profile, which replaces the `ClientCache` instance with a peer `Cache` +instance instead. + +Finally, when running this application, you must decide on your AEQ management strategy. For example, do you want +the AEQ listener to be triggered by *batch size* (i.e. the number of cache events) or using the *batch time interval*. +Each strategy can be enabled using a Spring Profile, either "_queue-batch-size_" or "_queue-batch-time-interval_". +This allows you to experiment with different AEQ management strategies to observe the effects. + +In total, the Spring Profiles you need to enable would appear as follows: + +.Spring Profiles to enable when running the application. +[source,txt] +---- +-Dspring.profiles.active=peer-cache,queue-batch-size,server +---- + +Of course, you can replace "_queue-batch-size_" with "_queue-batch-time-interval_". + +The final configuration of the Spring Boot application, as seen in IntelliJ IDEA is: + +image::{images-dir}/BootGeodeAsyncInlineCachingClientApplication-IntelliJ-IDEA-Run-Configuration.png[] + +To access the golf application, simply navigate to: + +http://localhost:8080/golf-tournament-view.html + +You should see a web page similar to: + +image::{images-dir}/Asynchronous-Inline-Caching-Application.png[] + +You can also run this example using the SBDG Gradle build from the command-line like so: + +.Run the example using Gradle +[source,txt] +---- +$ gradlew --no-daemon :spring-geode-sample-caching-inline-async:bootRun +---- + +This is convenient since the Spring Profiles are already configured for you. + +However, when you switch to using the "_queue-batch-time-interval_" you will see a similar effect and behavior, but +with on a slightly different schedule for the database updates, i.e. at a fixed 5 second interval. + +[[geode-samples-caching-inline-asynchronous-example-conclusion]] +== Conclusion + +_Asynchronous Inline Caching_ can be a powerful pattern of caching applied to your diverse Spring Boot application +workflows depending on the use case and requirements. + +If throughput and latency are important to create the necessary responsiveness in your application design and user +experience, and consistency (i.e. between the cache and the backend _System of Record_ (SOR), or database) is not +as important of a concern, then you might want to consider _Asynchronous Inline Caching_. + +There are many factors to consider in the configuration of the AEQ that is at the heart of an _Asynchronous Inline +Caching_ configuration, such as the appropriate *batch size* and *batch time interval*. Neither setting is exclusive +from the other, in fact. Both settings are considered when {apache-geode-name} makes a decision of when to trigger +the listener registered on the AEQ to process the events from operations on the Region to which the AEQ is attached. + +You must decide on your *batch size*, based on how many events might occur in a given period of time. If the frequency +is quite high, then you might need a smaller *batch size*, for instance. The AEQ is in-memory, therefore you must be +conscious of memory constraints on your system, especially during peak loads. Of course, the AEQ can be configured +to overflow events to disk and even persist events between restarts, but ideally you want these events to be processed +in as near realtime as possible. + +However, when the load on your application is low and events occur sporadically, you also must be mindful that the +events do not sit in the AEQ for too long. If you have *batch size* of 1000, and there are currently only 20 events +(well, any number of events less than 1000) sitting in the AEQ waiting to be processed, then the *batch time interval* +becomes important, especially so that these remaining events (less than the configured *batch size*) don't wait in the +queue indefinitely. + +Other factors to consider are whether you can conflate the events in the AEQ. This minimizes the number of events for +a single logical Object to the latest update. Additionally, do you need to overflow events to disk after the configured +maximum queue memory is reached, or should events simply be discarded? Do you need to maintain the events in the queue +between restarts (i.e. configure the AEQ to be persistent)? Do the disk writes for overflow and/or persistence need to +be synchronous? Do the events in the queue need to be ordered based on some `OrderPolicy`? Do the events need to be +filtered? How many dispatcher threads do you require? Etc. Etc. There are many important things consider in the +configuration of the AEQ when using _Asynchronous Inline Caching_ for _Write-Behind_ capabilities. + +Usually, it is safe to start with the defaults and adjust as needed, and as your measurements and tests dictate. + +We hope that you found this guide useful and that it has armed you with more knowledge to tackle difficult problems, +the type of application problems where the _Asynchronous Inline Caching_ pattern can be applied with immediate benefits. + +Good luck. + +link:../index.html#geode-samples[Back to Samples] diff --git a/spring-geode-docs/src/docs/asciidoc/guides/images/Asynchronous-Inline-Caching-Application.png b/spring-geode-docs/src/docs/asciidoc/guides/images/Asynchronous-Inline-Caching-Application.png new file mode 100644 index 00000000..9500e95c Binary files /dev/null and b/spring-geode-docs/src/docs/asciidoc/guides/images/Asynchronous-Inline-Caching-Application.png differ diff --git a/spring-geode-docs/src/docs/asciidoc/guides/images/BootGeodeAsyncInlineCachingClientApplication-IntelliJ-IDEA-Run-Configuration.png b/spring-geode-docs/src/docs/asciidoc/guides/images/BootGeodeAsyncInlineCachingClientApplication-IntelliJ-IDEA-Run-Configuration.png new file mode 100644 index 00000000..9d90f67a Binary files /dev/null and b/spring-geode-docs/src/docs/asciidoc/guides/images/BootGeodeAsyncInlineCachingClientApplication-IntelliJ-IDEA-Run-Configuration.png differ diff --git a/spring-geode-samples/caching/inline-async/spring-geode-samples-caching-inline-async.gradle b/spring-geode-samples/caching/inline-async/spring-geode-samples-caching-inline-async.gradle index c665ea59..32348dea 100644 --- a/spring-geode-samples/caching/inline-async/spring-geode-samples-caching-inline-async.gradle +++ b/spring-geode-samples/caching/inline-async/spring-geode-samples-caching-inline-async.gradle @@ -26,9 +26,10 @@ dependencies { } bootJar { - mainClass = 'example.app.caching.inline.BootGeodeAsyncInlineCachingClientApplication' + mainClass = 'example.app.caching.inline.async.client.BootGeodeAsyncInlineCachingClientApplication' } bootRun { - main = 'example.app.caching.inline.BootGeodeAsyncInlineClientCachingApplication' + main = 'example.app.caching.inline.async.client.BootGeodeAsyncInlineCachingClientApplication' + args "--spring.profiles.active=peer-cache,queue-batch-size,server" } diff --git a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java index a7625be5..a3fb5fd4 100644 --- a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java +++ b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/BootGeodeAsyncInlineCachingClientApplication.java @@ -52,14 +52,15 @@ import example.app.caching.inline.async.config.AsyncInlineCachingRegionConfigura * @see org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer * @see org.springframework.geode.config.annotation.UseMemberName * @see org.springframework.scheduling.annotation.EnableScheduling + * @see example.app.caching.inline.async.client.model.Golfer * @see example.app.caching.inline.async.client.model.GolfCourse * @see example.app.caching.inline.async.client.model.GolfTournament - * @see example.app.caching.inline.async.client.model.Golfer * @see example.app.caching.inline.async.client.service.PgaTourService * @see example.app.caching.inline.async.config.AsyncInlineCachingConfiguration * @see example.app.caching.inline.async.config.AsyncInlineCachingRegionConfiguration * @since 1.4.0 */ +// tag::class[] @SpringBootApplication @SuppressWarnings("unused") public class BootGeodeAsyncInlineCachingClientApplication { @@ -70,16 +71,18 @@ public class BootGeodeAsyncInlineCachingClientApplication { SpringApplication.run(BootGeodeAsyncInlineCachingClientApplication.class, args); } + // tag::application-configuration[] @Configuration @EnableScheduling static class GolfApplicationConfiguration { + // tag::application-runner[] @Bean ApplicationRunner runGolfTournament(PgaTourService pgaTourService) { return args -> { - GolfTournament golfTournament = GolfTournament.newGolfTournament("Masters") + GolfTournament golfTournament = GolfTournament.newGolfTournament("The Masters") .at(GolfCourseBuilder.buildAugustaNational()) .register(GolferBuilder.buildGolfers(GolferBuilder.FAVORITE_GOLFER_NAMES)) .buildPairings() @@ -89,16 +92,23 @@ public class BootGeodeAsyncInlineCachingClientApplication { }; } + // end::application-runner[] } + // end::application-configuration[] + // tag::geode-configuration[] @Configuration @UseMemberName(APPLICATION_NAME) @EnableCachingDefinedRegions(serverRegionShortcut = RegionShortcut.REPLICATE) static class GeodeConfiguration { } + // end::geode-configuration[] + // tag::peer-cache-configuration[] @PeerCacheApplication @Profile("peer-cache") @Import({ AsyncInlineCachingConfiguration.class, AsyncInlineCachingRegionConfiguration.class }) static class PeerCacheApplicationConfiguration { } + // end::peer-cache-configuration[] } +// end::class[] diff --git a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/GolferService.java b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/GolferService.java index 1cb8643f..0b6aca67 100644 --- a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/GolferService.java +++ b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/GolferService.java @@ -44,6 +44,7 @@ import example.app.caching.inline.async.client.repo.GolferRepository; * @see example.app.caching.inline.async.client.repo.GolferRepository * @since 1.4.0 */ +// tag::class[] @Service @SuppressWarnings("unused") public class GolferService { @@ -62,10 +63,12 @@ public class GolferService { this.golferRepository = golferRepository; } + // tag::cache-put[] @CachePut(cacheNames = "Golfers", key = "#golfer.name") public Golfer update(Golfer golfer) { return golfer; } + // end::cache-put[] public List getAllGolfersFromCache() { @@ -83,3 +86,4 @@ public class GolferService { return RegionUtils.isClient(region) ? region.keySetOnServer() : region.keySet(); } } +// end::class[] diff --git a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/PgaTourService.java b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/PgaTourService.java index 1486c2a2..8afe4a6d 100644 --- a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/PgaTourService.java +++ b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/service/PgaTourService.java @@ -45,6 +45,7 @@ import example.app.caching.inline.async.client.model.Golfer; * @see example.app.caching.inline.async.client.model.Golfer * @since 1.4.0 */ +// tag::class[] @Service @SuppressWarnings("unused") public class PgaTourService implements Closeable { @@ -73,15 +74,12 @@ public class PgaTourService implements Closeable { this.golfTournament = null; } - public boolean isFinished() { - - GolfTournament golfTournament = this.golfTournament; - + public boolean isFinished(@Nullable GolfTournament golfTournament) { return golfTournament == null || golfTournament.isFinished(); } - public boolean isNotFinished() { - return !isFinished(); + public boolean isNotFinished(@Nullable GolfTournament golfTournament) { + return !isFinished(golfTournament); } public PgaTourService manage(GolfTournament golfTournament) { @@ -97,17 +95,18 @@ public class PgaTourService implements Closeable { return this; } - @SuppressWarnings("unused") + // tag::play[] @Scheduled(initialDelay = 5000L, fixedDelay = 2500L) public void play() { GolfTournament golfTournament = this.golfTournament; - if (isNotFinished()) { + if (isNotFinished(golfTournament)) { playHole(golfTournament); finish(golfTournament); } } + // end::play[] private synchronized void playHole(@NonNull GolfTournament golfTournament) { @@ -171,3 +170,4 @@ public class PgaTourService implements Closeable { } } } +// end::class[] diff --git a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/web/GolferController.java b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/web/GolferController.java index e7054350..9192b6c7 100644 --- a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/web/GolferController.java +++ b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/client/web/GolferController.java @@ -40,8 +40,9 @@ import example.app.caching.inline.async.client.service.GolferService; * @see example.app.caching.inline.async.client.service.GolferService * @since 1.4.0 */ +// tag::class[] @RestController -@RequestMapping("/golf/tournament") +@RequestMapping("/api/golf/tournament") @SuppressWarnings("unused") public class GolferController { @@ -68,3 +69,4 @@ public class GolferController { return getGolferService().getAllGolfersFromDatabase(); } } +// end::class[] diff --git a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java index 725cc380..49168158 100644 --- a/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java +++ b/spring-geode-samples/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java @@ -39,12 +39,14 @@ import example.app.caching.inline.async.client.repo.GolferRepository; * @see example.app.caching.inline.async.client.repo.GolferRepository * @since 1.4.0 */ +// tag::class[] @Configuration @SuppressWarnings("unused") public class AsyncInlineCachingConfiguration { protected static final String GOLFERS_REGION_NAME = "Golfers"; + // tag::queue-batch-size[] @Bean @Profile("queue-batch-size") AsyncInlineCachingRegionConfigurer batchSizeAsyncInlineCachingConfigurer( @@ -54,10 +56,12 @@ public class AsyncInlineCachingConfiguration { return AsyncInlineCachingRegionConfigurer.create(golferRepository, GOLFERS_REGION_NAME) .withQueueBatchConflationEnabled() .withQueueBatchSize(queueBatchSize) - .withQueueBatchTimeInterval(Duration.ofMinutes(60)) + .withQueueBatchTimeInterval(Duration.ofMinutes(15)) .withQueueDispatcherThreadCount(1); } + // end::queue-batch-size[] + // tag::queue-batch-time-interval[] @Bean @Profile("queue-batch-time-interval") AsyncInlineCachingRegionConfigurer batchTimeIntervalAsyncInlineCachingConfigurer( @@ -69,4 +73,6 @@ public class AsyncInlineCachingConfiguration { .withQueueBatchSize(1000000) .withQueueDispatcherThreadCount(1); } + // end::queue-batch-time-interval[] } +// end::class[] diff --git a/spring-geode-samples/caching/inline-async/src/main/resources/static/golf-tournament-view.html b/spring-geode-samples/caching/inline-async/src/main/resources/static/golf-tournament-view.html index fa20eb92..29f5acc4 100644 --- a/spring-geode-samples/caching/inline-async/src/main/resources/static/golf-tournament-view.html +++ b/spring-geode-samples/caching/inline-async/src/main/resources/static/golf-tournament-view.html @@ -27,11 +27,11 @@ } function golfTournamentCacheStandingsRunner() { - golfTournamentStandingsRunner("http://localhost:8080/golf/tournament/cache", "#golfersInCache"); + golfTournamentStandingsRunner("http://localhost:8080/api/golf/tournament/cache", "#golfersInCache"); } function golfTournamentDatabaseStandingsRunner() { - golfTournamentStandingsRunner("http://localhost:8080/golf/tournament/database", "#golfersInDatabase"); + golfTournamentStandingsRunner("http://localhost:8080/api/golf/tournament/database", "#golfersInDatabase"); } $(document).ready(function() { diff --git a/spring-geode-samples/caching/inline-async/src/test/java/example/app/caching/inline/async/queue_batch_size/AsyncInlineCachingUsingQueueBatchSizeIntegrationTests.java b/spring-geode-samples/caching/inline-async/src/test/java/example/app/caching/inline/async/queue_batch_size/AsyncInlineCachingUsingQueueBatchSizeIntegrationTests.java index 5059c753..b0280f77 100644 --- a/spring-geode-samples/caching/inline-async/src/test/java/example/app/caching/inline/async/queue_batch_size/AsyncInlineCachingUsingQueueBatchSizeIntegrationTests.java +++ b/spring-geode-samples/caching/inline-async/src/test/java/example/app/caching/inline/async/queue_batch_size/AsyncInlineCachingUsingQueueBatchSizeIntegrationTests.java @@ -137,7 +137,7 @@ public class AsyncInlineCachingUsingQueueBatchSizeIntegrationTests extends Integ assertThat(golfersEventQueue).isNotNull(); assertThat(golfersEventQueue.getBatchSize()).isEqualTo(2); - assertThat(golfersEventQueue.getBatchTimeInterval()).isEqualTo(Duration.ofMinutes(60).toMillis()); + assertThat(golfersEventQueue.getBatchTimeInterval()).isEqualTo(Duration.ofMinutes(15).toMillis()); assertThat(golfersEventQueue.getDispatcherThreads()).isEqualTo(1); assertThat(golfersEventQueue.isParallel()).isFalse();