Add user guide for the 'boot-gemfire-with-scoped-proxies' sample.

Resolves Issue #3.
This commit is contained in:
John Blum
2017-11-06 20:46:12 -08:00
parent 64245a24dc
commit f136c1edeb

View File

@@ -0,0 +1,349 @@
= Spring Session - HttpSession with Apache Geode Client/Server using Spring Boot
John Blum
:toc:
This guide describes how to build a _Spring Boot_ application configured with _Spring Session_ that transparently
delegates to Apache Geode for managing a Web application's `javax.servlet.http.HttpSession` in a clustered/replicated,
highly available and optionally, durable fashion.
In addition, this samples explores the effects of using _Spring Session_ and Apache Geode (or Pivotal GemFire) to
transparently manage the (HTTP) Session when the _Spring Boot_, Web application also declares both Session and Request
scoped bean definitions used by the application when processing requests.
This sample originated from a https://stackoverflow.com/questions/45674137/can-session-scope-beans-be-used-with-spring-session-and-gemfire[_StackOverflow_ post],
which posed the following question...
> _Can session scope beans be used with Spring Session and GemFire?_
The poster when on to state...
> If using Spring Session for "session" scoped beans Spring creates an extra HttpSession for this bean,
is this an existing issue? What is the solution for this?
Well, in a nutshell, the answer to the first question is most definitely, *yes*. And, the second statement/question
is not correct/valid, as explained in the answer.
This sample uses Apache Geode's client/server topology with a pair of _Spring Boot_ applications, one to configure
and run a Geode Server, and another to configure and run a GemFire-based cache client, Spring MVC Web application
making use of the `HttpSession`.
NOTE: The completed guide can be found in the section,
<<spring-session-sample-boot-geode-with-scoped-proxies,Spring Boot Sample Web Application with Apache Geode-managed HttpSessions and Request and Session Scoped Proxy Beans>>,
below.
== Updating Dependencies
Before using _Spring Session_, you must ensure that the required dependencies are included.
If you are using _Maven_, include the following `dependencies` in your `pom.xml`:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-geode</artifactId>
<version>${spring-session-data-geode-version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
----
NOTE: if using Pivotal GemFire, you may substitute the `spring-session-data-gemfire` artifact
for `spring-session-data-geode`.
ifeval::["{version-snapshot}" == "true"]
Since we are using a SNAPSHOT version, we need to add the _Spring_ Snapshot Maven Repository.
If you are using _Maven_, include the following `repository` declaration in your `pom.xml`:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
----
endif::[]
ifeval::["{version-milestone}" == "true"]
Since we are using a Milestone version, we need to add the _Spring_ Milestone Maven Repository.
If you are using _Maven_, include the following `repository` declaration in your `pom.xml`:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
----
endif::[]
// tag::config[]
[[httpsession-spring-java-configuration-gemfire-boot]]
== Spring Boot Configuration
After adding the required dependencies and repository declarations, we can create the _Spring_ configuration
for both our Apache Geode client and server using _Spring Boot_. The _Spring_ configuration is responsible for
creating a `Servlet Filter` that replaces the `HttpSession` with an implementation backed by _Spring Session_
and Apace Geode.
=== Spring Boot, Apache Geode Cache Server
We start with a _Spring Boot_ application to configure and bootstrap the Apache Geode server process...
[source,java]
----
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java[tags=class]
----
<1> First, we annotate the Apache Geode server configuration class with `@SpringBootApplication` to declare this as a
_Spring Boot_ application allowing us to leverage all of _Spring Boot's_ features (e.g. _auto-configuration_).
<2> Next, we declare _Spring Data Geode's_ `@CacheServerApplication`, which creates a peer, cache server that allows
our cache client to connect.
<3> (Optional) Then, we declare the `@EnableGemFireHttpSession` annotation to create the necessary server-side `Region`
(by default, "_ClusteredSpringSessions_") used to store the `HttpSessions` state. This step is optional since
the `Region` used to store `Session` state could be manually created. Using `@EnableGemFireHttpSession` is easy
and convenient, and ensure that our client and server-side Region match by name, which is required by GemFire/Geode.
<4> Additionally, and optionally, we also enable Apache Geode's embedded Management service, which allows JMX clients
(e.g. Apache Geode's _Gfsh_ shell tool) to connect to the server and manage the server/cluster.
<5> Finally, we adjust the port that the `CacheServer` will use to listen for cache clients by declaring
a `CacheServerConfigurer` bean that modified the port using SDG's `CacheServerFactoryBean` with property placeholders.
Defaults to *40404* if not adjusted.
The sample also makes use of _Spring's_ `PropertySourcesPlaceholderConfigurer` in order to externalize
the sample application's configuration using a properties file or with JVM System properties.
=== Spring Boot, Apache Geode Cache Client Web application
Now, we create a _Spring Boot_ Web application exposing our Web service with _Spring_ MVC, running as an Apache Geode
cache client connected to our _Spring Boot_, Apache Geode server. The Web application will use _Spring Session_
backed by Apache Geode to manage (HTTP) Session state in a clustered (i.e. distributed) and replicated,
as well as highly available manner.
[source,java]
----
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java[tags=class]
----
<1> Like the server, we declare our Web application to be a _Spring Boot_ application
by annotating our application class with `@SpringBootApplication`.
<2> `@Controller` is a _Spring_ Web MVC annotation enabling our MVC handler mapping methods (i.e. methods annotated
with `@RequestMapping`) to process client HTTP requests (e.g. <6>)
<3> We also declare our Web application to be a Geode cache client by annotating our application class with
`@ClientCacheApplication`. Additionally, we adjust a few basic, "DEFAULT" Geode client `Pool` settings.
<4> Next, we declare that the Web application will use _Spring Session_ backed by Apache Geode to manage the (HTTP)
`Session's` state by annotating the nested `ClientCacheConfiguration` class with `@EnableGemFireHttpSession`.
This will create the necessary client-side `PROXY` `Region` (by default, "ClusteredSpringSessions`) corresponding to
the same server `Region` by name. All Session state will be sent from the client to the server through `Region`
data access operations. The client-side `Region` uses the "DEFAULT" `Pool` (of connections) to communicate
with the server.
<5> Then, we adjust the port used by the cache client `Pool` to connect to the `CacheServer` using a SDG
`ClientCacheConfigurer`.
<6> We adjust the _Spring_ Web MVC configuration to set the home page, and...
<7> Finally, we declare the `/counts` HTTP request handler method to keep track of the number of instances created by
the Sprig container of both Request and Session scoped proxy beans, of types `RequestScopedProxyBean`
and `SessionScopedProxyBean`, respectively, every time a request is processed by this Web service endpoint.
TIP: In typical Geode production deployments, where the cluster includes potentially hundreds or thousands of servers
(a.k.a. data nodes), it is more common for clients to connect to 1 or more Geode Locators running in the cluster.
A Locator passes meta-data to clients about the servers available in the cluster, the individual server load
and which servers have the client's data of interest, which is particularly important for direct, single-hop data access
and latency-sensitive operations. See more details about the
http://geode.apache.org/docs/guide/12/topologies_and_comm/cs_configuration/standard_client_server_deployment.html[Client/Server Deployment]
in the Apache Geode User Guide.
NOTE: For more information on configuring _Spring Data Geode, refer to the
http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[Reference Guide].
==== Enabling GemFire HttpSession Management
`@EnableGemFireHttpSession` enables a developer to configure certain aspects of both _Spring Session_ and Apache Geode
out-of-the-box using the following attributes:
* `clientRegionShortcut` - specifies the Apache Geode http://geode.apache.org/docs/guide/12/developing/region_options/region_types.html[data management policy]
used on the client with http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring the client `Region`.
* `indexableSessionAttributes` - Identifies the Session attributes by name that should be indexed for querying purposes.
Only Session attributes explicitly identified by name will be indexed. This is useful in situations where your application
is looking up the `HttpSession` by the currently authenticated user's, or principal's, name.
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `poolName` - name of the dedicated Apache Geode `Pool` used to connect a client to the cluster of servers.
This attribute is only used when the application is a cache client. Defaults to `gemfirePool`.
* `regionName` - specifies the name of the Apache Geode `Region` used to store and manage `HttpSession` state
(default is "*ClusteredSpringSessions*").
* `serverRegionShortcut` - specifies the Apache Geode http://geode.apache.org/docs/guide/12/developing/region_options/region_types.html[data management policy]
used on the server with http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`). This attribute is only used when configuring server `Regions`, or when a P2P topology
is employed.
* `sessionSerializerBeanName` - refers to the name of the bean that handles serializing the (HTTP) `Session` state
between the client and the server.
NOTE: It is important to remember that the Apache Geode client `Region` name must match a server `Region`
by the same name if the client `Region` is a `PROXY` or `CACHING_PROXY`. Client and server `Region` names
are not required to match if the client `Region` used to store Sessions is `LOCAL` only. However, keep in mind
that Session state will not be propagated to the server when the client Region is only `LOCAL` to the client
and you lose all the benefits of using Apache Geode to store and manage Session state information on the servers
in a cluster in a highly available and replicated manner.
=== Session-scoped Proxy Bean
The Geode cache client, Spring Web application defines the `SessionScopedProxyBean` class.
[source,java]
----
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean[tags=class]
----
<1> First, the `SessionScopedProxyBean` domain class is stereotyped as a Spring `@Component` to be picked up in
_Spring's_ classpath component-scan.
<2> Additionally, instances of this class are scoped to the (HTTP) `Session`. That is, each time a client interaction
results in a (HTTP) Session being created (such as by a login event), a single instance of this type will be created
and will last for the duration of the (HTTP) Session. When the Session ends or expires, this instance is destroyed
by the _Spring_ container. If the client re-establishes a new (HTTP) Session, then another, new instance of this type
will be provided to the application's beans. However only ever 1 instance of this type exists for the duration
of the (HTTP) Session, no more!
<3> Finally, this class keeps track of how many instances of this type are created by the _Spring_ container throughout
the entire lifecycle of the application.
TIP: More information on Spring's `@SessionScope` (i.e. Session-scoped proxy beans) can be found in the
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other[Reference Documentation]
and specifically https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-session[this].
=== Request-scoped Proxy Bean
The Geode cache client, Spring Web application defines the `RequestScopedProxyBean` class.
[source,java]
----
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean[tags=class]
----
<1> First, this `RequestScopedProxyBean` domain class is stereotyped as a Spring `@Component` to be picked up in
_Spring's_ classpath component-scan.
<2> Additionally, instances of this class are scoped to the (HTTP) Request. That is, each time a client request
is sent (e.g. to process a Thread-scoped transaction), a single instance of this type will be created and will last
for the duration of the (HTTP) Request. When the Request ends, this instance is destroyed by the _Spring_ container.
Any subsequent client (HTTP) Requests results in another, new instance of this type, which will be provided to
the application's beans. However, only ever 1 instance of this type exists for the duration of the (HTTP) Request,
no more!
<3> Finally, this class keeps track of how many instances of this type are created by the _Spring_ container throughout
the entire lifecycle of the application.
TIP: More information on Spring's `@RequestScope` (i.e. Request-scoped proxy beans) can be found in the
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other[Reference Documentation]
and specifically, https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-request[this].
[spring-session-sample-boot-geode-with-scoped-proxies]]
== Spring Boot Sample Web Application with Apache Geode-managed HttpSessions and Request and Session Scoped Proxy Beans
=== Running the Boot Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
First, you must run the server:
----
$ ./gradlew :spring-session-sample-boot-gemfire:run [-Dgemfire.log-level=config]
----
Then, in a separate terminal, run the client:
----
$ ./gradlew :spring-session-sample-boot-gemfire:bootRun [-Dgemfire.log-level=config]
----
You should now be able to access the application at http://localhost:8080/.
In this sample, the Web application is the _Spring Boot_, Apache Geode cache client
and the server is standalone, separate (JVM) process.
=== Exploring the Boot Sample Application
Try using the application. Fill out the form with the following information:
* **Attribute Name:** _username_
* **Attribute Value:** _test_
Now click the **Set Attribute** button. You should now see the attribute name and value displayed in the table
along with an additional attribute (`requestCount`) indicating the number of Session interactions (via HTTP requests).
=== How does it work?
We interact with the standard `javax.servlet.http.HttpSession` in the the Spring Web MVC service endpoint,
shown here for convenience:
.src/main/java/sample/client/Application.java
[source,java]
----
@RequestMapping(method = RequestMethod.POST, path = "/session")
public String session(HttpSession session, ModelMap modelMap,
@RequestParam(name = "attributeName", required = false) String name,
@RequestParam(name = "attributeValue", required = false) String value) {
modelMap.addAttribute("sessionAttributes",
attributes(setAttribute(updateRequestCount(session), name, value)));
return INDEX_TEMPLATE_VIEW_NAME;
}
----
Instead of using the embedded HTTP server's `HttpSession`, we are actually persisting the Session state in Apache Geode.
_Spring Session_ creates a cookie named SESSION in your browser that contains the id of your Session.
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
NOTE: The following instructions assume you have a local Apache Geode installation. For more information on installation,
see http://geode.apache.org/docs/guide/12/prereq_and_install.html[Prerequisites and Installation Instructions].
If you like, you can easily remove the Session using `gfsh`.
For example, on a Linux-based system type the following at the command-line:
$ gfsh
Then, enter the following commands in _Gfsh_, ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
of your SESSION cookie, or the Session id returned by the Apache Geode OQL query (which should match):
....
gfsh>connect --jmx-manager=localhost[1099]
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
Result : true
startCount : 0
endCount : 20
Rows : 1
Result
------------------------------------
70002719-3c54-4c20-82c3-e7faa6b718f3
NEXT_STEP_NAME : END
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
....
NOTE: The _Apache Geode User Guide_ contains more detailed instructions on using
http://geode.apache.org/docs/guide/12/tools_modules/gfsh/chapter_overview.html[gfsh].
Now visit the application at `http://localhost:8080/` again and observe the attribute we added is no longer displayed.
Alternatively, you can wait **20 seconds** for the Session to timeout and expire and then refresh the page.
The attribute we added should no longer be displayed in the table.