Create base Spring Geode Sample Example application on Async Inline Caching.

This commit is contained in:
John Blum
2020-12-01 16:58:30 -08:00
parent be01322025
commit ad77707d6f
15 changed files with 1038 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
plugins {
id "io.freefair.lombok" version "5.3.0"
}
apply plugin: 'io.spring.convention.spring-sample-boot'
description = "Spring Geode Sample demonstrating Spring's Cache Abstraction using Apache Geode as the caching provider for Asynchronous Inline Caching."
dependencies {
compile project(":spring-geode-starter")
compile "org.projectlombok:lombok"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-web"
runtime "org.hsqldb:hsqldb"
testCompile project(":spring-geode-starter-test")
testCompile "org.awaitility:awaitility:$awaitilityVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
}
bootJar {
mainClassName = 'example.app.caching.inline.BootGeodeAsyncInlineCachingClientApplication'
}
bootRun {
main = 'example.app.caching.inline.BootGeodeAsyncInlineClientCachingApplication'
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client;
import org.apache.geode.cache.RegionShortcut;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
import org.springframework.geode.config.annotation.UseMemberName;
import org.springframework.scheduling.annotation.EnableScheduling;
import example.app.caching.inline.async.client.model.GolfTournament;
import example.app.caching.inline.async.client.model.support.GolfCourseBuilder;
import example.app.caching.inline.async.client.model.support.GolferBuilder;
import example.app.caching.inline.async.client.service.GolfTournamentService;
import example.app.caching.inline.async.config.AsyncInlineCachingConfiguration;
/**
* {@link SpringBootApplication} class simulating a golf tournament management application.
*
* @author John Blum
* @see org.springframework.boot.ApplicationRunner
* @see org.springframework.boot.SpringApplication
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Import
* @see org.springframework.context.annotation.Profile
* @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
* @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.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.GolfTournamentService
* @see example.app.caching.inline.async.config.AsyncInlineCachingConfiguration
* @since 1.4.0
*/
@SpringBootApplication
@SuppressWarnings("unused")
public class BootGeodeAsyncInlineCachingClientApplication {
private static final String APPLICATION_NAME = "GolfClientApplication";
public static void main(String[] args) {
SpringApplication.run(BootGeodeAsyncInlineCachingClientApplication.class, args);
}
@Configuration
@EnableScheduling
static class GolfApplicationConfiguration {
@Bean
ApplicationRunner runGolfTournament(GolfTournamentService golfTournamentService) {
return args -> {
GolfTournament golfTournament = GolfTournament.newGolfTournament("Masters")
.at(GolfCourseBuilder.buildAugustaNational())
.register(GolferBuilder.buildGolfers(GolferBuilder.FAVORITE_GOLFER_NAMES))
.buildPairings()
.play();
golfTournamentService.manage(golfTournament);
};
}
}
@Configuration
@UseMemberName(APPLICATION_NAME)
@EnableCachingDefinedRegions(serverRegionShortcut = RegionShortcut.LOCAL)
static class GeodeConfiguration { }
@PeerCacheApplication
@Profile("peer-cache")
@Import(AsyncInlineCachingConfiguration.class)
static class PeerCacheApplicationConfiguration { }
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.util.Assert;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
/**
* Abstract Data Type (ADT) modeling a golf course.
*
* @author John Blum
* @since 1.4.0
*/
@Getter
@ToString(of = "name")
@EqualsAndHashCode(of = "name")
@RequiredArgsConstructor(staticName = "newGolfCourse")
@SuppressWarnings("unused")
public class GolfCourse {
public static final int STANDARD_PAR_FOR_COURSE = 72;
public static final Set<Integer> VALID_PARS_FOR_HOLE =
Collections.unmodifiableSet(CollectionUtils.asSet(3, 4, 5));
@NonNull
private final String name;
private final List<Integer> parForHole = new ArrayList<>(18);
public int getPar(int hole) {
assertValidHoleNumber(hole);
return this.parForHole.get(indexForHole(hole));
}
public int getParForCourse() {
return this.parForHole.stream()
.reduce(Integer::sum)
.orElse(STANDARD_PAR_FOR_COURSE);
}
public GolfCourse withHole(int holeNumber, int par) {
assertValidHoleNumber(holeNumber);
assertValidParForHole(par, holeNumber);
this.parForHole.set(indexForHole(holeNumber), par);
return this;
}
private void assertValidHoleNumber(int hole) {
Assert.isTrue(isValidHoleNumber(hole),
() -> String.format("Hole number [%d] must be 1 through 18", hole));
}
private void assertValidParForHole(int par, int hole) {
Assert.isTrue(isValidPar(par),
() -> String.format("Par [%1$d] for hole [%2$d] must be in [%3$s]", par, hole, VALID_PARS_FOR_HOLE));
}
private int indexForHole(int hole) {
return hole - 1;
}
public boolean isValidHoleNumber(int hole) {
return hole >= 1 && hole <= 18;
}
public boolean isValidPar(int par) {
return VALID_PARS_FOR_HOLE.contains(par);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.StreamSupport;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
/**
* Abstract Data Type (ADT) modeling a golf tournament.
*
* @author John Blum
* @since 1.4.0
*/
@Getter
@ToString
@RequiredArgsConstructor(staticName = "newGolfTournament")
@SuppressWarnings("unused")
public class GolfTournament implements Iterable<GolfTournament.Pairing> {
@NonNull
private final String name;
private GolfCourse golfCourse;
private final List<Pairing> pairings = new ArrayList<>();
private final Set<Golfer> players = new HashSet<>();
@Override
public Iterator<GolfTournament.Pairing> iterator() {
return Collections.unmodifiableList(this.pairings).iterator();
}
public boolean isFinished() {
Set<Pairing> finishedPairings = new HashSet<>(this.pairings.size());
for (Pairing pairing : this) {
if (pairing.getHole() < 18) {
return false;
}
else {
finishedPairings.add(pairing);
}
}
this.pairings.removeAll(finishedPairings);
return this.pairings.isEmpty();
}
public GolfTournament at(GolfCourse golfCourse) {
Assert.notNull(golfCourse, "Golf Course must not be null");
this.golfCourse = golfCourse;
return this;
}
public GolfTournament buildPairings() {
Assert.notEmpty(this.players,
() -> String.format("No players are registered for this golf tournament [%s]", getName()));
Assert.isTrue(this.players.size() % 2 == 0,
() -> String.format("An even number of players must register to play this golf tournament [%s]; currently at [%d]",
getName(), this.players.size()));
List<Golfer> playersToPair = new ArrayList<>(this.players);
Collections.shuffle(playersToPair);
for (int index = 0, size = playersToPair.size(); index < size; index += 2) {
this.pairings.add(Pairing.of(playersToPair.get(index), playersToPair.get(index + 1)));
}
return this;
}
public GolfTournament play() {
Assert.state(this.golfCourse != null, "No golf course was declared");
Assert.state(!this.players.isEmpty(), "Golfers must register to play before the golf tournament is played");
Assert.state(!this.pairings.isEmpty(), "Pairings must be formed before the golf tournament is played");
Assert.state(!isFinished(), () -> String.format("Golf tournament [%s] has already been played", getName()));
return this;
}
public GolfTournament register(Golfer... players) {
return register(Arrays.asList(ArrayUtils.nullSafeArray(players, Golfer.class)));
}
public GolfTournament register(Iterable<Golfer> players) {
StreamSupport.stream(CollectionUtils.nullSafeIterable(players).spliterator(), false)
.filter(Objects::nonNull)
.forEach(this.players::add);
return this;
}
@Getter
@ToString
@RequiredArgsConstructor(staticName = "of")
public static class Pairing {
@NonNull
private final Golfer playerOne;
@NonNull
private final Golfer playerTwo;
public int getHole() {
return getPlayerOne().getHole();
}
public void setHole(int hole) {
this.playerOne.setHole(hole);
this.playerTwo.setHole(hole);
}
public int playNextHole() {
return getHole() + 1;
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.model;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* Abstract Data Type (ADT) modeling a person who plays golf.
*
* In addition to the {@link Golfer Golfer's} {@link String name}, this class tracks the current {@link Integer hole}
* and {@link Integer score} of the {@link Golfer} when s/he competes/plays in a golf tournament.
*
* @author John Blum
* @see javax.persistence.Entity
* @see javax.persistence.Table
* @see org.springframework.data.annotation.Id
* @see org.springframework.data.gemfire.mapping.annotation.Region
* @since 1.4.0
*/
@Entity
@Getter
@ToString(of = "name")
@Table(name = "golfers")
@Region(name = "Golfers")
@EqualsAndHashCode(of = "name")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RequiredArgsConstructor(staticName = "newGolfer")
@SuppressWarnings("unused")
public class Golfer implements Comparable<Golfer> {
@javax.persistence.Id @Id @NonNull
private String name;
@Setter
private Integer hole = 0;
@Setter
private Integer score = 0;
@Override
public int compareTo(Golfer other) {
return this.getName().compareTo(other.getName());
}
private boolean isValidHole(int hole) {
return hole >= 1 && hole <= 18;
}
public Golfer on(int hole) {
Assert.isTrue(isValidHole(hole), () -> String.format("Hole [%d] must be 1 through 18", hole));
this.hole = hole;
return this;
}
public Golfer shot(int score) {
this.score = score;
return this;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.model.support;
import example.app.caching.inline.async.client.model.GolfCourse;
/**
* A {@literal Builder} class used to build {@link GolfCourse golf courses}.
*
* @author John Blum
* @see example.app.caching.inline.async.client.model.GolfCourse
* @since 1.4.0
*/
public abstract class GolfCourseBuilder {
/**
* Builds the {@literal Augusta National} {@link GolfCourse}, home of the {@literal Masters} major golf tournament.
*
* @return a new instance of {@link GolfCourse} modeling {@literal Augusta National} in Augusta, GA; USA.
* @see example.app.caching.inline.async.client.model.GolfCourse
*/
public static GolfCourse buildAugustaNational() {
return GolfCourse.newGolfCourse("Augusta National")
.withHole(1, 4)
.withHole(2, 5)
.withHole(3, 4)
.withHole(4, 3)
.withHole(5, 4)
.withHole(6, 3)
.withHole(7, 4)
.withHole(8, 5)
.withHole(9, 4)
.withHole(10, 4)
.withHole(11, 4)
.withHole(12, 3)
.withHole(13, 5)
.withHole(14, 4)
.withHole(15, 5)
.withHole(16, 3)
.withHole(17, 4)
.withHole(18, 4);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.model.support;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.util.StringUtils;
import example.app.caching.inline.async.client.model.Golfer;
/**
* A {@literal Builder} class used to build {@link Golfer Golfers}.
*
* @author John Blum
* @see example.app.caching.inline.async.client.model.Golfer
* @since 1.4.0
*/
public abstract class GolferBuilder {
public static final String[] FAVORITE_GOLFER_NAMES = {
"Arnold Palmer",
"Ben Hogan",
"Bobby Jones",
"Tiger Woods",
"Rory McIlroy",
"Dustin Johnson",
"Jason Day",
"Justin Thomas",
"John Rahm",
"Jordan Spieth",
"Phil Michelson",
"Ricky Fowler"
};
public static Set<Golfer> buildGolfers(String... names) {
return Arrays.stream(ArrayUtils.nullSafeArray(names, String.class))
.filter(StringUtils::hasText)
.map(Golfer::newGolfer)
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import example.app.caching.inline.async.client.model.Golfer;
/**
* Spring Data {@link JpaRepository} and Data Access Object (DAO) used to perform basic CRUD and simple SQL query
* data access operations on {@link Golfer Golfers} stored in an RDBMS (database) with JPA.
*
* @author John Blum
* @see org.springframework.data.jpa.repository.JpaRepository
* @see example.app.caching.inline.async.client.model.Golfer
* @since 1.4.0
*/
public interface GolferRepository extends JpaRepository<Golfer, String> {
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.service;
import java.io.Closeable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import example.app.caching.inline.async.client.model.GolfCourse;
import example.app.caching.inline.async.client.model.GolfTournament;
import example.app.caching.inline.async.client.model.Golfer;
/**
* Spring {@link Service} class used to manage golf tournaments.
*
* @author John Blum
* @see java.io.Closeable
* @see org.springframework.scheduling.annotation.Scheduled
* @see org.springframework.stereotype.Service
* @see example.app.caching.inline.async.client.model.GolfTournament
* @see example.app.caching.inline.async.client.model.Golfer
* @since 1.4.0
*/
@Service
@SuppressWarnings("unused")
public class GolfTournamentService implements Closeable {
protected static final int SCORE_DELTA_BOUND = 2;
private final GolferService golferService;
private volatile GolfTournament golfTournament;
private final Random random = new Random(System.currentTimeMillis());
public GolfTournamentService(GolferService golferService) {
Assert.notNull(golferService, "GolferService must not be null");
this.golferService = golferService;
}
public Optional<GolfTournament> getGolfTournament() {
return Optional.ofNullable(this.golfTournament);
}
@Override
public void close() {
this.golfTournament = null;
}
public GolfTournamentService manage(GolfTournament golfTournament) {
GolfTournament currentGolfTournament = this.golfTournament;
Assert.state(currentGolfTournament == null,
() -> String.format("Can only manage 1 golf tournament at a time; currently managing [%s]",
currentGolfTournament));
this.golfTournament = golfTournament;
return this;
}
@SuppressWarnings("unused")
@Scheduled(fixedRate = 2500L)
public void play() {
GolfTournament golfTournament = this.golfTournament;
if (golfTournament != null) {
playHole(golfTournament);
finish(golfTournament);
}
}
private void playHole(@NonNull GolfTournament golfTournament) {
GolfCourse golfCourse = golfTournament.getGolfCourse();
Set<Integer> occupiedHoles = new HashSet<>();
for (GolfTournament.Pairing pairing : golfTournament) {
int hole = pairing.playNextHole();
if (!occupiedHoles.contains(hole)) {
if (golfCourse.isValidHoleNumber(hole)) {
occupiedHoles.add(hole);
pairing.setHole(hole);
updateScore(this::calculateRunningScore, pairing.getPlayerOne());
updateScore(this::calculateRunningScore, pairing.getPlayerTwo());
}
}
}
}
private Golfer updateScore(@NonNull Function<Integer, Integer> scoreFunction, @NonNull Golfer player) {
player.setScore(scoreFunction.apply(player.getScore()));
this.golferService.update(player);
return player;
}
private int calculateFinalScore(@Nullable Integer scoreRelativeToPar) {
int finalScore = scoreRelativeToPar != null ? scoreRelativeToPar : 0;
int parForCourse = getGolfTournament()
.map(GolfTournament::getGolfCourse)
.map(GolfCourse::getParForCourse)
.orElse(GolfCourse.STANDARD_PAR_FOR_COURSE);
return parForCourse + finalScore;
}
private int calculateRunningScore(@Nullable Integer currentScore) {
int runningScore = currentScore != null ? currentScore : 0;
int scoreDelta = this.random.nextInt(SCORE_DELTA_BOUND);
scoreDelta *= this.random.nextBoolean() ? -1 : 1;
return runningScore + scoreDelta;
}
private void finish(@NonNull GolfTournament golfTournament) {
if (golfTournament.isFinished()) {
GolfCourse golfCourse = golfTournament.getGolfCourse();
for (GolfTournament.Pairing pairing : golfTournament) {
updateScore(this::calculateFinalScore, pairing.getPlayerOne());
updateScore(this::calculateFinalScore, pairing.getPlayerTwo());
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.client.service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.geode.cache.Region;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CachePut;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import example.app.caching.inline.async.client.model.Golfer;
import example.app.caching.inline.async.client.repo.GolferRepository;
/**
* Spring {@link Service} class used to manage {@link Golfer Golfers}.
*
* @author John Blum
* @see org.springframework.stereotype.Service
* @see org.springframework.data.gemfire.GemfireTemplate
* @see example.app.caching.inline.async.client.model.Golfer
* @see example.app.caching.inline.async.client.repo.GolferRepository
* @since 1.4.0
*/
@Service
@SuppressWarnings("unused")
public class GolferService {
private final GemfireTemplate golfersTemplate;
private final GolferRepository golferRepository;
public GolferService(@Qualifier("golfersTemplate") GemfireTemplate golfersTemplate,
GolferRepository golferRepository) {
Assert.notNull(golfersTemplate, "GolfersTemplate must not be null");
Assert.notNull(golferRepository, "GolferRepository must not be null");
this.golfersTemplate = golfersTemplate;
this.golferRepository = golferRepository;
}
@CachePut(cacheNames = "Golfers", key = "#golfer.name")
public Golfer update(Golfer golfer) {
return golfer;
}
public List<Golfer> getAllGolfersFromCache() {
Map<String, Golfer> golferMap =
nullSafeMap(this.golfersTemplate.getAll(resolveKeys(this.golfersTemplate.getRegion())));
return sort(new ArrayList<>(golferMap.values()));
}
public List<Golfer> getAllGolfersFromDatabase() {
return sort(this.golferRepository.findAll());
}
private <KEY, VALUE> Map<KEY, VALUE> nullSafeMap(Map<KEY, VALUE> map) {
return map != null ? map : Collections.emptyMap();
}
private Set<String> resolveKeys(Region<String, ?> region) {
return RegionUtils.isClient(region) ? region.keySetOnServer() : region.keySet();
}
private <T extends Comparable<T>> List<T> sort(List<T> list) {
Collections.sort(list);
return list;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.config;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer;
import example.app.caching.inline.async.client.model.Golfer;
import example.app.caching.inline.async.client.repo.GolferRepository;
/**
* Spring {@link Configuration} class used to configure {@literal Async Inline Caching}.
*
* @author John Blum
* @see java.time.Duration
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Profile
* @see org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer
* @see example.app.caching.inline.async.client.model.Golfer
* @see example.app.caching.inline.async.client.repo.GolferRepository
* @since 1.4.0
*/
@Configuration
@SuppressWarnings("unused")
public class AsyncInlineCachingConfiguration {
private static final String GOLFERS_REGION_NAME = "Golfers";
@Bean
@Profile("queue-batch-size")
AsyncInlineCachingRegionConfigurer<Golfer, String> batchSizeAsyncInlineCachingConfigurer(
@Value("${spring.geode.sample.async-inline-caching.queue.batch-size:4}") int queueBatchSize,
GolferRepository golferRepository) {
return AsyncInlineCachingRegionConfigurer.create(golferRepository, GOLFERS_REGION_NAME)
.withQueueBatchSize(queueBatchSize)
.withQueueBatchTimeInterval(Duration.ofMinutes(60))
.withQueueDispatcherThreadCount(1);
}
@Bean
@Profile("queue-batch-time-interval")
AsyncInlineCachingRegionConfigurer<Golfer, String> batchTimeIntervalAsyncInlineCachingConfigurer(
@Value("${spring.geode.sample.async-inline-caching.queue.batch-time-interval-ms:5000}") int queueBatchTimeIntervalMilliseconds,
GolferRepository golferRepository) {
return AsyncInlineCachingRegionConfigurer.create(golferRepository, GOLFERS_REGION_NAME)
.withQueueBatchTimeInterval(Duration.ofMillis(queueBatchTimeIntervalMilliseconds))
.withQueueBatchSize(1000000)
.withQueueDispatcherThreadCount(1);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package example.app.caching.inline.async.server;
import org.apache.geode.cache.RegionShortcut;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import example.app.caching.inline.async.client.model.Golfer;
import example.app.caching.inline.async.config.AsyncInlineCachingConfiguration;
/**
* {@link SpringBootApplication} class implementing the server-side of the golf tournament management application.
*
* @author John Blum
* @see java.time.Duration
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.boot.builder.SpringApplicationBuilder
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Import
* @see org.springframework.context.annotation.Profile
* @see org.springframework.data.gemfire.config.annotation.CacheServerApplication
* @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions
* @see example.app.caching.inline.async.config.AsyncInlineCachingConfiguration
* @see example.app.caching.inline.async.client.model.Golfer
* @since 1.4.0
*/
@SpringBootApplication
@Profile("server")
public class BootGeodeAsyncInlineCachingServerApplication {
private static final String APPLICATION_NAME = "GolfServerApplication";
public static void main(String[] args) {
new SpringApplicationBuilder(BootGeodeAsyncInlineCachingServerApplication.class)
.web(WebApplicationType.NONE)
.build()
.run(args);
}
@Configuration
@CacheServerApplication(name = APPLICATION_NAME)
@EnableEntityDefinedRegions(basePackageClasses = Golfer.class, serverRegionShortcut = RegionShortcut.LOCAL)
@Import(AsyncInlineCachingConfiguration.class)
@SuppressWarnings("unused")
static class GeodeConfiguration { }
}

View File

@@ -0,0 +1,3 @@
# Spring Boot application.properties configuration for the golf client application
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

View File

@@ -0,0 +1,5 @@
# Spring Boot application.properties configuration for the golfer server application.
# RBMS (Database) configuration properties.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

View File

@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS golfers (
name VARCHAR(256) PRIMARY KEY,
hole NUMERIC(10),
score NUMERIC(10)
);