Products
Clients
Extensions
APIs
This guide walks you through how to implement a session state cache using VMware GemFire and Spring Boot for VMware GemFire.
Session state caching is useful for storing data associated with an HTTP session. Storing this data in a cache allows it to be retrieved quickly and persisted across log-ins. Some examples where this might be useful include:
When a user connects to a website that utilizes sessions, an HTTP session is created.
In our example the Spring Session library takes care of managing the user session. When a user connects, a unique ID for the session is generated and stored as a cookie in the user’s browser. On subsequent requests, the cookie is sent to the server, identifying the session.
The session UUID is used as a key in a data store holding information associated with the session (see examples of session data above.) The data store can be a traditional database, but this can lead to performance issues when there is a large volume of users, or user data, or both. A cache can improve performance in these cases.
To complete this guide you need:
If running on Tanzu Application Service
If running on Kubernetes
A VMware GemFire for Kubernetes Cluster.
For this example:
notes-app
notes-app-gemfire-cluster
Docker.
An image repository for the Session State Example (this example uses Docker Hub).
This example consists of a simple Spring Boot back end application and a React front end application that records user-provided notes, and associates them with the user’s session. If the user navigates away, and then returns to the site, their notes will still be available. The app also offers the ability to destroy the session - analogous to logging out of a website or closing the browser/tab.
The back end (in the src/main/java/sessionstate/ directory) handles all the session management and storage, and is the main focus of the example.
src/main/java/sessionstate/
The front end (in the frontend/ directory) is provided to illustrate how a web app can interact with the session data. The example front end is written using the React framework, but clients can use any language or framework capable of interacting with a REST endpoint.
frontend/
You can download the complete application from the VMware GemFire examples GitHub repository.
$ git clone https://github.com/gemfire/spring-for-gemfire-examples.git
The Spring Boot for VMware GemFire dependencies are available from the Broadcom Support Portal. Access to the Broadcom Maven Repository requires a one-time registration step to create an account.
After registering, you will receive a confirmation email. Follow the instruction in this email to activate your account.
Spring Boot for VMware GemFire requires users to add the GemFire repository to their projects.
Add the GemFire repository to your project:
Gradle Add the following to the repositories section of the build.gradle file:
build.gradle
repositories { mavenCentral() maven { credentials { username "$gemfireRepoUsername" password "$gemfireRepoPassword" } url = uri("https://packages.broadcom.com/artifactory/gemfire/") } }
Maven Add the following to the pom.xml file:
pom.xml
<repository> <id>gemfire-release-repo</id> <name>Broadcom GemFire Release Repository</name> <url>https://packages.broadcom.com/artifactory/gemfire/</url> </repository>
Add your Broadcom Maven Repository credentials.
Gradle
Add the following to the local (.gradle/gradle.properties) or project gradle.properties file. Replace YOUR_BROADCOM_MAVEN_REPO_EMAIL and YOUR_BROADCOM_MAVEN_REPO_ACCESS_TOKEN with your Broadcom Maven Repository credentials.
.gradle/gradle.properties
gradle.properties
YOUR_BROADCOM_MAVEN_REPO_EMAIL
YOUR_BROADCOM_MAVEN_REPO_ACCESS_TOKEN
gemfireRepoUsername=YOUR_BROADCOM_MAVEN_REPO_EMAIL gemfireRepoPassword=YOUR_BROADCOM_MAVEN_REPO_ACCESS_TOKEN
Maven Add the following to the .m2/settings.xml file. Replace MY-USERNAME@example and MY-ACCESS-TOKEN with your Broadcom Maven Repository credentials.
.m2/settings.xml
MY-USERNAME@example
MY-ACCESS-TOKEN
<settings> <servers> <server> <id>gemfire-release-repo</id> <username>[email protected]</username> <password>MY-ACCESS-TOKEN</password> </server> </servers> </settings>
Add the Spring for VMware GemFire dependencies to your project:
dependencies { implementation "com.vmware.gemfire:spring-boot-3.1-gemfire-10.0:1.0.0" implementation "com.vmware.gemfire:spring-boot-session3.1-gemfire-10.0:1.0.0" ... }
Maven
<dependencies> <dependency> <groupId>com.vmware.gemfire</groupId> <artifactId>spring-boot-3.1-gemfire-10.0</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.vmware.gemfire</groupId> <artifactId>spring-boot-session-3.1-gemfire-10.0</artifactId> <version>1.0.0</version> </dependency> ... <dependencies>
artifactId
The Spring Boot application will need the following annotations
@SpringBootApplication @EnableClusterAware public class SessionStateApplication { public static void main(String[] args) { SpringApplication.run(SessionStateApplication.class, args); } }
@EnableClusterConfiguration
The example Spring Boot application uses a @RestController that allows the front end application to interact with a REST API to read, update, and destroy session data (notes in this example application).
@RestController
@RestController public class SessionController { @GetMapping("/getSessionNotes") public List<String> getSessionNotes(HttpServletRequest request) { List<String> notes = (List<String>) request.getSession().getAttribute("NOTES"); return notes; } @PostMapping("/addSessionNote") public void addSessionNote(@RequestBody String note, HttpServletRequest request) { List<String> notes = (List<String>) request.getSession().getAttribute("NOTES"); if (notes == null) { notes = new ArrayList<>(); } notes.add(note); request.getSession().setAttribute("NOTES", notes); } @PostMapping("/invalidateSession") public void invalidateSession(HttpServletRequest request) { request.getSession(false).invalidate(); } }
The front end web application accesses the back end REST API using standard GET and POST HTTP methods. See frontend/src/sessionService.js
frontend/src/sessionService.js
import axios from 'axios'; const instance = axios.create(); const addNote = async (note) => { await instance.post('/addSessionNote', note,{ headers: { 'Content-Type': 'text/plain' } }); }; const getNotes = async () => { const response = await instance.get('/getSessionNotes'); return response.data; }; const destroySession = async () => { await instance.post('/invalidateSession'); };
When unit testing during development, to verify caching, @Autowire a CacheManager and use it to confirm that session data is properly stored in the cache.
@Autowire
The @DirtiesContext is used to destroy the test region and its data after the test is run. This prevents interference with other tests.
@RunWith(SpringRunner.class) @SpringBootTest(classes = SessionStateApplication.class) @AutoConfigureMockMvc public class SessionControllerTest { @Autowired MockMvc mockMvc; @Autowired CacheManager cacheManager; static String NOTE1 = "Nothing More Than Memories"; ... @Test @DirtiesContext public void addSessionNote_should_addNoteToSessionInCache() throws Exception { MvcResult mvcResult = mockMvc.perform(post("/addSessionNote") .content(NOTE1)) .andExpect(status().isOk()) .andReturn(); String encodedSessionUUID = mvcResult.getResponse().getCookie("SESSION").getValue(); List<String> notesList = getNotesForSessionInCache(encodedSessionUUID); assertEquals(NOTE1, (notesList.get(0))); } ...
Navigate to the root of the project in a command line and run the Spring Boot run command.
Follow the instructions in the Getting Started Locally guide to start a small GemFire cluster on your local machine.
Once you have a cluster running, in a new terminal, navigate to the root of the project and build the app.
./gradlew clean build
mvn clean package
One the application has finished building, start the Spring Boot application.
./gradlew bootRun
mvn spring-boot:run
When the app is running, open a browser and go to http://localhost:8080.
The “Enter your note:” form can be used to enter notes.
The “DESTROY SESSION” button can be used to clear the session data and delete the notes.
In the project root directory, open the manifest.yml file and replace <SERVICE-INSTANCE-NAME> with the name of your service instance.
manifest.yml
<SERVICE-INSTANCE-NAME>
Once the VMware GemFire service instance is running (you can check the status by running the cf services command), push your app to TAS with cf push.
cf services
cf push
After the app has successfully been pushed, in the output find the route. Then open a browser and copy and paste the route into the browser.
route
To deploy the Session State Example application on Kubernetes make sure you have created a VMware GemFire cluster on Kubernetes using the namespace and GemFire cluster names below.
application.properties
Navigate to the application directory.
Open the application.properties.
Uncomment the two listed properties.
Replace the value for spring.data.gemfire.pool.locators: with your VMware GemFire cluster information, for each locator (in this example we only have one locator). The information will follow the form:
spring.data.gemfire.pool.locators:
[GEMFIRE-CLUSTER-NAME]-locator-[LOCATOR-NUMBER].[GEMFIRE-CLUSTER-NAME]-locator.[NAMESPACE-NAME][10334]
For our example the value looks like this:
notes-app-gemfire-cluster-locator-0.notes-app-gemfire-cluster-locator.notes-app[10334]
Replace the value for spring.data.gemfire.management.http.host: with your VMware GemFire cluster information. This will allow Spring Boot for VMware GemFire to push your initial cluster configuration to your VMware GemFire cluster. The information follows a similar form as above:
spring.data.gemfire.management.http.host:
[GEMFIRE-CLUSTER-NAME]-locator-[LOCATOR-NUMBER].[GEMFIRE-CLUSTER-NAME]-locator.[NAMESPACE-NAME]
notes-app-gemfire-cluster-locator-0.notes-app-gemfire-cluster-locator.notes-app
Starting with Spring Boot 2.3, you can now customize and create an OCI image using Spring Boot. In this example we’re using the Gradle - packaging OCI images option. If you are using Maven check out the instructions found here.
./gradlew clean build -x test
bootBuildImage
docker.io/[YOUR DOCKER USERNAME]/notes-app:0.0.1-SNAPSHOT
./gradlew bootBuildImage
For this example, we’re using Docker Hub as our registry. This will create a repository on Docker Hub called notes-app and push the image we created into that repository.
In a terminal
Login to your Docker account
Run the docker push [IMAGE NAME HERE]. For this example it should be similar to this
docker push [IMAGE NAME HERE]
docker push docker.io/[YOUR DOCKER USERNAME]/notes-app:0.0.1-SNAPSHOT
Create a Kubernetes deployment for your Notes app. This will create a deployment, replicaset, and pod using the image we created above.
kubectl -n notes-app create deployment notes-app-deployment --image=docker.io/[YOUR DOCKER USERNAME]/notes-app:0.0.1-SNAPSHOT
If successful you should see deployment.apps/notes-app-deployment created
deployment.apps/notes-app-deployment created
In order to access the Notes app from a browser, we need to expose the deployment.
Notes
kubectl -n notes-app expose deployment/notes-app-deployment --type="LoadBalancer" --port=80 --target-port=8080
If you’re trying this locally with MiniKube, you will need to replace LoadBalancer with NodePort.
LoadBalancer
NodePort
Once the Load Balancer has been created, you can now access the Notes app using the External IP on the LoadBalancer service.
External IP
kubectl -n notes-app get services
This should output something similar to (your locator and server names may be different).
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE notes-app-cluster-locator ClusterIP None <none> 10334/TCP,4321/TCP 26h notes-app-cluster-server ClusterIP None <none> 40404/TCP,4321/TCP 26h notes-app-deployment LoadBalancer 10.0.113.16 52.170.169.174 80:30109/TCP 26h
In your browser, go to the EXTERNAL-IP of the notes-app-deployment and you should see a working Notes app.
EXTERNAL-IP
notes-app-deployment
Open a terminal
Start gfsh for kubernetes
kubectl -n notes-app exec -it notes-app-gemfire-cluster-locator-0 -- gfsh
Once you see that GFSH has started, connect to your cluster with the connect command
GFSH
connect
connect --locator=notes-app-gemfire-cluster-locator-0.notes-app-gemfire-cluster-locator.notes-app[10334]
Once connected run the list regions command
list regions
You should see something similar to
List of regions ------------------ ClusteredSpringSessions
This shows that the Spring Boot for VMware GemFire app has connected to the VMware GemFire cluster and pushed the initial Session configuration, including a region called ClusteredSpringSessions), to the cluster.
ClusteredSpringSessions
If the ClusteredSpringSessions region IS NOT listed, the first item to check is the application.properties file. Confirm that the spring data property values are set correctly. If you need to update them, make sure you also increment your version number of your image in the build.gradle file. This will force Kubernetes to pull the new image (as opposed to using a cached version of the image).
Congratulations! You have now deployed a Spring Boot for VMware GemFire app that implements Session State Caching