Restructure the Spring Boot for Apache Geode project to mirror Spring Boot's project structure.

Resolves gh-60.
This commit is contained in:
John Blum
2022-02-24 16:23:35 -08:00
parent 0e8e3baeeb
commit 192133b95e
576 changed files with 0 additions and 19 deletions

View File

@@ -0,0 +1,585 @@
[[actuator]]
== Spring Boot Actuator
:geode-name: {apache-geode-name}
Spring Boot for {geode-name} (SBDG) adds {spring-boot-docs-html}/production-ready.html[Spring Boot Actuator] support
and dedicated `HealthIndicators` for {geode-name}. Equally, the provided `HealthIndicators` even work with Tanzu Cache
(which is backed by {pivotal-gemfire-name}) when you push your Spring Boot applications using {geode-name}
to {VMware Tanzu Application Service (TAS)} platform.
Spring Boot `HealthIndicators` provide details about the runtime operation and behavior of your {geode-name}-based
Spring Boot applications. For instance, by querying the right `HealthIndicator` endpoint, you can get the current
hit/miss count for your `Region.get(key)` data access operations.
In addition to vital health information, SBDG provides basic, pre-runtime configuration metadata about the
{geode-name} components that are monitored by Spring Boot Actuator. This makes it easier to see how
the application was configured all in one place, rather than in properties files, Spring configuration, XML,
and so on.
The provided Spring Boot `HealthIndicators` fall into three categories:
* Base `HealthIndicators` that apply to all {geode-name}, Spring Boot applications, regardless of cache type,
such as `Regions`, `Indexes`, and `DiskStores`.
* Peer `Cache`-based `HealthIndicators` that apply only to peer `Cache` applications, such as `AsyncEventQueues`,
`CacheServers`, `GatewayReceivers`, and `GatewaySenders`.
* `ClientCache`-based `HealthIndicators` that apply only to `ClientCache` applications, such as `ContinuousQuery`
and connection `Pools`.
The following sections give a brief overview of all the available Spring Boot `HealthIndicators` provided for
{geode-name}.
TIP: See the corresponding sample link:guides/boot-actuator.html[guide] and {github-samples-url}/boot/actuator[code]
to see Spring Boot Actuator for {geode-name} in action.
[[actuator-base-healthindicators]]
=== Base HealthIndicators
This section covers Spring Boot `HealthIndicators` that apply to both {geode-name} peer `Cache` and `ClientCache`,
Spring Boot applications. That is, these `HealthIndicators` are not specific to the cache type.
In {geode-name}, the cache instance is either a peer `Cache` instance (which makes your Spring Boot application
part of a {geode-name} cluster) or, more commonly, a `ClientCache` instance (which talks to an existing cluster).
Your Spring Boot application can only be one cache type or the other and can only have a single instance of
that cache type.
[[actuator-base-healthindicators-cache]]
==== GeodeCacheHealthIndicator
`GeodeCacheHealthIndicator` provides essential details about the (single) cache instance (client or peer)
and the underlying `DistributedSystem`, the `DistributedMember` and configuration details of the `ResourceManager`.
When your Spring Boot application creates an instance of a peer {apache-geode-javadoc}/org/apache/geode/cache/Cache.html[`Cache`],
the {apache-geode-javadoc}/org/apache/geode/distributed/DistributedMember.html[`DistributedMember`] object represents
your application as a peer member or node of the {apache-geode-javadoc}/org/apache/geode/distributed/DistributedSystem.html[`DistributedSystem`].
The distributed system (that is, the cluster) is formed from a collection of connected peers, to which your application
also has {apache-geode-javadoc}/org/apache/geode/cache/GemFireCache.html#getDistributedSystem--[access] -- indirectly,
through the cache instance.
This is no different for a `ClientCache` even though the client is technically not part of the peer/server cluster.
However, it still creates instances of the `DistributedSystem` and `DistributedMember` objects, respectively.
Each object has the following configuration metadata and health details:
.Cache Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.name | Name of the member in the distributed system.
| geode.cache.closed | Determines whether the cache has been closed.
| geode.cache.cancel-in-progress | Indicates whether cancellation of operations is in progress.
|=====================================================================================================================
.DistributedMember Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.distributed-member.id | `DistributedMember` identifier (used in logs internally).
| geode.distributed-member.name | Name of the member in the distributed system.
| geode.distributed-members.groups | Configured groups to which the member belongs.
| geode.distributed-members.host | Name of the machine on which the member is running.
| geode.distributed-members.process-id | Identifier of the JVM process (PID).
|=====================================================================================================================
.DistributedSystem Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.distributed-system.connected | Indicates whether the member is currently connected to
the cluster.
| geode.distributed-system.member-count | Total number of members in the cluster (1 for clients).
| geode.distributed-system.reconnecting | Indicates whether the member is in a reconnecting state,
which happens when a network partition occurs and the member gets disconnected from the cluster.
| geode.distributed-system.properties-location | Location of the
{apache-geode-docs}/topics/gemfire_properties.html[standard configuration properties].
| geode.distributed-system.security-properties-location | Location of the
{apache-geode-docs}/topics/gemfire_properties.html[security configuration properties].
|=====================================================================================================================
.ResourceManager Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.resource-manager.critical-heap-percentage | Percentage of heap at which the cache is in danger of
becoming inoperable.
| geode.resource-manager.critical-off-heap-percentage | Percentage of off-heap at which the cache is in danger of
becoming inoperable.
| geode.resource-manager.eviction-heap-percentage | Percentage of heap at which eviction begins on Regions
configured with a heap LRU eviction policy.
| geode.resource-manager.eviction-off-heap-percentage | Percentage of off-heap at which eviction begins on Regions
configured with a heap LRU eviction policy.
|=====================================================================================================================
[[actuator-base-healthindicators-regions]]
==== GeodeRegionsHealthIndicator
`GeodeRegionsHealthIndicator` provides details about all the configured and known `Regions` in the cache.
If the cache is a client, details include all `LOCAL`, `PROXY`, and `CACHING_PROXY` `Regions`. If the cache is a peer
then details include all `LOCAL`, `PARTITION`, and `REPLICATE` `Region` instances.
The following table describes the essential details and basic performance metrics:
.Region Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.regions.<name>.cloning-enabled | Whether Region values are cloned on read (for example, `cloning-enabled`
is `true` when cache transactions are used to prevent in-place modifications).
| geode.cache.regions.<name>.data-policy | Policy used to manage data in the Region (`PARTITION`, `REPLICATE`,
and others).
| geode.cache.regions.<name>.initial-capacity | Initial number of entries that can be held by a Region before it needs
to be resized.
| geode.cache.regions.<name>.load-factor | Load factor used to determine when to resize the Region when it nears
capacity.
| geode.cache.regions.<name>.key-constraint | Type constraint for Region keys.
| geode.cache.regions.<name>.off-heap | Determines whether this Region stores values in off-heap memory
(NOTE: Keys are always kept on the JVM heap).
| geode.cache.regions.<name>.pool-name | If this Region is a client Region, this property determines
the configured connection `Pool`. (NOTE: Regions can have and use dedicated `Pools` for their data access operations.)
| geode.cache.regions.<name>.pool-name | Determines the `Scope` of the Region, which plays a factor in
the Region's consistency-level, as it pertains to acknowledgements for writes.
| geode.cache.regions.<name>.value-constraint | Type constraint for Region values.
|=====================================================================================================================
The following details also apply when the Region is a peer `Cache` `PARTITION` Region:
.Partition Region Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.regions.<name>.partition.collocated-with | Indicates whether this Region is collocated with
another `PARTITION` Region, which is necessary when performing equi-joins queries (NOTE: distributed joins
are not supported).
| geode.cache.regions.<name>.partition.local-max-memory | Total amount of heap memory allowed to be used by
this Region on this node.
| geode.cache.regions.<name>.partition.redundant-copies | Number of replicas for this `PARTITION` Region,
which is useful in high availability (HA) use cases.
| geode.cache.regions.<name>.partition.total-max-memory | Total amount of heap memory allowed to be used by
this Region across all nodes in the cluster hosting this Region.
| geode.cache.regions.<name>.partition.total-number-of-buckets | Total number of buckets (shards) into which this Region
is divided (defaults to 113).
|=====================================================================================================================
Finally, when statistics are enabled (for example, when you use `@EnableStatistics` --
(see {spring-data-geode-docs-html}/#bootstrap-annotation-config-statistics[doc] for more details),
the following metadata is available:
.Region Statistic Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.regions.<name>.statistics.hit-count | Number of hits for a region entry.
| geode.cache.regions.<name>.statistics.hit-ratio | Ratio of hits to the number of `Region.get(key)` calls.
| geode.cache.regions.<name>.statistics.last-accessed-time | For an entry, indicates the last time it was accessed
with `Region.get(key)`.
| geode.cache.regions.<name>.statistics.last-modified-time | For an entry, indicates the time when a Region's entry
value was last modified.
| geode.cache.regions.<name>.statistics.miss-count | Returns the number of times that a `Region.get`
was performed and no value was found locally.
|=====================================================================================================================
[[actuator-base-healthindicators-indexes]]
==== GeodeIndexesHealthIndicator
`GeodeIndexesHealthIndicator` provides details about the configured Region `Indexes` used by OQL query data access
operations.
The following details are covered:
.Index Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.index.<name>.from-clause | Region from which data is selected.
| geode.index.<name>.indexed-expression | Region value fields and properties used in the Index expression.
| geode.index.<name>.projection-attributes | For `Map` `Indexes`, returns either `*` or the specific Map keys
that were indexed. For all other `Indexes`, returns `*`.
| geode.index.<name>.region | Region to which the Index is applied.
|=====================================================================================================================
Additionally, when statistics are enabled (for example, when you use `@EnableStatistics` --
see {spring-data-geode-docs-html}/#bootstrap-annotation-config-statistics[Configuring Statistics] for more details),
the following metadata is available:
.Index Statistic Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.index.<name>.statistics.number-of-bucket-indexes | Number of bucket Indexes created in a PARTITION Region.
| geode.index.<name>.statistics.number-of-keys | Number of keys in this Index.
| geode.index.<name>.statistics.number-of-map-indexed-keys | Number of keys in this Index at the highest level.
| geode.index.<name>.statistics.number-of-values | Number of values in this Index.
| geode.index.<name>.statistics.number-of-updates | Number of times this Index has been updated.
| geode.index.<name>.statistics.read-lock-count | Number of read locks taken on this Index.
| geode.index.<name>.statistics.total-update-time | Total amount of time (ns) spent updating this Index.
| geode.index.<name>.statistics.total-uses | Total number of times this Index has been accessed by
an OQL query.
|=====================================================================================================================
[[actuator-base-healthindicators-diskstores]]
==== GeodeDiskStoresHealthIndicator
The `GeodeDiskStoresHealthIndicator` provides details about the configured `DiskStores` in the system or application.
Remember, `DiskStores` are used to overflow and persist data to disk, including type metadata tracked by PDX when the
values in the Regions have been serialized with PDX and the Regions are persistent.
Most of the tracked health information pertains to configuration:
.DiskStore Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.disk-store.<name>.allow-force-compaction | Indicates whether manual compaction of the DiskStore
is allowed.
| geode.disk-store.<name>.auto-compact | Indicates whether compaction occurs automatically.
| geode.disk-store.<name>.compaction-threshold | Percentage at which the oplog becomes compactible.
| geode.disk-store.<name>.disk-directories | Location of the oplog disk files.
| geode.disk-store.<name>.disk-directory-sizes | Configured and allowed sizes (MB) for the disk directory
that stores the disk files.
| geode.disk-store.<name>.disk-usage-critical-percentage | Critical threshold of disk usage proportional to
the total disk volume.
| geode.disk-store.<name>.disk-usage-warning-percentage | Warning threshold of disk usage proportional to
the total disk volume.
| geode.disk-store.<name>.max-oplog-size | Maximum size (MB) allowed for a single oplog file.
| geode.disk-store.<name>.queue-size | Size of the queue used to batch writes that are flushed
to disk.
| geode.disk-store.<name>.time-interval | Time to wait (ms) before writes are flushed to disk
from the queue if the size limit has not be reached.
| geode.disk-store.<name>.uuid | Universally unique identifier for the DiskStore across a
distributed system.
| geode.disk-store.<name>.write-buffer-size | Size the of write buffer the DiskStore uses to write data
to disk.
|=====================================================================================================================
[[actuator-clientcache-healthindicators]]
=== `ClientCache` `HealthIndicators`
The `ClientCache`-based `HealthIndicators` provide additional details specifically for Spring Boot, cache client
applications. These `HealthIndicators` are available only when the Spring Boot application creates a `ClientCache`
instance (that is, the application is a cache client), which is the default.
[[actuator-clientcache-healthindicators-cq]]
==== GeodeContinuousQueriesHealthIndicator
`GeodeContinuousQueriesHealthIndicator` provides details about registered client Continuous Queries (CQs). CQs let
client applications receive automatic notification about events that satisfy some criteria. That criteria can be easily
expressed by using the predicate of an OQL query (for example, `SELECT * FROM /Customers c WHERE c.age > 21`).
When data is inserted or updated and the data matches the criteria specified in the OQL query predicate
(data of interests), an event is sent to the registered client.
The following details are covered for CQs by name:
.Continuous Query (CQ) Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.continuous-query.<name>.oql-query-string | OQL query constituting the CQ.
| geode.continuous-query.<name>.closed | Indicates whether the CQ has been closed.
| geode.continuous-query.<name>.closing | Indicates whether the CQ is in the process of closing.
| geode.continuous-query.<name>.durable | Indicates whether the CQ events are remembered
between client sessions.
| geode.continuous-query.<name>.running | Indicates whether the CQ is currently running.
| geode.continuous-query.<name>.stopped | Indicates whether the CQ has been stopped.
|=====================================================================================================================
In addition, the following CQ query and statistical data is covered:
.Continuous Query (CQ), Query Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.continuous-query.<name>.query.number-of-executions | Total number of times the query has been executed.
| geode.continuous-query.<name>.query.total-execution-time | Total amount of time (ns) spent executing the query.
|=====================================================================================================================
.Continuous Query(CQ), Statistic Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.continuous-query.<name>.statistics.number-of-deletes | Number of delete events qualified by this CQ.
| geode.continuous-query.<name>.statistics.number-of-events | Total number of events qualified by this CQ.
| geode.continuous-query.<name>.statistics.number-of-inserts | Number of insert events qualified by this CQ.
| geode.continuous-query.<name>.statistics.number-of-updates | Number of update events qualified by this CQ.
|=====================================================================================================================
The {geode-name} Continuous Query system is also tracked with the following additional details on the client:
.Continuous Query (CQ), Additional Statistic Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.continuous-query.count | Total count of CQs.
| geode.continuous-query.number-of-active | Number of currently active CQs (if available).
| geode.continuous-query.number-of-closed | Total number of closed CQs (if available).
| geode.continuous-query.number-of-created | Total number of created CQs (if available).
| geode.continuous-query.number-of-stopped | Number of currently stopped CQs (if available).
| geode.continuous-query.number-on-client | Number of CQs that are currently active or stopped
(if available).
|=====================================================================================================================
[[actuator-clientcache-healthindicators-pools]]
==== GeodePoolsHealthIndicator
`GeodePoolsHealthIndicator` provides details about all the configured client connection `Pools`. This `HealthIndicator`
primarily provides configuration metadata for all the configured `Pools`.
The following details are covered:
.Pool Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.pool.count | Total number of client connection pools.
| geode.pool.<name>.destroyed | Indicates whether the pool has been destroyed.
| geode.pool.<name>.free-connection-timeout | Configured amount of time to wait for a free connection
from the Pool.
| geode.pool.<name>.idle-timeout | The amount of time to wait before closing unused, idle
connections, not exceeding the configured number of minimum required connections.
| geode.pool.<name>.load-conditioning-interval | How frequently the Pool checks to see whether a connection
to a given server should be moved to a different server to improve the load balance.
| geode.pool.<name>.locators | List of configured Locators.
| geode.pool.<name>.max-connections | Maximum number of connections obtainable from the Pool.
| geode.pool.<name>.min-connections | Minimum number of connections contained by the Pool.
| geode.pool.<name>.multi-user-authentication | Determines whether the Pool can be used by multiple
authenticated users.
| geode.pool.<name>.online-locators | Returns a list of living Locators.
| geode.pool.<name>.pending-event-count | Approximate number of pending subscription events maintained
at the server for this durable client Pool at the time it (re)connected to the server.
| geode.pool.<name>.ping-interval | How often to ping the servers to verify they are still alive.
| geode.pool.<name>.pr-single-hop-enabled | Whether the client acquires a direct connection to
the server.
| geode.pool.<name>.read-timeout | Number of milliseconds to wait for a response from a server
before timing out the operation and trying another server (if any are available).
| geode.pool.<name>.retry-attempts | Number of times to retry a request after a timeout
or an exception.
| geode.pool.<name>.server-group | All servers must belong to the same group, and this value
sets the name of that group.
| geode.pool.<name>.servers | List of configured servers.
| geode.pool.<name>.socket-buffer-size | Socket buffer size for each connection made in this pool.
| geode.pool.<name>.statistic-interval | How often to send client statistics to the server.
| geode.pool.<name>.subscription-ack-interval | Interval in milliseconds to wait before sending
acknowledgements to the cache server for events received from the server subscriptions.
| geode.pool.<name>.subscription-enabled | Enabled server-to-client subscriptions.
| geode.pool.<name>.subscription-message-tracking-timeout | Time-to-Live (TTL) period (ms) for subscription events
the client has received from the server.
| geode.pool.<name>.subscription-redundancy | Redundancy level for this Pool's server-to-client
subscriptions, which is used to ensure clients do not miss potentially important events.
| geode.pool.<name>.thread-local-connections | Thread local connection policy for this Pool.
|=====================================================================================================================
[[actuator-peercache-healthindicators]]
=== Peer Cache HealthIndicators
The peer `Cache`-based `HealthIndicators` provide additional details specifically for Spring Boot peer cache member
applications. These `HealthIndicators` are available only when the Spring Boot application creates a peer `Cache`
instance.
NOTE: The default cache instance created by Spring Boot for {geode-name} is a `ClientCache` instance.
TIP: To control what type of cache instance is created, such as a "`peer`", you can explicitly declare either the
`@PeerCacheApplication` or, alternatively, the `@CacheServerApplication` annotation on your
`@SpringBootApplication`-annotated class.
[[actuator-peercache-healthindicators-cacheservers]]
==== GeodeCacheServersHealthIndicator
The `GeodeCacheServersHealthIndicator` provides details about the configured {geode-name} `CacheServer` instances.
`CacheServer` instances are required to enable clients to connect to the servers in the cluster.
This `HealthIndicator` captures basic configuration metadata and the runtime behavior and characteristics of
the configured `CacheServer` instances:
.CacheServer Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.server.count | Total number of configured `CacheServer` instances
on this peer member.
| geode.cache.server.<index>.bind-address | IP address of the NIC to which the `CacheServer` `ServerSocket`
is bound (useful when the system contains multiple NICs).
| geode.cache.server.<index>.hostname-for-clients | Name of the host used by clients to connect to the `CacheServer`
(useful with DNS).
| geode.cache.server.<index>.load-poll-interval | How often (ms) to query the load probe on the `CacheServer`.
| geode.cache.server.<index>.max-connections | Maximum number of connections allowed to this `CacheServer`.
| geode.cache.server.<index>.max-message-count | Maximum number of messages that can be put in a client queue.
| geode.cache.server.<index>.max-threads | Maximum number of threads allowed in this `CacheServer`
to service client requests.
| geode.cache.server.<index>.max-time-between-pings | Maximum time between client pings.
| geode.cache.server.<index>.message-time-to-live | Time (seconds) in which the client queue expires.
| geode.cache.server.<index>.port | Network port to which the CacheServer `ServerSocket` is bound
and on which it listens for client connections.
| geode.cache.server.<index>.running | Determines whether this `CacheServer` is currently running
and accepting client connections.
| geode.cache.server.<index>.socket-buffer-size | Configured buffer size of the socket connection used by
this CacheServer.
| geode.cache.server.<index>.tcp-no-delay | Configures the TCP/IP `TCP_NO_DELAY` setting on outgoing sockets.
|=====================================================================================================================
In addition to the configuration settings shown in the preceding table, the `ServerLoadProbe` of the `CacheServer`
tracks additional details about the runtime characteristics of the `CacheServer`:
.CacheServer Metrics and Load Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.cache.server.<index>.load.connection-load | Load on the server due to client-to-server
connections.
| geode.cache.server.<index>.load.load-per-connection | Estimate of how much load each new connection
adds to this server.
| geode.cache.server.<index>.load.subscription-connection-load | Load on the server due to subscription connections.
| geode.cache.server.<index>.load.load-per-subscription-connection | Estimate of how much load each new subscriber adds
to this server.
| geode.cache.server.<index>.metrics.client-count | Number of connected clients.
| geode.cache.server.<index>.metrics.max-connection-count | Maximum number of connections made to this
`CacheServer`.
| geode.cache.server.<index>.metrics.open-connection-count | Number of open connections to this `CacheServer`.
| geode.cache.server.<index>.metrics.subscription-connection-count | Number of subscription connections to this
`CacheServer`.
|=====================================================================================================================
[[actuator-peercache-healthindicators-aeq]]
==== GeodeAsyncEventQueuesHealthIndicator
`GeodeAsyncEventQueuesHealthIndicator` provides details about the configured `AsyncEventQueues`. AEQs can be attached to
Regions to configure asynchronous write-behind behavior.
This `HealthIndicator` captures configuration metadata and runtime characteristics for all AEQs:
.AsyncEventQueue Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.async-event-queue.count | Total number of configured AEQs.
| geode.async-event-queue.<id>.batch-conflation-enabled | Indicates whether batch events are conflated when sent.
| geode.async-event-queue.<id>.batch-size | Size of the batch that gets delivered over this AEQ.
| geode.async-event-queue.<id>.batch-time-interval | Maximum time interval that can elapse before a batch is sent.
| geode.async-event-queue.<id>.disk-store-name | Name of the disk store used to overflow and persist events.
| geode.async-event-queue.<id>.disk-synchronous | Indicates whether disk writes are synchronous or asynchronous.
| geode.async-event-queue.<id>.dispatcher-threads | Number of threads used to dispatch events.
| geode.async-event-queue.<id>.forward-expiration-destroy | Indicates whether expiration destroy operations
are forwarded to `AsyncEventListener`.
| geode.async-event-queue.<id>.max-queue-memory | Maximum memory used before data needs to be overflowed
to disk.
| geode.async-event-queue.<id>.order-policy | Order policy followed while dispatching the events to
`AsyncEventListeners`.
| geode.async-event-queue.<id>.parallel | Indicates whether this queue is parallel (higher throughput)
or serial.
| geode.async-event-queue.<id>.persistent | Indicates whether this queue stores events to disk.
| geode.async-event-queue.<id>.primary | Indicates whether this queue is primary or secondary.
| geode.async-event-queue.<id>.size | Number of entries in this queue.
|=====================================================================================================================
[[actuator-peercache-healthindicators-gateway-receivers]]
==== GeodeGatewayReceiversHealthIndicator
`GeodeGatewayReceiversHealthIndicator` provides details about the configured (WAN) `GatewayReceivers`,
which are capable of receiving events from remote clusters when using {geode-name}'s
{apache-geode-docs}/topologies_and_comm/multi_site_configuration/chapter_overview.html[multi-site, WAN topology].
This `HealthIndicator` captures configuration metadata along with the running state for each `GatewayReceiver`:
.GatewayReceiver Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.gateway-receiver.count | Total number of configured `GatewayReceiver` instances.
| geode.gateway-receiver.<index>.bind-address | IP address of the NIC to which the `GatewayReceiver`
`ServerSocket` is bound (useful when the system contains multiple NICs).
| geode.gateway-receiver.<index>.end-port | End value of the port range from which the port of
the `GatewayReceiver` is chosen.
| geode.gateway-receiver.<index>.host | IP address or hostname that Locators tell clients (that is,
`GatewaySender` instances) on which this `GatewayReceiver` listens.
| geode.gateway-receiver.<index>.max-time-between-pings | Maximum amount of time between client pings.
| geode.gateway-receiver.<index>.port | Port on which this `GatewayReceiver` listens for clients
(that is, `GatewaySender` instances).
| geode.gateway-receiver.<index>.running | Indicates whether this `GatewayReceiver` is running
and accepting client connections (from `GatewaySender` instances).
| geode.gateway-receiver.<index>.socket-buffer-size | Configured buffer size for the socket connections used by
this `GatewayReceiver`.
| geode.gateway-receiver.<index>.start-port | Start value of the port range from which the port of
the `GatewayReceiver` is chosen.
|=====================================================================================================================
[[actuator-peercache-healthindicators-gateway-senders]]
==== GeodeGatewaySendersHealthIndicator
The `GeodeGatewaySendersHealthIndicator` provides details about the configured `GatewaySenders`. `GatewaySender`
instances are attached to Regions in order to send Region events to remote clusters in {geode-name}'s
{apache-geode-docs}/topologies_and_comm/multi_site_configuration/chapter_overview.html[multi-site, WAN topology].
This `HealthIndicator` captures essential configuration metadata and runtime characteristics for each `GatewaySender`:
.GatewaySender Details
[width="90%",cols="^3,<10",options="header"]
|=====================================================================================================================
| Name | Description
| geode.gateway-sender.count | Total number of configured `GatewaySender` instances.
| geode.gateway-sender.<id>.alert-threshold | Alert threshold (ms) for entries in this
`GatewaySender` instances queue.
| geode.gateway-sender.<id>.batch-conflation-enabled | Indicates whether batch events are conflated
when sent.
| geode.gateway-sender.<id>.batch-size | Size of the batches sent.
| geode.gateway-sender.<id>.batch-time-interval | Maximum time interval that can elapse before a batch
is sent.
| geode.gateway-sender.<id>.disk-store-name | Name of the `DiskStore` used to overflow and persist
queued events.
| geode.gateway-sender.<id>.disk-synchronous | Indicates whether disk writes are synchronous
or asynchronous.
| geode.gateway-sender.<id>.dispatcher-threads | Number of threads used to dispatch events.
| geode.gateway-sender.<id>.max-parallelism-for-replicated-region |
| geode.gateway-sender.<id>.max-queue-memory | Maximum amount of memory (MB) usable for this
`GatewaySender` instance's queue.
| geode.gateway-sender.<id>.order-policy | Order policy followed while dispatching the events
to `GatewayReceiver` instances.
| geode.gateway-sender.<id>.parallel | Indicates whether this `GatewaySender` is parallel
(higher throughput) or serial.
| geode.gateway-sender.<id>.paused | Indicates whether this `GatewaySender` is paused.
| geode.gateway-sender.<id>.persistent | Indicates whether this `GatewaySender` persists
queue events to disk.
| geode.gateway-sender.<id>.remote-distributed-system-id | Identifier for the remote distributed system.
| geode.gateway-sender.<id>.running | Indicates whether this `GatewaySender`
is currently running.
| geode.gateway-sender.<id>.socket-buffer-size | Configured buffer size for the socket connections
between this `GatewaySender` and the receiving `GatewayReceiver`.
| geode.gateway-sender.<id>.socket-read-timeout | Amount of time (ms) that a socket read between this
sending `GatewaySender` and the receiving `GatewayReceiver` blocks.
|=====================================================================================================================

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,761 @@
[[geode-clientcache-applications]]
== Building `ClientCache` Applications
:geode-name: {apache-geode-name}
The first opinionated option provided to you by Spring Boot for {geode-name} (SBDG) is a
{apache-geode-javadoc}/org/apache/geode/cache/client/ClientCache.html[`ClientCache`] instance
that you get by declaring Spring Boot for {geode-name} on your application classpath.
It is assumed that most application developers who use Spring Boot to build applications backed by {geode-name}
are building cache client applications deployed in an {geode-name}
{apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology].
The client/server topology is the most common and traditional architecture employed by enterprise applications
that use {geode-name}.
For example, you can begin building a Spring Boot {geode-name} `ClientCache` application by declaring the
`spring-geode-starter` on your application's classpath:
.Spring Boot for {geode-name} on the application classpath
====
[source,xml]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter</artifactId>
</dependency>
----
====
Then you configure and bootstrap your Spring Boot, {geode-name} `ClientCache` application with the following
main application class:
.Spring Boot, {geode-name} `ClientCache` Application
====
[source,java]
----
@SpringBootApplication
public class SpringBootApacheGeodeClientCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeClientCacheApplication.class, args);
}
}
----
====
Your application now has a `ClientCache` instance that can connect to an {geode-name} server running on `localhost`
and listening on the default `CacheServer` port, `40404`.
By default, an {geode-name} server (that is, `CacheServer`) must be running for the application to use the `ClientCache`
instance. However, it is perfectly valid to create a `ClientCache` instance and perform data access operations by using
`LOCAL` Regions. This is useful during development.
TIP: To develop with `LOCAL` Regions, configure your cache Regions with the
{apache-geode-javadoc}/org/apache/geode/cache/client/ClientRegionShortcut.html#LOCAL[`ClientRegionShortcut.LOCAL`]
data management policy.
When you are ready to switch from your local development environment (IDE) to a client/server architecture in a managed
environment, change the data management policy of the client Region from `LOCAL` back to the default (`PROXY`)
or even a `CACHING_PROXY`, which causes the data to be sent to and received from one or more servers.
TIP: Compare and contrast the preceding configuration with the Spring Data for {geode-name}
{spring-data-geode-docs-html}/#bootstrap-annotation-config-geode-applications[approach].
It is uncommon to ever need a direct reference to the `ClientCache` instance provided by SBDG injected into your
application components (for example, `@Service` or `@Repository` beans defined in a Spring `ApplicationContext`),
whether you are configuring additional {geode-name} objects (Regions, Indexes, and so on) or are using those objects
indirectly in your applications. However, it is possible to do so if and when needed.
For example, perhaps you want to perform some additional `ClientCache` initialization in a Spring Boot
{spring-boot-javadoc}/org/springframework/boot/ApplicationRunner.html[`ApplicationRunner`] on startup:
.Injecting a `GemFireCache` reference
====
[source,java]
----
@SpringBootApplication
public class SpringBootApacheGeodeClientCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeClientCacheApplication.class, args);
}
@Bean
ApplicationRunner runAdditionalClientCacheInitialization(GemFireCache gemfireCache) {
return args -> {
ClientCache clientCache = (ClientCache) gemfireCache;
// perform additional ClientCache initialization as needed
};
}
}
----
====
[[geode-peercache-applications]]
=== Building Embedded (Peer & Server) Cache Applications
What if you want to build an embedded peer `Cache` application instead?
Perhaps you need an actual peer cache member, configured and bootstrapped with Spring Boot, along with the ability
to join this member to an existing cluster (of data servers) as a peer node.
Remember the second goal in Spring Boot's {spring-boot-docs-html}/#getting-started-introducing-spring-boot[documentation]:
> Be opinionated out of the box but get out of the way quickly as requirements start to diverge from the defaults.
Here, we focus on the second part of the goal: "_get out of the way quickly as requirements start to diverge
from the defaults_".
If your application requirements demand you use Spring Boot to configure and bootstrap an embedded peer `Cache` instance,
declare your intention with either SDG's
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html[`@PeerCacheApplication`] annotation,
or, if you also need to enable connections from `ClientCache` applications, use SDG's
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html[`@CacheServerApplication`] annotation:
.Spring Boot, {geode-name} `CacheServer` Application
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication(name = "SpringBootApacheGeodeCacheServerApplication")
public class SpringBootApacheGeodeCacheServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeCacheServerApplication.class, args);
}
}
----
====
TIP: An {geode-name} server is not necessarily a `CacheServer` capable of serving cache clients. It is merely a peer
member node in an {geode-name} cluster (that is, a distributed system) that stores and manages data.
By explicitly declaring the `@CacheServerApplication` annotation, you tell Spring Boot that you do not want the default
`ClientCache` instance but rather want an embedded peer `Cache` instance with a `CacheServer` component, which enables
connections from `ClientCache` applications.
You can also enable two other {geode-name} services:
* An embedded _Locator_, which allows clients or even other peers to locate servers in the cluster.
* An embedded _Manager_, which allows the {geode-name} application process to be managed and monitored by using
{apache-geode-docs}/tools_modules/gfsh/chapter_overview.html[Gfsh], {geode-name}'s command-line shell tool:
.Spring Boot {geode-name} `CacheServer` Application with _Locator_ and _Manager_ services enabled
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication(name = "SpringBootApacheGeodeCacheServerApplication")
@EnableLocator
@EnableManager
public class SpringBootApacheGeodeCacheServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeCacheServerApplication.class, args);
}
}
----
====
Then you can use Gfsh to connect to and manage this server:
====
[source,text]
----
$ echo $GEMFIRE
/Users/jblum/pivdev/apache-geode-1.2.1
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.2.1
Monitor and Manage Apache Geode
gfsh>connect
Connecting to Locator at [host=localhost, port=10334] ..
Connecting to Manager at [host=10.0.0.121, port=1099] ..
Successfully connected to: [host=10.0.0.121, port=1099]
gfsh>list members
Name | Id
------------------------------------------- | --------------------------------------------------------------------------
SpringBootApacheGeodeCacheServerApplication | 10.0.0.121(SpringBootApacheGeodeCacheServerApplication:29798)<ec><v0>:1024
gfsh>describe member --name=SpringBootApacheGeodeCacheServerApplication
Name : SpringBootApacheGeodeCacheServerApplication
Id : 10.0.0.121(SpringBootApacheGeodeCacheServerApplication:29798)<ec><v0>:1024
Host : 10.0.0.121
Regions :
PID : 29798
Groups :
Used Heap : 168M
Max Heap : 3641M
Working Dir : /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build
Log file : /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build
Locators : localhost[10334]
Cache Server Information
Server Bind :
Server Port : 40404
Running : true
Client Connections : 0
----
====
You can even start additional servers in Gfsh. These additional servers connect to your Spring Boot configured
and bootstrapped {geode-name} `CacheServer` application. These additional servers started in Gfsh know about
the Spring Boot, {geode-name} server because of the embedded Locator service, which is running on `localhost`
and listening on the default Locator port, `10334`:
====
[source,text]
----
gfsh>start server --name=GfshServer --log-level=config --disable-default-server
Starting a Geode Server in /Users/jblum/pivdev/lab/GfshServer...
...
Server in /Users/jblum/pivdev/lab/GfshServer on 10.0.0.121 as GfshServer is currently online.
Process ID: 30031
Uptime: 3 seconds
Geode Version: 1.2.1
Java Version: 1.8.0_152
Log File: /Users/jblum/pivdev/lab/GfshServer/GfshServer.log
JVM Arguments: -Dgemfire.default.locators=10.0.0.121:127.0.0.1[10334] -Dgemfire.use-cluster-configuration=true -Dgemfire.start-dev-rest-api=false -Dgemfire.log-level=config -XX:OnOutOfMemoryError=kill -KILL %p -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-core-1.2.1.jar:/Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-dependencies.jar
gfsh>list members
Name | Id
------------------------------------------- | --------------------------------------------------------------------------
SpringBootApacheGeodeCacheServerApplication | 10.0.0.121(SpringBootApacheGeodeCacheServerApplication:29798)<ec><v0>:1024
GfshServer | 10.0.0.121(GfshServer:30031)<v1>:1025
----
====
Perhaps you want to start the other way around. You may need to connect a Spring Boot configured and bootstrapped
{geode-name} server application to an existing cluster. You can start the cluster in Gfsh with the following commands
(shown with partial typical output):
====
[source,text]
----
gfsh>start locator --name=GfshLocator --port=11235 --log-level=config
Starting a Geode Locator in /Users/jblum/pivdev/lab/GfshLocator...
...
Locator in /Users/jblum/pivdev/lab/GfshLocator on 10.0.0.121[11235] as GfshLocator is currently online.
Process ID: 30245
Uptime: 3 seconds
Geode Version: 1.2.1
Java Version: 1.8.0_152
Log File: /Users/jblum/pivdev/lab/GfshLocator/GfshLocator.log
JVM Arguments: -Dgemfire.log-level=config -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-core-1.2.1.jar:/Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-dependencies.jar
Successfully connected to: JMX Manager [host=10.0.0.121, port=1099]
Cluster configuration service is up and running.
gfsh>start server --name=GfshServer --log-level=config --disable-default-server
Starting a Geode Server in /Users/jblum/pivdev/lab/GfshServer...
....
Server in /Users/jblum/pivdev/lab/GfshServer on 10.0.0.121 as GfshServer is currently online.
Process ID: 30270
Uptime: 4 seconds
Geode Version: 1.2.1
Java Version: 1.8.0_152
Log File: /Users/jblum/pivdev/lab/GfshServer/GfshServer.log
JVM Arguments: -Dgemfire.default.locators=10.0.0.121[11235] -Dgemfire.use-cluster-configuration=true -Dgemfire.start-dev-rest-api=false -Dgemfire.log-level=config -XX:OnOutOfMemoryError=kill -KILL %p -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-core-1.2.1.jar:/Users/jblum/pivdev/apache-geode-1.2.1/lib/geode-dependencies.jar
gfsh>list members
Name | Id
----------- | --------------------------------------------------
GfshLocator | 10.0.0.121(GfshLocator:30245:locator)<ec><v0>:1024
GfshServer | 10.0.0.121(GfshServer:30270)<v1>:1025
----
====
Then modify the `SpringBootApacheGeodeCacheServerApplication` class to connect to the existing cluster:
.Spring Boot {geode-name} `CacheServer` Application connecting to an external cluster
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication(name = "SpringBootApacheGeodeCacheServerApplication", locators = "localhost[11235]")
public class SpringBootApacheGeodeCacheServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeClientCacheApplication.class, args);
}
}
----
====
TIP: Notice that the `SpringBootApacheGeodeCacheServerApplication` class, `@CacheServerApplication` annotation's
`locators` property are configured with the host and port (`localhost[11235]`), on which the Locator was started
by using Gfsh.
After running your Spring Boot {geode-name} `CacheServer` application again and executing the `list members` command
in Gfsh again, you should see output similar to the following:
====
[source,text]
----
gfsh>list members
Name | Id
------------------------------------------- | ----------------------------------------------------------------------
GfshLocator | 10.0.0.121(GfshLocator:30245:locator)<ec><v0>:1024
GfshServer | 10.0.0.121(GfshServer:30270)<v1>:1025
SpringBootApacheGeodeCacheServerApplication | 10.0.0.121(SpringBootApacheGeodeCacheServerApplication:30279)<v2>:1026
gfsh>describe member --name=SpringBootApacheGeodeCacheServerApplication
Name : SpringBootApacheGeodeCacheServerApplication
Id : 10.0.0.121(SpringBootApacheGeodeCacheServerApplication:30279)<v2>:1026
Host : 10.0.0.121
Regions :
PID : 30279
Groups :
Used Heap : 165M
Max Heap : 3641M
Working Dir : /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build
Log file : /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build
Locators : localhost[11235]
Cache Server Information
Server Bind :
Server Port : 40404
Running : true
Client Connections : 0
----
====
In both scenarios, the Spring Boot configured and bootstrapped {geode-name} server, the Gfsh Locator and Gfsh server
formed a cluster.
While you can use either approach and Spring does not care, it is far more convenient to use Spring Boot and your IDE
to form a small cluster while developing. Spring profiles make it far simpler and much faster to configure and start
a small cluster.
Also, this approach enables rapidly prototyping, testing, and debugging your entire end-to-end application
and system architecture right from the comfort and familiarity of your IDE. No additional tooling (such as Gfsh)
or knowledge is required to get started quickly and easily. Just build and run.
TIP: Be careful to vary your port numbers for the embedded services, like the `CacheServer`, Locators, and the Manager,
especially if you start multiple instances on the same machine. Otherwise, you are likely to run into
a `java.net.BindException` caused by port conflicts.
TIP: See the <<geode-cluster-configuration-bootstrapping>> appendix for more details.
[[geode-locator-applications]]
=== Building Locator Applications
In addition to `ClientCache`, `CacheServer`, and peer `Cache` applications, SDG, and by extension SBDG, now supports
Spring Boot {geode-name} Locator applications.
An {geode-name} Locator is a location-based service or, more typically, a standalone process that lets clients locate
a cluster of {geode-name} servers to manage data. Many cache clients can connect to the same cluster to share data.
Running multiple clients is common in a Microservices architecture where you need to scale-up the number of application
instances to satisfy the demand.
An {geode-name} Locator is also used by joining members of an existing cluster to scale-out and increase capacity of
the logically pooled system resources (memory, CPU, network and disk). A Locator maintains metadata that is sent to
the clients to enable such capabilities as single-hop data access to route data access operations to the data node
in the cluster maintaining the data of interests. A Locator also maintains load information for servers in the cluster,
which enables the load to be uniformly distributed across the cluster while also providing fail-over services to a
redundant member if the primary fails. A Locator provides many more benefits, and we encourage you to read the
{apache-geode-docs}/configuring/running/running_the_locator.html[documentation] for more details.
As shown earlier, you can embed a Locator service within either a Spring Boot peer `Cache` or a `CacheServer`
application by using the SDG `@EnableLocator` annotation:
.Embedded Locator Service
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication
@EnableLocator
class SpringBootCacheServerWithEmbeddedLocatorApplication {
// ...
}
----
====
However, it is more common to start standalone Locator JVM processes. This is useful when you want to increase the
resiliency of your cluster in the face of network and process failures, which are bound to happen. If a Locator JVM
process crashes or gets severed from the cluster due to a network failure or partition, having multiple Locators
provides a higher degree of availability (HA) through redundancy.
Even if all Locators in the cluster go down, the cluster still remains intact. You cannot add more peer members
(that is, scale-up the number of data nodes in the cluster) or connect any more clients, but the cluster is fine.
If all the locators in the cluster go down, it is safe to restart them only after a thorough diagnosis.
NOTE: Once a client receives metadata about the cluster of servers, all data-access operations are sent directly to
servers in the cluster, not a Locator. Therefore, existing, connected clients remain connected and operable.
To configure and bootstrap Spring Boot {geode-name} Locator applications as standalone JVM processes, use the following
configuration:
.Standalone Locator Process
====
[source,java]
----
@SpringBootApplication
@LocatorApplication
class SpringBootApacheGeodeLocatorApplication {
// ...
}
----
====
Instead of using the `@EnableLocator` annotation, you now use the `@LocatorApplication` annotation.
The `@LocatorApplication` annotation works in the same way as the `@PeerCacheApplication` and `@CacheServerApplication`
annotations, bootstrapping an {geode-name} process and overriding the default `ClientCache` instance provided by SBDG.
NOTE: If your `@SpringBootApplication` class is annotated with `@LocatorApplication`, it must be a `Locator` and not
a `ClientCache`, `CacheServer`, or peer `Cache` application. If you need the application to function as a peer `Cache`,
perhaps with embedded `CacheServer` components and an embedded Locator, you need to follow the approach shown earlier:
using the `@EnableLocator` annotation with either the `@PeerCacheApplication` or `@CacheServerApplication` annotation.
With our Spring Boot {geode-name} Locator application, we can connect both Spring Boot configured and bootstrapped
peer members (peer `Cache`, `CacheServer` and `Locator` applications) as well as Gfsh started Locators and servers.
First, we need to start two Locators by using our Spring Boot {geode-name} Locator application class:
.SpringBootApacheGeodeLocatorApplication class
====
[source,java]
----
include::{docs-src-dir}/org/springframework/geode/docs/example/app/locator/SpringBootApacheGeodeLocatorApplication.java[tags=class]
----
====
We also need to vary the configuration for each Locator application instance.
{geode-name} requires each peer member in the cluster to be uniquely named. We can set the name of the Locator by using
the `spring.data.gemfire.locator.name` SDG property set as a JVM System Property in your IDE's run configuration profile
for the main application class: `-Dspring.data.gemfire.locator.name=SpringLocatorOne`. We name the second Locator
application instance `SpringLocatorTwo`.
Additionally, we must vary the port numbers that the Locators use to listen for connections. By default, an {geode-name}
Locator listens on port `10334`. We can set the Locator port by using the `spring.data.gemfire.locator.port`
SDG property.
For our first Locator application instance (`SpringLocatorOne`), we also enable the "manager" profile so that
we can connect to the Locator by using Gfsh.
Our IDE run configuration profile for our first Locator application instance appears as:
`-server -ea -Dspring.profiles.active=manager -Dspring.data.gemfire.locator.name=SpringLocatorOne -Dlogback.log.level=INFO`
And our IDE run configuration profile for our second Locator application instance appears as:
`-server -ea -Dspring.profiles.active= -Dspring.data.gemfire.locator.name=SpringLocatorTwo -Dspring.data.gemfire.locator.port=11235 -Dlogback.log.level=INFO`
You should see log output similar to the following when you start a Locator application instance:
.Spring Boot {geode-name} Locator log output
====
[source,txt]
----
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.BUILD-SNAPSHOT)
2019-09-01 11:02:48,707 INFO .SpringBootApacheGeodeLocatorApplication: 55 - Starting SpringBootApacheGeodeLocatorApplication on jblum-mbpro-2.local with PID 30077 (/Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/out/production/classes started by jblum in /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build)
2019-09-01 11:02:48,711 INFO .SpringBootApacheGeodeLocatorApplication: 651 - No active profile set, falling back to default profiles: default
2019-09-01 11:02:49,374 INFO xt.annotation.ConfigurationClassEnhancer: 355 - @Bean method LocatorApplicationConfiguration.exclusiveLocatorApplicationBeanFactoryPostProcessor is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.
2019-09-01 11:02:49,919 INFO ode.distributed.internal.InternalLocator: 530 - Starting peer location for Distribution Locator on 10.99.199.24[11235]
2019-09-01 11:02:49,925 INFO ode.distributed.internal.InternalLocator: 498 - Starting Distribution Locator on 10.99.199.24[11235]
2019-09-01 11:02:49,926 INFO distributed.internal.tcpserver.TcpServer: 242 - Locator was created at Sun Sep 01 11:02:49 PDT 2019
2019-09-01 11:02:49,927 INFO distributed.internal.tcpserver.TcpServer: 243 - Listening on port 11235 bound on address 0.0.0.0/0.0.0.0
2019-09-01 11:02:49,928 INFO ternal.membership.gms.locator.GMSLocator: 162 - GemFire peer location service starting. Other locators: localhost[10334] Locators preferred as coordinators: true Network partition detection enabled: true View persistence file: /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build/locator11235view.dat
2019-09-01 11:02:49,928 INFO ternal.membership.gms.locator.GMSLocator: 416 - Peer locator attempting to recover from localhost/127.0.0.1:10334
2019-09-01 11:02:49,963 INFO ternal.membership.gms.locator.GMSLocator: 422 - Peer locator recovered initial membership of View[10.99.199.24(SpringLocatorOne:30043:locator)<ec><v0>:41000|0] members: [10.99.199.24(SpringLocatorOne:30043:locator)<ec><v0>:41000]
2019-09-01 11:02:49,963 INFO ternal.membership.gms.locator.GMSLocator: 407 - Peer locator recovered state from LocatorAddress [socketInetAddress=localhost/127.0.0.1:10334, hostname=localhost, isIpString=false]
2019-09-01 11:02:49,965 INFO ode.distributed.internal.InternalLocator: 644 - Starting distributed system
2019-09-01 11:02:50,007 INFO he.geode.internal.logging.LoggingSession: 82 -
---------------------------------------------------------------------------
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with this
work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
---------------------------------------------------------------------------
Build-Date: 2019-04-19 11:49:13 -0700
Build-Id: onichols 0
Build-Java-Version: 1.8.0_192
Build-Platform: Mac OS X 10.14.4 x86_64
Product-Name: Apache Geode
Product-Version: 1.9.0
Source-Date: 2019-04-19 11:11:31 -0700
Source-Repository: release/1.9.0
Source-Revision: c0a73d1cb84986d432003bd12e70175520e63597
Native version: native code unavailable
Running on: 10.99.199.24/10.99.199.24, 8 cpu(s), x86_64 Mac OS X 10.13.6
Communications version: 100
Process ID: 30077
User: jblum
Current dir: /Users/jblum/pivdev/spring-boot-data-geode/spring-geode-docs/build
Home dir: /Users/jblum
Command Line Parameters:
-ea
-Dspring.profiles.active=
-Dspring.data.gemfire.locator.name=SpringLocatorTwo
-Dspring.data.gemfire.locator.port=11235
-Dlogback.log.level=INFO
-javaagent:/Applications/IntelliJ IDEA 19 CE.app/Contents/lib/idea_rt.jar=51961:/Applications/IntelliJ IDEA 19 CE.app/Contents/bin
-Dfile.encoding=UTF-8
Class Path:
...
..
.
2019-09-01 11:02:54,112 INFO ode.distributed.internal.InternalLocator: 661 - Locator started on 10.99.199.24[11235]
2019-09-01 11:02:54,113 INFO ode.distributed.internal.InternalLocator: 769 - Starting server location for Distribution Locator on 10.99.199.24[11235]
2019-09-01 11:02:54,134 INFO nt.internal.locator.wan.LocatorDiscovery: 138 - Locator discovery task exchanged locator information 10.99.199.24[11235] with localhost[10334]: {-1=[10.99.199.24[10334]]}.
2019-09-01 11:02:54,242 INFO .SpringBootApacheGeodeLocatorApplication: 61 - Started SpringBootApacheGeodeLocatorApplication in 6.137470354 seconds (JVM running for 6.667)
Press <enter> to exit!
----
====
Next, start up the second Locator application instance (you should see log output similar to the preceding list).
Then connect to the cluster of Locators by using Gfsh:
.Cluster of Locators
====
[source,txt]
----
$ echo $GEMFIRE
/Users/jblum/pivdev/apache-geode-1.9.0
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.9.0
Monitor and Manage Apache Geode
gfsh>connect
Connecting to Locator at [host=localhost, port=10334] ..
Connecting to Manager at [host=10.99.199.24, port=1099] ..
Successfully connected to: [host=10.99.199.24, port=1099]
gfsh>list members
Name | Id
---------------- | ------------------------------------------------------------------------
SpringLocatorOne | 10.99.199.24(SpringLocatorOne:30043:locator)<ec><v0>:41000 [Coordinator]
SpringLocatorTwo | 10.99.199.24(SpringLocatorTwo:30077:locator)<ec><v1>:41001
----
====
By using our `SpringBootApacheGeodeCacheServerApplication` main class from the previous section, we can configure
and bootstrap an {geode-name} `CacheServer` application with Spring Boot and connect it to our cluster of Locators:
.SpringBootApacheGeodeCacheServerApplication class
====
[source,java]
----
include::{docs-src-dir}/org/springframework/geode/docs/example/app/server/SpringBootApacheGeodeCacheServerApplication.java[tags=class]
----
====
To do so, enable the "clustered" profile by using an IDE run profile configuration similar to:
`-server -ea -Dspring.profiles.active=clustered -Dspring.data.gemfire.name=SpringServer -Dspring.data.gemfire.cache.server.port=41414 -Dlogback.log.level=INFO`
After the server starts up, you should see the new peer member in the cluster:
.Cluster with Spring Boot configured and bootstrapped {geode-name} `CacheServer`
====
[source,txt]
----
gfsh>list members
Name | Id
---------------- | ------------------------------------------------------------------------
SpringLocatorOne | 10.99.199.24(SpringLocatorOne:30043:locator)<ec><v0>:41000 [Coordinator]
SpringLocatorTwo | 10.99.199.24(SpringLocatorTwo:30077:locator)<ec><v1>:41001
SpringServer | 10.99.199.24(SpringServer:30216)<v2>:41002
----
====
Finally, we can even start additional Locators and servers connected to this cluster by using Gfsh:
.Gfsh started Locators and Servers
====
[source,txt]
----
gfsh>start locator --name=GfshLocator --port=12345 --log-level=config
Starting a Geode Locator in /Users/jblum/pivdev/lab/GfshLocator...
......
Locator in /Users/jblum/pivdev/lab/GfshLocator on 10.99.199.24[12345] as GfshLocator is currently online.
Process ID: 30259
Uptime: 5 seconds
Geode Version: 1.9.0
Java Version: 1.8.0_192
Log File: /Users/jblum/pivdev/lab/GfshLocator/GfshLocator.log
JVM Arguments: -Dgemfire.default.locators=10.99.199.24[11235],10.99.199.24[10334] -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.log-level=config -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.9.0/lib/geode-core-1.9.0.jar:/Users/jblum/pivdev/apache-geode-1.9.0/lib/geode-dependencies.jar
gfsh>start server --name=GfshServer --server-port=45454 --log-level=config
Starting a Geode Server in /Users/jblum/pivdev/lab/GfshServer...
...
Server in /Users/jblum/pivdev/lab/GfshServer on 10.99.199.24[45454] as GfshServer is currently online.
Process ID: 30295
Uptime: 2 seconds
Geode Version: 1.9.0
Java Version: 1.8.0_192
Log File: /Users/jblum/pivdev/lab/GfshServer/GfshServer.log
JVM Arguments: -Dgemfire.default.locators=10.99.199.24[11235],10.99.199.24[12345],10.99.199.24[10334] -Dgemfire.start-dev-rest-api=false -Dgemfire.use-cluster-configuration=true -Dgemfire.log-level=config -XX:OnOutOfMemoryError=kill -KILL %p -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.9.0/lib/geode-core-1.9.0.jar:/Users/jblum/pivdev/apache-geode-1.9.0/lib/geode-dependencies.jar
gfsh>list members
Name | Id
---------------- | ------------------------------------------------------------------------
SpringLocatorOne | 10.99.199.24(SpringLocatorOne:30043:locator)<ec><v0>:41000 [Coordinator]
SpringLocatorTwo | 10.99.199.24(SpringLocatorTwo:30077:locator)<ec><v1>:41001
SpringServer | 10.99.199.24(SpringServer:30216)<v2>:41002
GfshLocator | 10.99.199.24(GfshLocator:30259:locator)<ec><v3>:41003
GfshServer | 10.99.199.24(GfshServer:30295)<v4>:41004
----
====
You must be careful to vary the ports and name of your peer members appropriately. Spring, and Spring Boot
for {geode-name} (SBDG) in particular, make doing so easy.
[[geode-manager-applications]]
=== Building Manager Applications
As discussed in the previous sections, you can enable a Spring Boot configured and bootstrapped {geode-name}
peer member node in the cluster to function as a Manager.
An {geode-name} Manager is a peer member node in the cluster that runs the management service, letting the cluster
be managed and monitored with JMX-based tools, such as Gfsh, JConsole, or JVisualVM. Any tool using the JMX API
can connect to and manage an {geode-name} cluster for whatever purpose.
Like Locators, the cluster may have more than one Manager for redundancy. Only server-side, peer member nodes
in the cluster may function Managers. Therefore, a `ClientCache` application cannot be a Manager.
To create a Manager, use the SDG `@EnableManager` annotation.
The three primary uses of the `@EnableManager` annotation to create a Manager are:
1 - CacheServer Manager Application
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication(name = "CacheServerManagerApplication")
@EnableManager(start = true)
class CacheServerManagerApplication {
// ...
}
----
====
2 - Peer Cache Manager Application
====
[source,java]
----
@SpringBootApplication
@PeerCacheApplication(name = "PeerCacheManagerApplication")
@EnableManager(start = "true")
class PeerCacheManagerApplication {
// ...
}
----
====
3 - Locator Manager Application
====
[source,java]
----
@SpringBootApplication
@LocatorApplication(name = "LocatorManagerApplication")
@EnableManager(start = true)
class LocatorManagerApplication {
// ...
}
----
====
#1 creates a peer `Cache` instance with a `CacheServer` component that accepts client connections along with
an embedded Manager that lets JMX clients connect.
#2 creates only a peer `Cache` instance along with an embedded Manager. As a peer `Cache` with no `CacheServer`
component, clients are not able to connect to this node. It is merely a server managing data.
#3 creates a Locator instance with an embedded Manager.
In all configuration arrangements, the Manager is configured to start immediately.
TIP: See the Javadoc for the
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html[`@EnableManager` annotation]
for additional configuration options.
As of {geode-name} 1.11.0, you must include additional {geode-name} dependencies on your Spring Boot application
classpath to make your application a proper {geode-name} Manager in the cluster, particularly if you also enable
the embedded HTTP service in the Manager.
The required dependencies are:
.Additional Manager dependencies expressed in Gradle
====
[source,groovy]
----
runtime "org.apache.geode:geode-http-service"
runtime "org.apache.geode:geode-web"
runtime "org.springframework.boot:spring-boot-starter-jetty"
----
====
The embedded HTTP service (implemented with the Eclipse Jetty Servlet Container), runs the Management (Admin) REST API,
which is used by {geode-name} tooling, such as Gfsh, to connect to an {geode-name} cluster over HTTP. In addition, it
also enables the {geode-name} {apache-geode-docs}/tools_modules/pulse/pulse-overview.html[Pulse] Monitoring Tool
(and Web application) to run.
Even if you do not start the embedded HTTP service, a Manager still requires the `geode-http-service`, `geode-web`
and `spring-boot-starter-jetty` dependencies.
Optionally, you may also include the `geode-pulse` dependency, as follows:
.Additional, optional Manager depdendencies expressed in Gradle
====
[source,groovy]
----
runtime "org.apache.geode:geode-pulse"
----
====
The `geode-pulse` dependency is only required if you want the Manager to automatically start
the {geode-name} {apache-geode-docs}/tools_modules/pulse/pulse-overview.html[Pulse] Monitoring Tool. Pulse enables you
to view the nodes of your {geode-name} cluster and monitor them in realtime.

View File

@@ -0,0 +1,833 @@
[[cloudfoundry]]
== Pivotal CloudFoundry
:geode-name: {apache-geode-name}
:images-dir: ./images
:pcc-name: Pivotal Cloud Cache
:pcf-name: Pivotal CloudFoundry
NOTE: As of the VMware, Inc. acquisition of Pivotal Software, Inc., {pcf-name} (PCF) is now known as VMware Tanzu
Application Service (TAS) for VMs. Also, {pcc-name} (PCC) has been rebranded as VMware Tanzu GemFire for VMS.
This documentation will eventually be updated to reflect the rebranding.
In most cases, when you deploy (that is, `cf push`) your Spring Boot applications to {pcf-name} (PCF), you bind
your application to one or more instances of the {pcc-name} (PCC) service.
In a nutshell, {pivotal-cloudcache-website}[{pcc-name}] (PCC) is a managed version of
{pivotal-gemfire-website}[{pivotal-gemfire-name}] that runs in {pivotal-cloudfoundry-website}[{pcf-name}] (PCF).
When running in or across cloud environments (such as AWS, Azure, GCP, or PWS), PCC with PCF offers several advantages
over trying to run and manage your own standalone {geode-name} clusters. It handles many of the infrastructure-related,
operational concerns so that you need not do so.
[[cloudfoundry-cloudcache-security-auth-runtime-user-configuration]]
=== Running a Spring Boot application as a specific user
By default, Spring Boot applications run as a `cluster_operator` role-based user in {pcf-name} when the application
is bound to a {pcc-name} service instance.
A `cluster_operator` has full system privileges (that is, authorization) to do whatever that user wishes to involving
the PCC service instance. A `cluster_operator` has read and write access to all the data, can modify the schema (for
example, create and destroy Regions, add and remove Indexes, change eviction or expiration policies, and so on), start
and stop servers in the PCC cluster, or even modify permissions.
.About cluster_operator as the default user
****
One of the reasons why Spring Boot applications default to running as a `cluster_operator` is to allow configuration
metadata to be sent from the client to the server. Enabling configuration metadata to be sent from the client to the
server is a useful development-time feature and is as simple as annotating your main `@SpringBootApplication` class
with the `@EnableClusterConfiguration` annotation:
.Using `@EnableClusterConfiguration`
====
[source,java]
----
@SpringBootApplication
@EnableClusterConfiguration(useHttp = true)
class SpringBootApacheGeodeClientCacheApplication { }
----
====
With `@EnableClusterConfiguration`, Region and OQL Index configuration metadata that is defined on the client can be
sent to servers in the PCC cluster. {geode-name} requires matching Regions by name on both the client and the servers
in order for clients to send and receive data to and from the cluster.
For example, when you declare the Region where an application entity is persisted by using the `@Region` mapping
annotation and declare the `@EnableEntityDefinedRegions` annotation on the main `@SpringBootApplication` class
in conjunction with the `@EnableClusterConfiguration` annotation, not only does SBDG create the required client Region,
but it also sends the configuration metadata for this Region to the servers in the cluster to create the matching,
required server Region, where the data for your application entity is managed.
****
However...
> With great power comes great responsibility. - Uncle Ben
Not all Spring Boot applications using PCC need to change the schema or even modify data. Rather, certain applications
may need only read access. Therefore, it is ideal to be able to configure your Spring Boot applications to run with
a different user at runtime other than the auto-configured `cluster_operator`, by default.
A prerequisite for running a Spring Boot application in PCC with a specific user is to create a user with restricted
permissions by using {pcf-name} AppsManager while provisioning the PCC service instance to which the Spring Boot
application is bound.
Configuration metadata for the PCC service instance might appear as follows:
.{pcc-name} configuration metadata
====
[source,json]
----
{
"p-cloudcache":[{
"credentials": {
"distributed_system_id": "0",
"locators": [ "localhost[55221]" ],
"urls": {
"gfsh": "https://cloudcache-12345.services.cf.pws.com/gemfire/v1",
"pulse": "https://cloudcache-12345.services.cf.pws.com/pulse"
},
"users": [{
"password": "*****",
"roles": [ "cluster_operator" ],
"username": "cluster_operator_user"
}, {
"password": "*****",
"roles": [ "developer" ],
"username": "developer_user"
}, {
"password": "*****",
"roles": [ "read-only-user" ],
"username": "guest"
}],
"wan": {
"sender_credentials": {
"active": {
"password": "*****",
"username": "gateway-sender-user"
}
}
}
},
"name": "jblum-pcc",
"plan": "small",
"tags": [ "gemfire", "cloudcache", "database", "pivotal" ]
}]
}
----
====
In the PCC service instance configuration metadata shown in the preceding example, we see a `guest` user with
the `read-only-user` role. If the `read-only-user` role is properly configured with read-only permissions as the name
implies, we could configure our Spring Boot application to run as `guest` with read-only access:
.Configuring a Spring Boot application to run as a specific user
====
[source,properties]
----
# Spring Boot application.properties for PCF when using PCC
spring.data.gemfire.security.username=guest
----
====
TIP: The `spring.data.gemfire.security.username` property corresponds directly to the SDG `@EnableSecurity` annotation's
`securityUsername` attribute. See the
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#securityUsername--[Javadoc]
for more details.
The `spring.data.gemfire.security.username` property is the same property used by Spring Data for {geode-name} (SDG) to
configure the runtime user of your Spring Data application when you connect to an externally managed {geode-name}
cluster.
In this case, SBDG uses the configured username to look up the authentication credentials of the user to set
the username and password used by the Spring Boot `ClientCache` application when connecting to PCC while running in PCF.
If the username is not valid, an `IllegalStateException` is thrown.
By using {spring-boot-docs-html}/#boot-features-profiles[Spring profiles], it would be a simple matter to configure
the Spring Boot application to run with a different user depending on environment.
See the {pcc-name} documentation on {pivotal-cloudcache-docs}/security.html[security] for configuring users with
assigned roles and permissions.
[[cloudfoundry-cloudcache-security-auth-autoconfiguration-override]]
==== Overriding Authentication Auto-configuration
It should be understood that auto-configuration for client authentication is available only for managed environments,
such as {pcf-name}. When running in externally managed environments, you must explicitly set a username and password
to authenticate, as described in <<geode-security-auth-clients-non-managed>>.
To completely override the auto-configuration of client authentication, you can set both a username and a password:
.Overriding Security Authentication Auto-configuration with explicit username and password
====
[source,txt]
----
# Spring Boot application.properties
spring.data.gemfire.security.username=MyUser
spring.data.gemfire.security.password=MyPassword
----
====
In this case, SBDG's auto-configuration for authentication is effectively disabled and security credentials are not
extracted from the environment.
[[cloudfoundry-cloudcache-serviceinstance-targeting]]
=== Targeting Specific {pcc-name} Service Instances
It is possible to provision multiple instances of the {pcc-name} service in your {pcf-name} environment. You can then
bind multiple PCC service instances to your Spring Boot application.
However, Spring Boot for {geode-name} (SBDG) only auto-configures one PCC service instance for your Spring Boot
application. This does not mean that it is not possible to use multiple PCC service instances with your Spring Boot
application, just that SBDG only auto-configures one service instance for you.
You must select which PCC service instance your Spring Boot application automatically auto-configures for you when
you have multiple instances and want to target a specific PCC service instance to use.
To do so, declare the following SBDG property in Spring Boot `application.properties`:
.Spring Boot application.properties targeting a specific PCC service instance by name
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cloud.cloudfoundry.service.cloudcache.name=pccServiceInstanceTwo
----
====
The `spring.boot.data.gemfire.cloud.cloudfoundry.service.cloudcache.name` property tells SBDG which PCC service instance
to auto-configure.
If the PCC service instance identified by the property does not exist, SBDG throws an `IllegalStateException`
stating the PCC service instance by name could not be found.
If you did not set the property and your Spring Boot application is bound to multiple PCC service instances,
SBDG auto-configures the first PCC service instance it finds by name, alphabetically.
If you did not set the property and no PCC service instance is found, SBDG logs a warning.
[[cloudfoundry-cloudcache-multiinstance-using]]
=== Using Multiple {pcc-name} Service Instances
If you want to use multiple PCC service instances with your Spring Boot application, you need to configure multiple
connection `Pools` connected to each PCC service instance used by your Spring Boot application.
The configuration would be similar to the following:
.Multiple {pcc-name} Service Instance Configuration
====
[source,java]
----
@Configuration
@EnablePools(pools = {
@EnablePool(name = "PccOne"),
@EnablePool(name = "PccTwo"),
...,
@EnablePool(name = "PccN")
})
class PccConfiguration {
// ...
}
----
====
You would then externalize the configuration for the individually declared `Pools` in Spring Boot
`application.properties`:
.Configuring Locator-based Pool connections
====
[source,properties]
----
# Spring Boot `application.properties`
spring.data.gemfire.pool.pccone.locators=pccOneHost1[port1], pccOneHost2[port2], ..., pccOneHostN[portN]
spring.data.gemfire.pool.pcctwo.locators=pccTwoHost1[port1], pccTwoHost2[port2], ..., pccTwoHostN[portN]
----
====
NOTE: Though less common, you can also configure the `Pool` of connections to target specific servers in the cluster
by setting the `spring.data.gemfire.pool.<named-pool>.severs` property.
TIP: Keep in mind that properties in Spring Boot `application.properties` can refer to other properties:
`property=$\{otherProperty}`. This lets you further externalize properties by using Java System properties
or environment variables.
A client Region is then assigned the Pool of connections that are used to send data to and from the specific
PCC service instance (cluster):
.Assigning a Pool to a client Region
====
[source,java]
----
@Configuration
class GeodeConfiguration {
@Bean("Example")
ClientRegionFactoryBean exampleRegion(GemFireCache gemfireCache,
@Qualifier("PccTwo") Pool poolForPccTwo) {
ClientRegionFactoryBean exampleRegion = new ClientRegionFactoryBean();
exampleRegion.setCache(gemfireCache);
exampleRegion.setPool(poolForPccTwo);
exampleRegion.setShortcut(ClientRegionShortcut.PROXY);
return exampleRegion;
}
}
----
====
You can configure as many Pools and client Regions as your application needs. Again, the `Pool` determines
the {pcc-name} service instance and cluster in which the data for the client Region resides.
NOTE: By default, SBDG configures all `Pools` declared in a Spring Boot `ClientCache` application to connect to
and use a single PCC service instance. This may be a targeted PCC service instance when you use the
`spring.boot.data.gemfire.cloud.cloudfoundry.service.cloudcache.name` property
as discussed <<cloudfoundry-cloudcache-multiinstance-using,earlier>>.
[[cloudfoundry-geode]]
=== Hybrid {pcf-name} and {geode-name} Spring Boot Applications
Sometimes, it is desirable to deploy (that is, `cf push`) and run your Spring Boot applications in {pcf-name}
but still connect your Spring Boot applications to an externally managed, standalone {geode-name} cluster.
Spring Boot for {geode-name} (SBDG) makes this a non-event and honors its "_little to no code or configuration changes
necessary_" goal. Regardless of your runtime choice, it should just work!
To help guide you through this process, we cover the following topics:
. Install and Run PCFDev.
. Start an {geode-name} cluster.
. Create a User-Provided Service (CUPS).
. Push and Bind a Spring Boot application.
. Run the Spring Boot application.
[[cloudfoundry-geode-pcfdev]]
==== Running PCFDev
For this exercise, we use https://docs.pivotal.io/pcf-dev/install-osx.html[PCF Dev].
PCF Dev, much like PCF, is an elastic application runtime for deploying, running, and managing your Spring Boot
applications. However, it does so in the confines of your local development environment -- that is, your workstation.
Additionally, PCF Dev provides several services, such as MySQL, Redis, and RabbitMQ. You Spring Boot application
can bind to and use these services to accomplish its tasks.
However, PCF Dev lacks the {pcc-name} service that is available in PCF. This is actually ideal for this exercise since
we are trying to build and run Spring Boot applications in a PCF environment but connect to an externally managed,
standalone {geode-name} cluster.
As a prerequisite, you need to follow the steps outlined in the
https://pivotal.io/platform/pcf-tutorials/getting-started-with-pivotal-cloud-foundry-dev/introduction[tutorial]
to get PCF Dev set up and running on your workstation.
To run PCF Dev, execute the following `cf` CLI command, replacing the path to the TGZ file with the file you acquired
from the https://network.pivotal.io/products/pcfdev[download]:
.Start PCF Dev
====
[source,txt]
----
$ cf dev start -f ~/Downloads/Pivotal/CloudFoundry/Dev/pcfdev-v1.2.0-darwin.tgz
----
====
You should see output similar to the following:
.Running PCF Dev
====
[source,txt]
----
Downloading Network Helper...
Progress: |====================>| 100.0%
Installing cfdevd network helper (requires administrator privileges)...
Password:
Setting up IP aliases for the BOSH Director & CF Router (requires administrator privileges)
Downloading Resources...
Progress: |====================>| 100.0%
Setting State...
WARNING: PCF Dev requires 8192 MB of RAM to run. This machine may not have enough free RAM.
Creating the VM...
Starting VPNKit...
Waiting for the VM...
Deploying the BOSH Director...
Deploying PAS...
Done (14m34s)
Deploying Apps-Manager...
Done (1m41s)
██████╗ ██████╗███████╗██████╗ ███████╗██╗ ██╗
██╔══██╗██╔════╝██╔════╝██╔══██╗██╔════╝██║ ██║
██████╔╝██║ █████╗ ██║ ██║█████╗ ██║ ██║
██╔═══╝ ██║ ██╔══╝ ██║ ██║██╔══╝ ╚██╗ ██╔╝
██║ ╚██████╗██║ ██████╔╝███████╗ ╚████╔╝
╚═╝ ╚═════╝╚═╝ ╚═════╝ ╚══════╝ ╚═══╝
is now running!
To begin using PCF Dev, please run:
cf login -a https://api.dev.cfdev.sh --skip-ssl-validation
Admin user => Email: admin / Password: admin
Regular user => Email: user / Password: pass
To access Apps Manager, navigate here: https://apps.dev.cfdev.sh
To deploy a particular service, please run:
cf dev deploy-service <service-name> [Available services: mysql,redis,rabbitmq,scs]
----
====
To use the `cf` CLI tool, you must login to the PCF Dev environment:
.Login to PCF Dev using `cf` CLI
====
[source,txt]
----
$ cf login -a https://api.dev.cfdev.sh --skip-ssl-validation
----
====
You can also access the https://apps.dev.cfdev.sh/[PCF Dev Apps Manager] tool from your Web browser at the following URL:
https://apps.dev.cfdev.sh/
Apps Manager provides a nice UI to manage your org, space, services and apps. It lets you push and update apps,
create services, bind apps to the services, and start and stop your deployed applications, among many other things.
[[cloudfoundry-geode-cluster]]
==== Running an {geode-name} Cluster
Now that PCF Dev is set up and running, you need to start an external, standalone {geode-name} cluster to which our
Spring Boot application connects and uses to manage its data.
You need to install a {apache-geode-website}/releases/[distribution] of {geode-name} on your computer. Then you must
set the `$GEODE` environment variable. It is also convenient to add `$GEODE/bin` to your system `$PATH`.
Afterward, you can launch the Geode Shell (_Gfsh_) tool:
.Running Gfsh
====
[source,txt]
----
$ echo $GEODE
/Users/jblum/pivdev/apache-geode-1.6.0
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.6.0
Monitor and Manage Apache Geode
gfsh>
----
====
We have provided the Gfsh shell script that you can use to start the {geode-name} cluster:
.Gfsh shell script to start the {geode-name} cluster
====
[source,txt]
----
include::{docs-resources-dir}/geode/bin/start-cluster.gfsh[]
----
====
The `start-cluster.gfsh` shell script starts one Geode Locator and one Geode server.
A Locator is used by clients to discover and connect to servers in a cluster to manage its data. A Locator is also used
by new servers that join a cluster as peer members, which lets the cluster be elastically scaled out (or scaled down,
as needed). A Geode server stores the data for the application.
You can start as many Locators or servers as necessary to meet the availability and load demands of your application.
The more Locators and servers your cluster has, the more resilient it is to failure. However, you should size your
cluster accordingly, based on your application's needs, since there is overhead relative to the cluster size.
You see output similar to the following when starting the Locator and server:
.Starting the {geode-name} cluster
====
[source,txt]
----
gfsh>start locator --name=LocatorOne --log-level=config --classpath=/Users/jblum/pivdev/spring-boot-data-geode/apache-geode-extensions/build/libs/apache-geode-extensions-1.1.0.BUILD-SNAPSHOT.jar --J=-Dgemfire.security-manager=org.springframework.geode.security.TestSecurityManager --J=-Dgemfire.http-service-port=8080
Starting a Geode Locator in /Users/jblum/pivdev/lab/LocatorOne...
..
Locator in /Users/jblum/pivdev/lab/LocatorOne on 10.99.199.24[10334] as LocatorOne is currently online.
Process ID: 14358
Uptime: 1 minute 1 second
Geode Version: 1.6.0
Java Version: 1.8.0_192
Log File: /Users/jblum/pivdev/lab/LocatorOne/LocatorOne.log
JVM Arguments: -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.log-level=config -Dgemfire.security-manager=org.springframework.geode.security.TestSecurityManager -Dgemfire.http-service-port=8080 -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.6.0/lib/geode-core-1.6.0.jar:/Users/jblum/pivdev/spring-boot-data-geode/apache-geode-extensions/build/libs/apache-geode-extensions-1.1.0.BUILD-SNAPSHOT.jar:/Users/jblum/pivdev/apache-geode-1.6.0/lib/geode-dependencies.jar
Security Manager is enabled - unable to auto-connect. Please use "connect --locator=10.99.199.24[10334] --user --password" to connect Gfsh to the locator.
Authentication required to connect to the Manager.
gfsh>connect
Connecting to Locator at [host=localhost, port=10334] ..
Connecting to Manager at [host=10.99.199.24, port=1099] ..
user: admin
password: *****
Successfully connected to: [host=10.99.199.24, port=1099]
gfsh>start server --name=ServerOne --log-level=config --user=admin --password=admin --classpath=/Users/jblum/pivdev/spring-boot-data-geode/apache-geode-extensions/build/libs/apache-geode-extensions-1.1.0.BUILD-SNAPSHOT.jar
Starting a Geode Server in /Users/jblum/pivdev/lab/ServerOne...
....
Server in /Users/jblum/pivdev/lab/ServerOne on 10.99.199.24[40404] as ServerOne is currently online.
Process ID: 14401
Uptime: 3 seconds
Geode Version: 1.6.0
Java Version: 1.8.0_192
Log File: /Users/jblum/pivdev/lab/ServerOne/ServerOne.log
JVM Arguments: -Dgemfire.default.locators=10.99.199.24[10334] -Dgemfire.security-username=admin -Dgemfire.start-dev-rest-api=false -Dgemfire.security-password=******** -Dgemfire.use-cluster-configuration=true -Dgemfire.log-level=config -XX:OnOutOfMemoryError=kill -KILL %p -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/jblum/pivdev/apache-geode-1.6.0/lib/geode-core-1.6.0.jar:/Users/jblum/pivdev/spring-boot-data-geode/apache-geode-extensions/build/libs/apache-geode-extensions-1.1.0.BUILD-SNAPSHOT.jar:/Users/jblum/pivdev/apache-geode-1.6.0/lib/geode-dependencies.jar
----
====
Once the cluster has been started successfully, you can list the members:
.List members of the cluster
====
[source,txt]
----
gfsh>list members
Name | Id
---------- | -----------------------------------------------------------------
LocatorOne | 10.99.199.24(LocatorOne:14358:locator)<ec><v0>:1024 [Coordinator]
ServerOne | 10.99.199.24(ServerOne:14401)<v1>:1025
----
====
Currently, we have not defined any Regions in which to store our application's data:
.No Application Regions
====
[source,txt]
----
gfsh>list regions
No Regions Found
----
====
This is deliberate, since we are going to let the application drive its schema structure, both on the client
(application) as well as on the server-side (cluster). We cover this in more detail later in this chapter.
[[cloudfoundry-geode-cups]]
==== Creating a User-Provided Service
Now that we have PCF Dev and a small {geode-name} cluster up and running, it is time to create a user-provided service
to the external, standalone {geode-name} cluster that we started in <<cloudfoundry-geode-cluster,step 2>>.
As mentioned, PCF Dev offers MySQL, Redis and RabbitMQ services (among others). However, to use {geode-name} in the same
capacity as you would {pcc-name} when running in a production-grade PCF environment, you need to create a user-provided
service for the standalone {geode-name} cluster.
To do so, run the following `cf` CLI command:
.cf cups command
====
[source,txt]
----
$ cf cups <service-name> -t "gemfire, cloudcache, database, pivotal" -p '<service-credentials-in-json>'
----
====
NOTE: It is important that you specify the tags (`gemfire`, `cloudcache`, `database`, `pivotal`) exactly as shown
in the preceding `cf` CLI command.
The argument passed to the `-p` command-line option is a JSON document (object) containing the credentials for our
user-provided service.
The JSON object is as follows:
.User-Provided Service Crendentials JSON
====
[source,json]
----
{
"locators": [ "<hostname>[<port>]" ],
"urls": { "gfsh": "https://<hostname>/gemfire/v1" },
"users": [{ "password": "<password>", "roles": [ "cluster_operator" ], "username": "<username>" }]
}
----
====
The complete `cf` CLI command would be similar to the following:
.Example `cf cups` command
====
[source,txt]
----
cf cups apacheGeodeService -t "gemfire, cloudcache, database, pivotal" \
-p '{ "locators": [ "10.99.199.24[10334]" ], "urls": { "gfsh": "https://10.99.199.24/gemfire/v1" }, "users": [{ "password": "admin", "roles": [ "cluster_operator" ], "username": "admin" }] }'
----
====
We replaced the `<hostname>` placeholder with the IP address of our standalone {geode-name} Locator. You can find
the IP address in the Gfsh `start locator` command output shown in the preceding example.
Additionally, the `<port>` placeholder has been replaced with the default Locator port, `10334`,
Finally, we set the `username` and `password` accordingly.
TIP: Spring Boot for {geode-name} (SBDG) provides template files in the `{docs-dir}/src/main/resources` directory.
Once the service has been created, you can query the details of the service from the `cf` CLI:
.Query the CF Dev Services
====
[source,txt]
----
$ cf services
Getting services in org cfdev-org / space cfdev-space as admin...
name service plan bound apps last operation broker
apacheGeodeService user-provided boot-pcc-demo
$ cf service apacheGeodeService
Showing info of service apacheGeodeService in org cfdev-org / space cfdev-space as admin...
name: apacheGeodeService
service: user-provided
tags: gemfire, cloudcache, database, pivotal
bound apps:
name binding name status message
boot-pcc-demo create succeeded
----
====
You can also view the "apacheGeodeService" from Apps Manager, starting from the `Service` tab in your org and space:
image::{images-dir}/pcfdev-appsmanager-org-space-services.png[]
By clicking on the "apacheGeodeService" service entry in the table, you can get all the service details, such as
the bound apps:
image::{images-dir}/pcfdev-appsmanager-org-space-service-boundapps.png[]
You can also view and set the configuration:
image::{images-dir}/pcfdev-appsmanager-org-space-service-configuration.png[]
This brief section did not cover all the capabilities of the Apps Manager. We suggest you explore its UI to see all
that is possible.
TIP: You can learn more about CUPS in the
{pivotal-cloudfoundry-docs}/devguide/services/user-provided.html[PCF documentation].
[[cloudfoundry-geode-app]]
==== Push and Bind a Spring Boot application
Now it is time to push a Spring Boot application to PCF Dev and bind the application to the `apacheGeodeService`.
Any Spring Boot `ClientCache` application that uses SBDG works for this purpose. For this example, we use the
https://github.com/jxblum/PCCDemo/tree/sbdg-doc-ref[PCCDemo] application, which is available in GitHub.
After cloning the project to your computer, you must run a build to produce the artifact to push to PCF Dev:
.Build the PCCDemo application
====
[source,txt]
----
$ mvn clean package
----
====
Then you can push the application to PCF Dev with the following `cf` CLI command:
.Push the application to PCF Dev
====
[source,txt]
----
$ cf push boot-pcc-demo -u none --no-start -p target/client-0.0.1-SNAPSHOT.jar
----
====
Once the application has been successfully deployed to PCF Dev, you can get the application details:
.Get details for the deployed application
====
[source,txt]
----
$ cf apps
Getting apps in org cfdev-org / space cfdev-space as admin...
OK
name requested state instances memory disk urls
boot-pcc-demo stopped 0/1 768M 1G boot-pcc-demo.dev.cfdev.sh
$ cf app boot-pcc-demo
Showing health and status for app boot-pcc-demo in org cfdev-org / space cfdev-space as admin...
name: boot-pcc-demo
requested state: stopped
routes: boot-pcc-demo.dev.cfdev.sh
last uploaded: Tue 02 Jul 00:34:09 PDT 2019
stack: cflinuxfs3
buildpacks: https://github.com/cloudfoundry/java-buildpack.git
type: web
instances: 0/1
memory usage: 768M
state since cpu memory disk details
#0 down 2019-07-02T21:48:25Z 0.0% 0 of 0 0 of 0
type: task
instances: 0/0
memory usage: 256M
There are no running instances of this process.
----
====
You can bind the PPCDemo application to the `apacheGeodeService` using the `cf` CLI command:
.Bind application to `apacheGeodeService` using CLI
====
[source,txt]
----
cf bind-service boot-pcc-demo apacheGeodeService
----
====
Alternatively, you can create a YAML file (`manifest.yml` in `src/main/resources`) that contains
the deployment descriptor:
.Example YAML deployment descriptor
====
[source,yml]
----
\---
applications:
- name: boot-pcc-demo
memory: 768M
instances: 1
path: ./target/client-0.0.1-SNAPSHOT.jar
services:
- apacheGeodeService
buildpacks:
- https://github.com/cloudfoundry/java-buildpack.git
----
====
You can also use Apps Manager to view application details and bind and unbind additional services.
Start by navigating to the `App` tab under your org and space:
image::{images-dir}/pcfdev-appsmanager-org-space-apps.png[]
From there, you can click on the desired application and navigate to the `Overview`:
image::{images-dir}/pcfdev-appsmanager-org-space-app-overview.png[]
You can also review the application `Settings`. Specifically, we are looking at the configuration of the applicatinon
once it is bound to the `apacheGeodeService`, as seen in the `VCAP_SERVICES` environment variable:
image::{images-dir}/pcfdev-appsmanager-org-space-app-settings-envvars.png[]
This JSON document structure is not unlike the configuration used to bind your Spring Boot `ClientCache` application
to the {pcc-name} service when deploying the same application to {pcf-name}. This is actually key if you want to
minimize the amount of boilerplate code and configuration changes when you migrate between different CloudFoundry
environments, even https://www.cloudfoundry.org/[Open Source CloudFoundry].
Again, SBDG's goal is to simply the effort for you to build, run, and manage your application, in whatever context
your application lands, even if it changes later. If you follow the steps in this documentation, you can realize
that goal.
[[cloudfoundry-geode-app-run]]
==== Running the Spring Boot application
All that is left to do now is run the application.
You can start the PCCDemo application from the `cf` CLI by using the following command:
.Start the Spring Boot application
====
[source,txt]
----
$ cf start boot-pcc-demo
----
====
Alternatively, you can also start the application from Apps Manager. This is convenient, since you can then tail
and monitor the application log file.
image::{images-dir}/pcfdev-appsmanager-org-space-app-logs.png[]
Once the application has started, you can click the https://boot-pcc-demo.dev.cfdev.sh/[VIEW APP] link
in the upper right corner of the `APP` screen.
image::{images-dir}/PCCDemo-app-screenshot.png[]
You can navigate to any of the application Web Service, Controller endpoints. For example, if you know the ISBN
of a book, you can access it from your Web browser:
image::{images-dir}/PCCDemo-app-book-by-isbn-screenshot.png[]
You can also access the same data from the Gfsh command-line tool. However, the first thing to observe is that our
application informed the cluster that it needed a Region called `Books`:
.Books Region
====
[source,txt]
----
gfsh>list regions
List of regions
---------------
Books
gfsh>describe region --name=/Books
..........................................................
Name : Books
Data Policy : partition
Hosting Members : ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 1
| data-policy | PARTITION
----
====
The PCCDemo app creates fake data on startup, which we can query in Gfsh:
.Query Books
====
[source,txt]
----
gfsh>query --query="SELECT book.isbn, book.title FROM /Books book"
Result : true
Limit : 100
Rows : 1
isbn | title
------------- | ---------------------
1235432BMF342 | The Torment of Others
----
====
[[cloudfoundry-geode-summary]]
=== Summary
The ability to deploy Spring Boot, {geode-name} `ClientCache` applications to {pcf-name} yet connect your application to
an externally managed, standalone {geode-name} cluster is powerful.
Indeed, this is a useful arrangement and stepping stone for many users as they begin their journey towards Cloud-Native
platforms such as {pcf-name} and using services such as {pcc-name}.
Later, when you need to work with real (rather than sample) applications, you can migrate your Spring Boot applications
to a fully managed and production-grade {pcf-name} environment, and SBDG figures out what to do, leaving you to focus
entirely on your application.

View File

@@ -0,0 +1,407 @@
[[geode-auto-configuration-annotations]]
== Auto-configuration vs. Annotation-based configuration
:geode-name: {apache-geode-name}
:vmw-gemfire-name: {pivotal-gemfire-name}
:vmw-tas-name: {pivotal-cloudfoundry-name}
The question most often asked is, "`What Spring Data for {geode-name} (SDG) annotations can I use, or must I use, when
developing {geode-name} applications with Spring Boot?`"
This section answers this question and more.
See the complementary sample, link:guides/boot-configuration.html[Spring Boot Auto-configuration for {geode-name}],
which shows the auto-configuration provided by Spring Boot for {geode-name} in action.
[[geode-autoconfiguration-annotations-background]]
=== Background
To help answer this question, you must start by reviewing the complete collection of available Spring Data for
{geode-name} (SDG) annotations. These annotations are provided in the
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/package-summary.html[`org.springframework.data.gemfire.config.annotation`]
package. Most of the essential annotations begin with `@Enable...`, except for the base annotations:
`@ClientCacheApplication`, `@PeerCacheApplication` and `@CacheServerApplication`.
By extension, Spring Boot for {geode-name} (SBDG) builds on SDG's annotation-based configuration model to implement
auto-configuration and apply Spring Boot's core concepts, such as "`convention over configuration`", letting
{geode-name} applications be built with Spring Boot reliably, quickly, and easily.
SDG provides this annotation-based configuration model to, first and foremost, give application developers "`choice`"
when building Spring applications with {geode-name}. SDG makes no assumptions about what application developers are
trying to create and fails fast anytime the configuration is ambiguous, giving users immediate feedback.
Second, SDG's annotations were meant to get application developers up and running quickly and reliably with ease. SDG
accomplishes this by applying sensible defaults so that application developers need not know, or even have to learn, all
the intricate configuration details and tooling provided by {geode-name} to accomplish simple tasks, such as building a
prototype.
So, SDG is all about "`choice`" and SBDG is all about "`convention`". Together these frameworks provide application
developers with convenience and ease to move quickly and reliably.
To learn more about the motivation behind SDG's annotation-based configuration model, see the
{spring-data-gemfire-docs-html}/#bootstrap-annotation-config-introduction[Reference Documentation].
[[geode-autoconfiguration-annotations-conventions]]
=== Conventions
Currently, SBDG provides auto-configuration for the following features:
* `ClientCache`
* Caching with Spring's Cache Abstraction
* Continuous Query
* Function Execution and Implementation
* Logging
* PDX
* `GemfireTemplate`
* Spring Data Repositories
* Security (Client/server auth and SSL)
* Spring Session
This means the following SDG annotations are not required to use the features above:
* `@ClientCacheApplication`
* `@EnableGemfireCaching` (or by using Spring Framework's `@EnableCaching` annotation)
* `@EnableContinuousQueries`
* `@EnableGemfireFunctionExecutions`
* `@EnableGemfireFunctions`
* `@EnableLogging`
* `@EnablePdx`
* `@EnableGemfireRepositories`
* `@EnableSecurity`
* `@EnableSsl`
* `@EnableGemFireHttpSession`
Since SBDG auto-configures these features for you, the above annotations are not strictly required. Typically, you
would only declare one of these annotations when you want to "`override`" Spring Boot's conventions, as expressed in
auto-configuration, and "`customize`" the behavior of the feature.
[[geode-autoconfiguration-annotations-overriding]]
=== Overriding
In this section, we cover a few examples to make the behavior when overriding more apparent.
[[geode-autoconfiguration-annotations-overriding-caches]]
==== Caches
By default, SBDG provides you with a `ClientCache` instance. SBDG accomplishes this by annotating an auto-configuration
class with `@ClientCacheApplication` internally.
By convention, we assume most application developers' are developing Spring Boot applications by using {geode-name}
as "`client`" applications in {geode-name}'s client/server topology. This is especially true as users migrate their
applications to a managed cloud environment.
Still, you can "`override`" the default settings (convention) and declare your Spring applications to be actual peer
`Cache` members (nodes) of a {geode-name} cluster, instead:
.Spring Boot, {geode-name} Peer `Cache` Application
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication
class SpringBootApacheGeodePeerCacheServerApplication { }
----
====
By declaring the `@CacheServerApplication` annotation, you effectively override the SBDG default. Therefore, SBDG
does not provide you with a `ClientCache` instance by default, because you have informed SBDG of exactly what you want:
a peer `Cache` instance hosting an embedded `CacheServer` that allows client connections.
However, you then might ask, "`Well, how do I customize the `ClientCache` instance when developing client applications
without explicitly declaring the `@ClientCacheApplication` annotation?`"
First, you can "`customize`" the `ClientCache` instance by explicitly declaring the `@ClientCacheApplication` annotation
in your Spring Boot application configuration and setting specific attributes as needed. However, you should be aware
that, by explicitly declaring this annotation, (or, by default, any of the other auto-configured annotations), you
assume all the responsibility that comes with it, since you have effectively overridden the auto-configuration. One
example of this is security, which we touch on more later.
The most ideal way to "`customize`" the configuration of any feature is by way of the well-known and documented
<<geode-configuration-metadata,properties>>, specified in Spring Boot `application.properties` (the "`convention`"),
or by using a {spring-data-gemfire-docs-html}/#bootstrap-annotation-config-configurers[`Configurer`].
See the <<geode-clientcache-applications,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-overriding-security]]
==== Security
As with the `@ClientCacheApplication` annotation, the `@EnableSecurity` annotation is not strictly required, unless you
want to override and customize the defaults.
Outside a managed environment, the only security configuration required is specifying a username and password. You do
this by using the well-known and documented SDG username and password properties in Spring Boot `application.properties`:
.Required Security Properties in a Non-Manage Envionment
====
[source,properties]
----
spring.data.gemfire.security.username=MyUser
spring.data.gemfire.security.password=Secret
----
====
You need not explicitly declare the `@EnableSecurity` annotation just to specify security configuration (such as
username and password).
Inside a managed environment, such as the {vmw-tas-name} when using {vmw-gemfire-name}, SBDG is able to introspect
the environment and configure security (auth) completely without the need to specify any configuration, usernames
and passwords, or otherwise. This is due, in part, because TAS supplies the security details in the VCAP environment
when the application is deployed to TAS and bound to services (such as {vmw-gemfire-name}).
So, in short, you need not explicitly declare the `@EnableSecurity` annotation (or `@ClientCacheApplication`).
However, if you do explicitly declare the `@ClientCacheApplication` or `@EnableSecurity` annotations, you are now
responsible for this configuration, and SBDG's auto-configuration no longer applies.
While explicitly declaring `@EnableSecurity` makes more sense when "`overriding`" the SBDG security auto-configuration,
explicitly declaring the `@ClientCacheApplication` annotation most likely makes less sense with regard to its impact
on security configuration.
This is entirely due to the internals of {geode-name}, because, in certain cases (such as security), not even Spring
is able to completely shield you from the nuances of {geode-name}'s configuration. No framework can.
You must configure both auth and SSL before the cache instance (whether a `ClientCache` or a peer `Cache`) is created.
This is because security is enabled and configured during the "`construction`" of the cache. Also,, the cache pulls
the configuration from JVM System properties that must be set before the cache is constructed.
Structuring the "`exact`" order of the auto-configuration classes provided by SBDG when the classes are triggered, is no
small feat. Therefore, it should come as no surprise to learn that the security auto-configuration classes in SBDG must
be triggered before the `ClientCache` auto-configuration class, which is why a `ClientCache` instance cannot "`auto`"
authenticate properly in PCC when the `@ClientCacheApplication` is explicitly declared without some assistance. In other
words you must also explicitly declare the `@EnableSecurity` annotation in this case, since you overrode the
auto-configuration of the cache, and implicitly security, as well.
Again, this is due to the way security (auth) and SSL metadata must be supplied to {geode-name} on startup.
See the <<geode-security,Reference Guide>> for more details.
[[geode-autoconfiguration-annotations-extension]]
=== Extension
Most of the time, many of the other auto-configured annotations for CQ, Functions, PDX, Repositories, and so on need not
ever be declared explicitly.
Many of these features are enabled automatically by having SBDG or other libraries (such as Spring Session) on
the application classpath or are enabled based on other annotations applied to beans in the Spring `ApplicationContext`.
We review a few examples in the following sections.
[[geode-autoconfiguration-annotations-extension-caching]]
==== Caching
It is rarely, if ever, necessary to explicitly declare either the Spring Framework's `@EnableCaching` or the
SDG-specific `@EnableGemfireCaching` annotation in Spring configuration when you use SBDG. SBDG automatically
enables caching and configures the SDG `GemfireCacheManager` for you.
You need only focus on which application service components are appropriate for caching:
.Service Caching
====
[source,java]
----
@Service
class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Cacheable("CustomersByName")
public Customer findBy(String name) {
return customerRepository.findByName(name);
}
}
----
====
You need to create {geode-name} Regions that back the caches declared in your application service components
(`CustomersByName` in the preceding example) by using Spring's caching annotations (such as `@Cacheable`),
or alternatively, JSR-107 JCache annotations (such as `@CacheResult`).
You can do that by defining each Region explicitly or, more conveniently, you can use the following approach:
.Configuring Caches (Regions)
====
[source,java]
----
@SpringBootApplication
@EnableCachingDefinedRegions
class Application { }
----
====
`@EnableCachingDefinedRegions` is optional, provided for convenience, and complementary to caching when used rather than
being necessary.
See the <<geode-caching-provider,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-extension-cq]]
==== Continuous Query
It is rarely, if ever, necessary to explicitly declare the SDG `@EnableContinuousQueries` annotation. Instead, you
should focus on defining your application queries and worry less about the plumbing.
Consider the following example:
.Defining Queries for CQ
====
[source,java]
----
@Component
public class TemperatureMonitor extends AbstractTemperatureEventPublisher {
@ContinuousQuery(name = "BoilingTemperatureMonitor",
query = "SELECT * FROM /TemperatureReadings WHERE temperature.measurement >= 212.0")
public void boilingTemperatureReadings(CqEvent event) {
publish(event, temperatureReading -> new BoilingTemperatureEvent(this, temperatureReading));
}
@ContinuousQuery(name = "FreezingTemperatureMonitor",
query = "SELECT * FROM /TemperatureReadings WHERE temperature.measurement <= 32.0")
public void freezingTemperatureReadings(CqEvent event) {
publish(event, temperatureReading -> new FreezingTemperatureEvent(this, temperatureReading));
}
}
----
====
{geode-name} CQ applies only to clients.
See the <<geode-continuous-query,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-extension-functions]]
==== Functions
You rarely, if ever, need to explicitly declare either the `@EnableGemfireFunctionExecutions`
or `@EnableGemfireFunctions` annotations. SBDG provides auto-configuration for both Function implementations
and executions.
You need to define the implementation:
.Function Implementation
====
[source,java]
----
@Component
class GeodeFunctions {
@GemfireFunction
Object exampleFunction(Object arg) {
// ...
}
}
----
====
Then you need to define the execution:
.Function Execution
====
[source,java]
----
@OnRegion(region = "Example")
interface GeodeFunctionExecutions {
Object exampleFunction(Object arg);
}
----
====
SBDG automatically finds, configures, and registers Function implementations (POJOs) in {geode-name} as proper
`Functions` and creates execution proxies for the interfaces, which can then be injected into application service
components to invoke the registered `Functions` without needing to explicitly declare the enabling annotations.
The application Function implementations (POJOs) and executions (interfaces) should exist below
the `@SpringBootApplication` annotated main class.
See the <<geode-functions,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-extension-pdx]]
==== PDX
You rarely, if ever, need to explicitly declare the `@EnablePdx` annotation, since SBDG auto-configures PDX by default.
SBDG also automatically configures the SDG `MappingPdxSerializer` as the default `PdxSerializer`.
It is easy to customize the PDX configuration by setting the appropriate <<geode-configuration-metadata,properties>>
(search for "`PDX`") in Spring Boot `application.properties`.
See the <<geode-data-serialization,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-extension-repositories]]
==== Spring Data Repositories
You rarely, if ever, need to explicitly declare the `@EnableGemfireRepositories` annotation, since SBDG auto-configures
Spring Data (SD) Repositories by default.
You need only define your Repositories:
.Customer's Repository
====
[source,java]
----
interface CustomerRepository extends CrudRepository<Customer, Long> {
Customer findByName(String name);
}
----
====
SBDG finds the Repository interfaces defined in your application, proxies them, and registers them as beans in
the Spring `ApplicationContext`. The Repositories can be injected into other application service components.
It is sometimes convenient to use the `@EnableEntityDefinedRegions` along with Spring Data Repositories to identify
the entities used by your application and define the Regions used by the Spring Data Repository infrastructure to
persist the entity's state. The `@EnableEntityDefinedRegions` annotation is optional, provided for convenience,
and complementary to the `@EnableGemfireRepositories` annotation.
See the <<geode-repositories,Reference Guide>> for more detail.
[[geode-autoconfiguration-annotations-explicit]]
=== Explicit Configuration
Most of the other annotations provided in SDG are focused on particular application concerns or enable certain
{geode-name} features, rather than being a necessity, including:
* `@EnableAutoRegionLookup`
* `@EnableBeanFactoryLocator`
* `@EnableCacheServer(s)`
* `@EnableCachingDefinedRegions`
* `@EnableClusterConfiguration`
* `@EnableClusterDefinedRegions`
* `@EnableCompression`
* `@EnableDiskStore(s)`
* `@EnableEntityDefinedRegions`
* `@EnableEviction`
* `@EnableExpiration`
* `@EnableGatewayReceiver`
* `@EnableGatewaySender(s)`
* `@EnableGemFireAsLastResource`
* `@EnableHttpService`
* `@EnableIndexing`
* `@EnableOffHeap`
* `@EnableLocator`
* `@EnableManager`
* `@EnableMemcachedServer`
* `@EnablePool(s)`
* `@EnableRedisServer`
* `@EnableStatistics`
* `@UseGemFireProperties`
None of these annotations are necessary and none are auto-configured by SBDG. They are at your disposal when and if you
need them. This also means that none of these annotations are in conflict with any SBDG auto-configuration.
[[geode-autoconfiguration-annotations-summary]]
=== Summary
In conclusion, you need to understand where SDG ends and SBDG begins. It all begins with the auto-configuration
provided by SBDG.
If a feature or function is not covered by SBDG's auto-configuration, you are responsible for enabling and configuring
the feature appropriately, as needed by your application (for example, `@EnableRedisServer`).
In other cases, you might also want to explicitly declare a complimentary annotation
(such as `@EnableEntityDefinedRegions`) for convenience, since SBDG provides no convention or opinion.
In all remaining cases, it boils down to understanding how {geode-name} works under the hood. While we go to great
lengths to shield you from as many details as possible, it is not feasible or practical to address all matters, such as
cache creation and security.

View File

@@ -0,0 +1,696 @@
[[geode-configuration-auto]]
== Auto-configuration
:geode-name: {apache-geode-name}
The following Spring Framework, Spring Data for {geode-name} (SDG) and Spring Session for {geode-name} (SSDG)
annotations are implicitly declared by Spring Boot for {geode-name}'s (SBDG) auto-configuration.
* `@ClientCacheApplication`
* `@EnableGemfireCaching` (alternatively, Spring Framework's `@EnableCaching`)
* `@EnableContinuousQueries`
* `@EnableGemfireFunctions`
* `@EnableGemfireFunctionExecutions`
* `@EnableGemfireRepositories`
* `@EnableLogging`
* `@EnablePdx`
* `@EnableSecurity`
* `@EnableSsl`
* `@EnableGemFireHttpSession`
NOTE: This means that you need not explicitly declare any of these annotations on your `@SpringBootApplication` class,
since they are provided by SBDG already. The only reason you would explicitly declare any of these annotations is to
override Spring Boot's, and in particular, SBDG's auto-configuration. Otherwise, doing so is unnecessary.
TIP: You should read the chapter in Spring Boot's reference documentation on
{spring-boot-docs-html}/#using-boot-auto-configuration[auto-configuration].
TIP: You should review the chapter in Spring Data for {geode-name}'s (SDG) reference documentation
on {spring-data-geode-docs-html}/#bootstrap-annotation-config[annotation-based configuration]. For a quick reference
and overview of annotation-based configuration, see the
{spring-data-geode-docs-html}/#bootstap-annotations-quickstart[annotations quickstart].
TIP: See the corresponding sample link:guides/boot-configuration.html[guide] and {github-samples-url}/boot/configuration[code]
to see Spring Boot auto-configuration for {geode-name} in action.
[[geode-configuration-auto-customizing]]
=== Customizing Auto-configuration
You might ask, "`How do I customize the auto-configuration provided by SBDG if I do not explicitly declare
the annotation?`"
For example, you may want to customize the member's name. You know that the
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html[`@ClientCacheApplication`] annotation
provides the {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableGemFireProperties.html#name--[`name`] attribute
so that you can set the client member's name. However, SBDG has already implicitly declared the `@ClientCacheApplication`
annotation through auto-configuration on your behalf. What do you do?
In this case, SBDG supplies a few additional annotations.
For example, to set the (client or peer) member's name, you can use the `@UseMemberName` annotation:
.Setting the member's name using `@UseMemberName`
====
[source,java]
----
@SpringBootApplication
@UseMemberName("MyMemberName")
class SpringBootApacheGeodeClientCacheApplication {
//...
}
----
====
Alternatively, you could set the `spring.application.name` or the `spring.data.gemfire.name` property in Spring Boot
`application.properties`:
.Setting the member's name using the `spring.application.name` property
====
[source,txt]
----
# Spring Boot application.properties
spring.application.name = MyMemberName
----
====
.Setting the member's name using the `spring.data.gemfire.cache.name` property
====
[source,txt]
----
# Spring Boot application.properties
spring.data.gemfire.cache.name = MyMemberName
----
====
NOTE: The `spring.data.gemfire.cache.name` property is an alias for the `spring.data.gemfire.name` property. Both
properties do the same thing (set the name of the client or peer member node).
In general, there are three ways to customize configuration, even in the context of SBDG's auto-configuration:
* Using {spring-boot-data-geode-javadoc}/org/springframework/geode/config/annotation/package-summary.html[annotations]
provided by SBDG for common and popular concerns (such as naming client or peer members with the `@UseMemberName`
annotation or enabling durable clients with the `@EnableDurableClient` annotation).
* Using well-known and documented {spring-data-geode-docs-html}/#bootstrap-annotation-config-properties[properties]
(such as `spring.application.name`, or `spring.data.gemfire.name`, or `spring.data.gemfire.cache.name`).
* Using {spring-data-geode-docs-html}/#bootstrap-annotation-config-configurers[configurers]
(such as {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurer.html[`ClientCacheConfigurer`]).
TIP: For the complete list of documented properties, see <<geode-configuration-metadata>>.
[[geode-configuration-auto-disabling]]
=== Disabling Auto-configuration
Spring Boot's reference documentation explains how to
{spring-boot-docs-html}/#using-boot-disabling-specific-auto-configuration[disable Spring Boot auto-configuration].
<<geode-auto-configuration-disable>> also explains how to disable SBDG auto-configuration.
In a nutshell, if you want to disable any auto-configuration provided by either Spring Boot or SBDG, declare your intent
in the `@SpringBootApplication` annotation:
.Disabling Specific Auto-configuration Classes
====
[source,java]
----
@SpringBootApplication(
exclude = { DataSourceAutoConfiguration.class, PdxAutoConfiguration.class }
)
class SpringBootApacheGeodeClientCacheApplication {
// ...
}
----
====
CAUTION: Make sure you understand what you are doing when you disable auto-configuration.
[[geode-configuration-auto-overriding]]
=== Overriding Auto-configuration
<<geode-autoconfiguration-annotations-overriding>> explains how to override SBDG auto-configuration.
In a nutshell, if you want to override the default auto-configuration provided by SBDG, you must annotate your
`@SpringBootApplication` class with your intent.
For example, suppose you want to configure and bootstrap an {geode-name} `CacheServer` application
(a peer, not a client):
.Overriding the default `ClientCache` _Auto-Configuration_ by configuring & bootstrapping a `CacheServer` application
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication
class SpringBootApacheGeodeCacheServerApplication {
// ...
}
----
====
You can also explicitly declare the `@ClientCacheApplication` annotation on your `@SpringBootApplication` class:
.Overriding by explicitly declaring `@ClientCacheApplication`
====
[source,java]
----
@SpringBootApplication
@ClientCacheApplication
class SpringBootApacheGeodeClientCacheApplication {
// ...
}
----
====
You are overriding SBDG's auto-configuration of the `ClientCache` instance. As a result, you have now also implicitly
consented to being responsible for other aspects of the configuration (such as security).
Why does that happen?
It happens because, in certain cases, such as security, certain aspects of security configuration (such as SSL) must be
configured before the cache instance is created. Also, Spring Boot always applies user configuration before
auto-configuration partially to determine what needs to be auto-configured in the first place.
CAUTION: Make sure you understand what you are doing when you override auto-configuration.
[[geode-configuration-auto-replacing]]
=== Replacing Auto-configuration
See the Spring Boot reference documentation on
{spring-boot-docs-html}/#using-boot-replacing-auto-configuration[replacing auto-configuration].
[[geode-configuration-auto-explained]]
=== Understanding Auto-configuration
This section covers the SBDG provided auto-configuration classes that correspond to the SDG annotations in more detail.
To review the complete list of SBDG auto-confiugration classes, see <<geode-auto-configuration-disable-classes>>.
[[geode-configuration-declarative-auto-configuration-clientcacheapplication]]
==== `@ClientCacheApplication`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/ClientCacheAutoConfiguration.html[`ClientCacheAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html[`@ClientCacheApplication`] annotation.
As explained in <<getting-started>> SBDG starts with the opinion that application developers primarily build {geode-name}
<<geode-clientcache-applications,client applications>> by using Spring Boot.
Technically, this means building Spring Boot applications with an {geode-name} `ClientCache` instance connected to
a dedicated cluster of {geode-name} servers that manage the data as part of a
{apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[client/server] topology.
By way of example, this means that you need not explicitly declare and annotate your `@SpringBootApplication` class
with SDG's `@ClientCacheApplication` annotation, as the following example shows:
.Do Not Do This
====
[source,java]
----
@SpringBootApplication
@ClientCacheApplication
class SpringBootApacheGeodeClientCacheApplication {
// ...
}
----
====
SBDG's provided auto-configuration class is already meta-annotated with SDG's `@ClientCacheApplication` annotation.
Therefore, you need only do:
.Do This
====
[source,java]
----
@SpringBootApplication
class SpringBootApacheGeodeClientCacheApplication {
// ...
}
----
====
TIP: See SDG's reference documentation for more details on {geode-name}
{spring-data-geode-docs-html}/#bootstrap-annotation-config-geode-applications[cache applications]
and {spring-data-geode-docs-html}/#bootstrap-annotation-config-client-server-applications[client/server applications]
in particular.
[[geode-configuration-declarative-auto-configuration-enablecaching]]
==== `@EnableGemfireCaching`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/CachingProviderAutoConfiguration.html[`CachingProviderAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/cache/config/EnableGemfireCaching.html[`@EnableGemfireCaching`] annotation.
If you used the core Spring Framework to configure {geode-name} as a caching provider in
{spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction], you need to:
.Configuring caching using the Spring Framework
====
[source,java]
----
@SpringBootApplication
@EnableCaching
class CachingUsingApacheGeodeConfiguration {
@Bean
GemfireCacheManager cacheManager(GemFireCache cache) {
GemfireCacheManager cacheManager = new GemfireCacheManager();
cacheManager.setCache(cache);
return cacheManager;
}
}
----
====
If you use Spring Data for {geode-name}'s `@EnableGemfireCaching` annotation, you can simplify the preceding
configuration:
.Configuring caching using Spring Data for {geode-name}
====
[source,java]
----
@SpringBootApplication
@EnableGemfireCaching
class CachingUsingApacheGeodeConfiguration {
}
----
====
Also, if you use SBDG, you need only do:
.Configuring caching using Spring Boot for {geode-name}
====
[source,java]
----
@SpringBootApplication
class CachingUsingApacheGeodeConfiguration {
}
----
====
This lets you focus on the areas in your application that would benefit from caching without having to enable the
plumbing. You can then demarcate the service methods in your application that are good candidates for caching:
.Using caching in your application
====
[source,java]
----
@Service
class CustomerService {
@Caching("CustomersByName")
Customer findBy(String name) {
// ...
}
}
----
====
TIP: See <<geode-caching-provider,documentation on caching>> for more details.
[[geode-configuration-declarative-auto-configuration-enableautocontinuousqueies]]
==== `@EnableContinuousQueries`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/ContinuousQueryAutoConfiguration.html[`ContinuousQueryAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableContinuousQueries.html[`@EnableContinuousQueries`] annotation.
Without having to enable anything, you can annotate your application (POJO) component method(s) with the SDG
{spring-data-geode-javadoc}/org/springframework/data/gemfire/listener/annotation/ContinuousQuery.html[`@ContinuousQuery`]
annotation to register a CQ and start receiving events. The method acts as a `CqEvent` handler or, in {geode-name}'s
terminology, the method is an implementation of the
{apache-geode-javadoc}/org/apache/geode/cache/query/CqListener.html[`CqListener`] interface.
.Declare application CQs
====
[source,java]
----
@Component
class MyCustomerApplicationContinuousQueries {
@ContinuousQuery("SELECT customer.* "
+ " FROM /Customers customers"
+ " WHERE customer.getSentiment().name().equalsIgnoreCase('UNHAPPY')")
public void handleUnhappyCustomers(CqEvent event) {
// ...
}
}
----
====
As the preceding example shows, you can define the events you are interested in receiving by using an OQL query with a
finely tuned query predicate that describes the events of interests and implements the handler method to process the
events (such as applying a credit to the customer's account and following up in email).
TIP: See <<geode-continuous-query>> for more details.
[[geode-configuration-declarative-auto-configuration-enablefunctions]]
==== `@EnableGemfireFunctionExecutions` & `@EnableGemfireFunctions`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/FunctionExecutionAutoConfiguration.html[`FunctionExecutionAutoConfiguration`] class
corresponds to both the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/function/config/EnableGemfireFunctionExecutions.html[`@EnableGemfireFunctionExecutions`]
and SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/function/config/EnableGemfireFunctions.html[`@EnableGemfireFunctions`] annotations.
Whether you need to {spring-data-geode-docs-html}/#function-execution[execute]
or {spring-data-geode-docs-html}/#function-implementation[implement] a `Function`, SBDG detects the Function definition
and auto-configures it appropriately for use in your Spring Boot application. You need only define the Function
execution or implementation in a package below the main `@SpringBootApplication` class:
.Declare a Function Execution
====
[source,java]
----
package example.app.functions;
@OnRegion("Accounts")
interface MyCustomerApplicationFunctions {
void applyCredit(Customer customer);
}
----
====
Then you can inject the Function execution into any application component and use it:
.Use the Function
====
[source,java]
----
package example.app.service;
@Service
class CustomerService {
@Autowired
private MyCustomerApplicationFunctions customerFunctions;
void analyzeCustomerSentiment(Customer customer) {
// ...
this.customerFunctions.applyCredit(customer);
// ...
}
}
----
====
The same pattern basically applies to Function implementations, except in the implementation case, SBDG registers
the Function implementation for use (that is, to be called by a Function execution).
Doing so lets you focus on defining the logic required by your application and not worry about how Functions
are registered, called, and so on. SBDG handles this concern for you.
NOTE: Function implementations are typically defined and registered on the server-side.
TIP: See <<geode-functions>> for more details.
[[geode-configuration-declarative-auto-configuration-enablerepositories]]
==== `@EnableGemfireRepositories`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/GemFireRepositoriesAutoConfigurationRegistrar.html[`GemFireRepositoriesAutoConfigurationRegistrar`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/repository/config/EnableGemfireRepositories.html[`@EnableGemfireRepositories`] annotation.
As with Functions, you need concern yourself only with the data access operations (such as basic CRUD and simple queries)
required by your application to carry out its operation, not with how to create and perform them (for example,
`Region.get(key)` and `Region.put(key, obj)`) or execute them (for example, `Query.execute(arguments)`).
Start by defining your Spring Data Repository:
.Define an application-specific Repository
====
[source,java]
----
package example.app.repo;
interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findBySentimentEqualTo(Sentiment sentiment);
}
----
====
Then you can inject the Repository into an application component and use it:
.Using the application-specific Repository
====
[source,java]
----
package example.app.sevice;
@Service
class CustomerService {
@Autowired
private CustomerRepository repository;
public void processCustomersWithSentiment(Sentiment sentiment) {
this.repository.findBySentimentEqualTo(sentiment)
.forEach(customer -> { /* ... */ });
// ...
}
}
----
====
Your application-specific Repository simply needs to be declared in a package below the main `@SpringBootApplication`
class. Again, you are focusing only on the data access operations and queries required to carry out the operatinons
of your application, nothing more.
TIP: See <<geode-repositories>> for more details.
[[geode-configuration-declarative-auto-configuration-enablelogging]]
==== `@EnableLogging`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/LoggingAutoConfiguration.html[`LoggingAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html[`@EnableLogging`] annotation.
Logging is an essential application concern to understand what is happening in the system along with when and where
the events occurred. By default, SBDG auto-configures logging for {geode-name} with the default log-level, "`config`".
You can change any aspect of logging, such as the log-level, in Spring Boot `application.properties`:
.Change the log-level for {geode-name}
====
[source,txt]
----
# Spring Boot application.properites.
spring.data.gemfire.cache.log-level=debug
----
====
NOTE: The 'spring.data.gemfire.logging.level' property is an alias for `spring.data.gemfire.cache.log-level`.
You can also configure other aspects, such as the log file size and disk space limits for the filesystem location used
to store the {geode-name} log files at runtime.
Under the hood, {geode-name}'s logging is based on Log4j. Therefore, you can configure {geode-name} logging to use any
logging provider (such as Logback) and configuration metadata appropriate for that logging provider so long as you
supply the necessary adapter between Log4j and whatever logging system you use. For instance, if you include
`org.springframework.boot:spring-boot-starter-logging`, you are using Logback and you will need the
`org.apache.logging.log4j:log4j-to-slf4j` adapter.
[[geode-configuration-declarative-auto-configuration-enablepdx]]
==== `@EnablePdx`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/PdxSerializationAutoConfiguration.html[`PdxSerializationAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html[`@EnablePdx`] annotation.
Any time you need to send an object over the network or overflow or persist an object to disk, your application domain
model object must be serializable. It would be painful to have to implement `java.io.Serializable` in every one of your
application domain model objects (such as `Customer`) that would potentially need to be serialized.
Furthermore, using Java Serialization may not be ideal (it may not be the most portable or efficient solution) in all
cases or even possible in other cases (such as when you use a third party library over which you have no control).
In these situations, you need to be able to send your object anywhere, anytime without unduly requiring the class type
to be serializable and exist on the classpath in every place it is sent. Indeed, the final destination may not even be
a Java application. This is where {geode-name}
{apache-geode-docs}/developing/data_serialization/gemfire_pdx_serialization.html[PDX Serialization] steps in to help.
However, you need not figure out how to configure PDX to identify the application class types that needs to be
serialized. Instead, you can define your class type as follows:
.Customer class
====
[source,java]
----
@Region("Customers")
class Customer {
@Id
private Long id;
@Indexed
private String name;
// ...
}
----
====
SBDG's auto-configuration handles the rest.
TIP: See <<geode-data-serialization>> for more details.
[[geode-configuration-declarative-auto-configuration-enablesecurity]]
==== `@EnableSecurity`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/ClientSecurityAutoConfiguration.html[`ClientSecurityAutoConfiguration`] class
and {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/PeerSecurityAutoConfiguration.html[`PeerSecurityAutoConfiguration`] class
correspond to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html[`@EnableSecurity`] annotation,
but they apply security (specifically, authentication and authorization (auth) configuration) for both clients
and servers.
Configuring your Spring Boot, {geode-name} `ClientCache` application to properly authenticate with a cluster of secure
{geode-name} servers is as simple as setting a username and a password in Spring Boot `application.properties`:
.Supplying Authentication Credentials
====
[source,txt]
----
# Spring Boot application.properties
spring.data.gemfire.security.username=Batman
spring.data.gemfire.security.password=r0b!n5ucks
----
====
NOTE: Authentication is even easier to configure in a managed environment, such as PCF when using PCC. You need not do
anything.
Authorization is configured on the server-side and is made simple with SBDG and the help of
https://shiro.apache.org/[Apache Shiro].
Of course, this assumes you use SBDG to configure and bootstrap your {geode-name} cluster in the first place, which is
even easier with SBDG. See <<geode-cluster-configuration-bootstrapping>>.
TIP: See <<geode-security>> for more details.
[[geode-configuration-declarative-auto-configuration-enablessl]]
==== `@EnableSsl`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/SslAutoConfiguration.html[`SslAutoConfiguration`] class
corresponds to the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html[`@EnableSsl`] annotation.
Configuring SSL for secure transport (TLS) between your Spring Boot, {geode-name} `ClientCache` application and an
{geode-name} cluster can be a real problem, especially to get right from the start. So, it is something that SBDG
makes as simple as possible.
You can supply a `trusted.keystore` file containing the certificates in a well-known location (such as the root of your
application classpath), and SBDG's auto-configuration steps in to handle the rest.
This is useful during development, but we highly recommend using a more secure procedure (such as integrating with a
secure credential store like LDAP, CredHub or Vault) when deploying your Spring Boot application to production.
TIP: See <<geode-security-ssl>> for more details.
[[geode-configuration-declarative-auto-configuration-enablespringsession]]
==== `@EnableGemFireHttpSession`
NOTE: The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/SpringSessionAutoConfiguration.html[`SpringSessionAutoConfiguration`] class
corresponds to the SSDG {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/EnableGemFireHttpSession.html[`@EnableGemFireHttpSession`] annotation.
Configuring {geode-name} to serve as the (HTTP) session state caching provider by using Spring Session requires that
you only include the correct starter, that is `spring-geode-starter-session`:
.Using Spring Session
====
[subs="verbatim,attributes"]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter-session</artifactId>
<version>{revnumber}</version>
</dependency>
----
====
With Spring Session -- and specifically Spring Session for {geode-name} (SSDG) -- on the classpath of your Spring Boot,
{geode-name} `ClientCache` Web application, you can manage your (HTTP) session state with {geode-name}. No further
configuration is needed. SBDG auto-configuration detects Spring Session on the application classpath and does the rest.
TIP: See <<geode-session>> for more details.
[[geode-configuration-declarative-auto-configuration-regiontemplates]]
==== RegionTemplateAutoConfiguration
The SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/RegionTemplateAutoConfiguration.html[`RegionTemplateAutoConfiguration`] class
has no corresponding SDG annotation. However, the auto-configuration of a `GemfireTemplate` for every {geode-name}
`Region` defined and declared in your Spring Boot application is still supplied by SBDG.
For example, you can define a Region by using:
.Region definition using JavaConfig
====
[source,java]
----
@Configuration
class GeodeConfiguration {
@Bean("Customers")
ClientRegionFactoryBean<Long, Customer> customersRegion(GemFireCache cache) {
ClientRegionFactoryBean<Long, Customer> customersRegion =
new ClientRegionFactoryBean<>();
customersRegion.setCache(cache);
customersRegion.setShortcut(ClientRegionShortcut.PROXY);
return customersRegion;
}
}
----
====
Alternatively, you can define the `Customers` Region by using `@EnableEntityDefinedRegions`:
.Region definition using `@EnableEntityDefinedRegions`
====
[source,java]
----
@Configuration
@EnableEntityDefinedRegion(basePackageClasses = Customer.class)
class GeodeConfiguration {
}
----
====
Then SBDG supplies a `GemfireTemplate` instance that you can use to perform low-level data-access operations
(indirectly) on the `Customers` Region:
.Use the `GemfireTemplate` to access the "Customers" Region
====
[source,java]
----
@Repository
class CustomersDao {
@Autowired
@Qualifier("customersTemplate")
private GemfireTemplate customersTemplate;
Customer findById(Long id) {
return this.customerTemplate.get(id);
}
}
----
====
You need not explicitly configure `GemfireTemplates` for each Region to which you need low-level data access (such as
when you are not using the Spring Data Repository abstraction).
Be careful to qualify the `GemfireTemplate` for the Region to which you need data access, especially given that you
probably have more than one Region defined in your Spring Boot application.
TIP: See <<geode-data-access-region-templates>> for more details.

View File

@@ -0,0 +1,760 @@
[[geode-configuration-declarative]]
== Declarative Configuration
:geode-name: {apache-geode-name}
The primary purpose of any software development framework is to help you be productive as quickly and as easily as
possible and to do so in a reliable manner.
As application developers, we want a framework to provide constructs that are both intuitive and familiar so that their
behaviors are predictable. This provided convenience not only helps you hit the ground running in the right direction
sooner but increases your focus on the application domain so that you can better understand the problem you are trying
to solve in the first place. Once the problem domain is well understood, you are more apt to make informed decisions
about the design, which leads to better outcomes, faster.
This is exactly what Spring Boot's auto-configuration provides for you. It enables features, functionality, services
and supporting infrastructure for Spring applications in a loosely integrated way by using conventions (such as the
classpath) that ultimately help you keep your attention and focus on solving the problem at hand and not on the plumbing.
For example, if you are building a web application, you can include the `org.springframework.boot:spring-boot-starter-web`
dependency on your application classpath. Not only does Spring Boot enable you to build Spring Web MVC Controllers
appropriate to your application UC (your responsibility), but it also bootstraps your web application in an embedded
Servlet container on startup (Spring Boot's responsibility).
This saves you from having to handle many low-level, repetitive, and tedious development tasks that are error-prone and
easy to get wrong when you are trying to solve problems. You need not care how the plumbing works until you need to
customize something. And, when you do, you are better informed and prepared to do so.
It is also equally essential that frameworks, such as Spring Boot, get out of the way quickly when application
requirements diverge from the provided defaults. This is the beautiful and powerful thing about Spring Boot and why
it is second to none in its class.
Still, auto-configuration does not solve every problem all the time. Therefore, you need to use declarative
configuration in some cases, whether expressed as bean definitions, in properties, or by some other means. This is so
that frameworks do not leave things to chance, especially when things are ambiguous. The framework gives you choice.
Keeping our goals in mind, this chapter:
* Refers you to the SDG annotations covered by SBDG's auto-configuration.
* Lists all SDG annotations not covered by SBDG's auto-configuration.
* Covers the SBDG, SSDG and SDG annotations that you must explicitly declare and that provide the most value
and productivity when getting started with {geode-name} in Spring [Boot] applications.
NOTE: SDG refers to {spring-data-geode-website}[Spring Data for {geode-name}]. SSDG refers to
{spring-session-data-gemfire-website}[Spring Session for {geode-name}]. SBDG refers to
Spring Boot for {geode-name} (this project).
TIP: The list of SDG annotations covered by SBDG's auto-configuration is discussed in detail in the <<appendix>>,
in the <<geode-auto-configuration-annotations,Auto-configuration vs. Annotation-based configuration>> section.
To be absolutely clear about which SDG annotations we are referring to, we mean the SDG annotations in the
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/package-summary.html[`org.springframework.data.gemfire.config.annotation`]
package.
In subsequent sections, we also cover which annotations are added by SBDG.
[[geode-configuration-declarative-auto-configuration]]
=== Auto-configuration
We explained auto-configuration in detail in the <<geode-configuration-auto,Auto-configuration>> chapter.
[[geode-configuration-declarative-annotations]]
=== Annotations Not Covered by Auto-configuration
The following SDG annotations are not implicitly applied by SBDG's auto-configuration:
* `@EnableAutoRegionLookup`
* `@EnableBeanFactoryLocator`
* `@EnableCacheServer(s)`
* `@EnableCachingDefinedRegions`
* `@EnableClusterConfiguration`
* `@EnableClusterDefinedRegions`
* `@EnableCompression`
* `@EnableDiskStore(s)`
* `@EnableEntityDefinedRegions`
* `@EnableEviction`
* `@EnableExpiration`
* `@EnableGatewayReceiver`
* `@EnableGatewaySender(s)`
* `@EnableGemFireAsLastResource`
* `@EnableGemFireMockObjects`
* `@EnableHttpService`
* `@EnableIndexing`
* `@EnableOffHeap`
* `@EnableLocator`
* `@EnableManager`
* `@EnableMemcachedServer`
* `@EnablePool(s)`
* `@EnableRedisServer`
* `@EnableStatistics`
* `@UseGemFireProperties`
NOTE: This content was also covered in <<geode-autoconfiguration-annotations-explicit>>.
One reason SBDG does not provide auto-configuration for several of the annotations is because the annotations
are server-specific:
* `@EnableCacheServer(s)`
* `@EnableGatewayReceiver`
* `@EnableGatewaySender(s)`.
* `@EnableHttpService`
* `@EnableLocator`
* `@EnableManager`
* `@EnableMemcachedServer`
* `@EnableRedisServer`
Also, we <<geode-clientcache-applications,already stated>> that SBDG is opinionated about providing a `ClientCache`
instance.
Other annotations are driven by need, including:
* `@EnableAutoRegionLookup` and `@EnableBeanFactoryLocator`: Really useful only when mixing configuration metadata
formats, such as Spring config with {geode-name} `cache.xml`. This is usually the case only if you have legacy
`cache.xml` config to begin with. Otherwise, you should not use these annotations.
* `@EnableCompression`: Requires the Snappy Compression Library to be on your application classpath.
* `@EnableDiskStore(s)` Used only for overflow and persistence.
* `@EnableOffHeap`: Enables data to be stored in main memory, which is useful only when your application data (that is,
objects stored in {geode-name}) are generally uniform in size.
* `@EnableGemFireAsLastResource`: Needed only in the context of JTA Transactions.
* `@EnableStatistics`: Useful if you need runtime metrics. However, enabling statistics gathering does consume
considerable system resources (CPU & Memory).
Still other annotations require more careful planning:
* `@EnableEviction`
* `@EnableExpiration`
* `@EnableIndexing`
One annotation is used exclusively for unit testing:
* `@EnableGemFireMockObjects`
The bottom-line is that a framework should not auto-configure every possible feature, especially when the features
consume additional system resources or require more careful planning (as determined by the use case).
However, all of these annotations are available for the application developer to use when needed.
[[geode-configuration-declarative-annotations-productivity]]
=== Productivity Annotations
This section calls out the annotations we believe to be most beneficial for your application development purposes when
using {geode-name} in Spring [Boot] applications.
[[geode-configuration-declarative-annotations-productivity-enableclusteraware]]
==== `@EnableClusterAware` (SBDG)
The `@EnableClusterAware` annotation is arguably the most powerful and valuable annotation.
.Declaring `@EnableClusterAware`
====
[source,java]
----
@SpringBootApplication
@EnableClusterAware
class SpringBootApacheGeodeClientCacheApplication { }
----
====
When you annotate your main `@SpringBootApplication` class with `@EnableClusterAware`, your Spring Boot, {geode-name}
`ClientCache` application is able to seamlessly switch between client/server and local-only topologies with no code
or configuration changes, regardless of the runtime environment (such as local/standalone versus cloud-managed
environments).
When a cluster of {geode-name} servers is detected, the client application sends and receives data to and from the
{geode-name} cluster. If a cluster is not available, the client automatically switches to storing data locally on the
client by using `LOCAL` Regions.
Additionally, the `@EnableClusterAware` annotation is meta-annotated with SDG's
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html[`@EnableClusterConfiguration`]
annotation.
The `@EnableClusterConfiguration` annotation lets configuration metadata defined on the client (such as Region and Index
definitions, as needed by the application based on requirements and use cases) be sent to the cluster of servers. If
those schema objects are not already present, they are created by the servers in the cluster in such a way that the
servers remember the configuration on restart as well as provide the configuration to new servers that join the cluster
when it is scaled out. This feature is careful not to stomp on any existing Region or Index objects already defined on
the servers, particularly since you may already have critical data stored in the Regions.
The primary motivation for the `@EnableClusterAware` annotation is to let you switch environments with minimal effort.
It is a common development practice to debug and test your application locally (in your IDE) and then push up to a
production-like (staging) environment for more rigorous integration testing.
By default, the configuration metadata is sent to the cluster by using a non-secure HTTP connection. However, you can
configure HTTPS, change the host and port, and configure the data management policy used by the servers when creating
Regions.
TIP: See the section in the SDG reference documentation on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-cluster[Configuring Cluster Configuration Push]
for more details.
[[geode-configuration-declarative-annotations-productivity-enableclusteraware-strictmatch]]
===== @EnableClusterAware, strictMatch
The `strictMatch` attribute has been added to the `@EnableClusterAware` annotation to enable fail-fast behavior.
`strictMatch` is set to `false` by default.
Essentially, when you set `strictMatch` to `true`, your Spring Boot, {geode-name} `ClientCache` application requires
an {geode-name} cluster to exist. That is, the application requires a client/server topology to operate, and the
application should fail to start if a cluster is not present. The application should not startup in a local-only
capacity.
When `strictMatch` is set to `true` and an {geode-name} cluster is not available, your Spring Boot, {geode-name}
`ClientCache` application fails to start with a `ClusterNotFoundException`. The application does not attempt to
start in a local-only capacity.
You can explicitly set the `strictMatch` attribute programmatically by using the `@EnableClusterAware` annotation:
.Set `@EnableClusterAware.strictMatch`
====
[source,java]
----
@SpringBootApplication
@EnableClusterAware(strictMatch = true)
class SpringBootApacheGeodeClientCacheApplication { }
----
====
Alternatively, you can set `strictMatch` attribute by using the corresponding property
in Spring Boot `application.properties`:
.Set `strictMatch` using a property
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cluster.condition.match.strict=true
----
====
This is convenient when you need to apply this configuration setting conditionally, based on a Spring profile.
When you adjust the log level of the `org.springframework.geode.config.annotation.ClusterAwareConfiguration` logger
to `INFO`, you get more details from the `@EnableClusterAware` functionality when applying the logic to determine
the presence of an {geode-name} cluster, such as which explicitly or implicitly configured connections were successful.
The following example shows typical output:
.`@EnableClusterAware` INFO log output
====
[source,txt]
----
2021-01-20 14:02:28,740 INFO fig.annotation.ClusterAwareConfiguration: 476 - Failed to connect to localhost[40404]
2021-01-20 14:02:28,745 INFO fig.annotation.ClusterAwareConfiguration: 476 - Failed to connect to localhost[10334]
2021-01-20 14:02:28,746 INFO fig.annotation.ClusterAwareConfiguration: 470 - Successfully connected to localhost[57649]
2021-01-20 14:02:28,746 INFO fig.annotation.ClusterAwareConfiguration: 576 - Cluster was found; Auto-configuration made [1] successful connection(s);
2021-01-20 14:02:28,746 INFO fig.annotation.ClusterAwareConfiguration: 586 - Spring Boot application is running in a client/server topology, using a standalone Apache Geode-based cluster
----
====
NOTE: An attempt is always made to connect to `localhost` on the default `Locator` port, `10334`, and the default
`CacheServer` port, `40404`.
TIP: You can force a successful match by setting the `spring.boot.data.gemfire.cluster.condition.match` property
to `true` in Spring Boot `application.properties`. This is sometimes useful for testing purposes.
[[geode-configuration-declarative-annotations-productivity-regions]]
==== `@EnableCachingDefinedRegions`, `@EnableClusterDefinedRegions` and `@EnableEntityDefinedRegions` (SDG)
These annotations are used to create Regions in the cache to manage your application data.
You can create Regions by using Java configuration and the Spring API as follows:
.Creating a Region with Spring JavaConfig
====
[source,java]
----
@Configuration
class GeodeConfiguration {
@Bean("Customers")
ClientRegionFactoryBean<Long, Customer> customersRegion(GemFireCache cache) {
ClientRegionFactoryBean<Long, Customer> customers =
new ClientRegionFactoryBean<>();
customers.setCache(cache);
customers.setShortcut(ClientRegionShortcut.PROXY);
return customers;
}
}
----
====
You can do the same in XML:
.Creating a client Region using Spring XML
====
[source,xml]
----
<gfe:client-region id="Customers" shorcut="PROXY"/>
----
====
However, using the provided annotations is far easier, especially during development, when the complete Region
configuration may be unknown and you want only to create a Region to persist your application data and move on.
[[geode-configuration-declarative-annotations-productivity-regions-enablecachingdefined]]
===== `@EnableCachingDefinedRegions`
The `@EnableCachingDefinedRegions` annotation is used when you have application components registered in the Spring
container that are annotated with Spring or JSR-107 JCache
{spring-framework-docs}/integration.html#cache-jsr-107[annotations].
Caches that are identified by name in the caching annotations are used to create Regions that hold the data
you want cached.
Consider the following example:
.Defining Regions based on Spring or JSR-107 JCache Annotations
====
[source,java]
----
@Service
class CustomerService {
@Cacheable(cacheNames = "CustomersByAccountNumber", key = "#account.number")
Customer findBy(Account account) {
// ...
}
}
----
====
Further consider the following example, in which the main `@SpringBootApplication` class is annotated with
`@EnableCachingDefinedRegions`:
.Using `@EnableCachingDefinedRegions`
====
[source,java]
----
@SpringBootApplication
@EnableCachingDefineRegions
class SpringBootApacheGeodeClientCacheApplication { }
----
====
With this setup, SBDG would create a client `PROXY` Region (or `PARTITION_REGION` if your application were a peer member
of the {geode-name} cluster) with a name of "`CustomersByAccountNumber`", as though you created the Region by using
either the Java configuration or XML approaches shown earlier.
You can use the `clientRegionShortcut` or `serverRegionShortcut` attribute to change the data management policy of
the Regions created on the client or servers, respectively.
For client Regions, you can also set the `poolName` attribute to assign a specific `Pool` of connections to be used by
the client `*PROXY` Regions to send data to the cluster.
[[geode-configuration-declarative-annotations-productivity-regions-enableentitydefined]]
===== `@EnableEntityDefinedRegions`
As with `@EnableCachingDefinedRegions`, `@EnableEntityDefinedRegions` lets you create Regions based on the entity
classes you have defined in your application domain model.
For instance, consider an entity class annotated with SDG's
{spring-data-geode-javadoc}/org/springframework/data/gemfire/mapping/annotation/Region.html[`@Region`]
mapping annotation:
.Customer entity class annotated with `@Region`
====
[source,java]
----
@Region("Customers")
class Customer {
@Id
private Long id;
@Indexed
private String name;
}
----
====
For this class, SBDG creates Regions from the name specified in the `@Region` mapping annotation on the entity class.
In this case, the `Customer` application-defined entity class results in the creation of a Region named "`Customers`"
when the main `@SpringBootApplication` class is annotated with `@EnableEntityDefinedRegions`:
.Using `@EnableEntityDefinedRegions`
====
[source,java]
----
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class,
clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY)
class SpringBootApacheGeodeClientCacheApplication { }
----
====
As with the `@EnableCachingDefinedRegions` annotation, you can set the client and server Region data management policy
by using the `clientRegionShortcut` and `serverRegionShortcut` attributes, respectively, and set a dedicated `Pool`
of connections used by client Regions with the `poolName` attribute.
However, unlike the `@EnableCachingDefinedRegions` annotation, you must specify either the `basePackage` attribute
or the type-safe `basePackageClasses` attribute (recommended) when you use the `@EnableEntityDefinedRegions` annotation.
Part of the reason for this is that `@EnableEntityDefinedRegions` performs a component scan for the entity classes
defined by your application. The component scan loads each class to inspect the annotation metadata for that class.
This is not unlike the JPA entity scan when working with JPA providers, such as Hibernate.
Therefore, it is customary to limit the scope of the scan. Otherwise, you end up potentially loading many classes
unnecessarily. After all, the JVM uses dynamic linking to load classes only when needed.
Both the `basePackages` and `basePackageClasses` attributes accept an array of values. With `basePackageClasses`, you
need only refer to a single class type in that package and every class in that package as well as classes in the
sub-packages are scanned to determine if the class type represents an entity. A class type is an entity if it is
annotated with the `@Region` mapping annotation. Otherwise, it is not considered to be an entity.
For example, suppose you had the following structure:
.Entity Scan
====
[source,txt]
----
- example.app.crm.model
|- Customer.class
|- NonEntity.class
|- contact
|- Address.class
|- PhoneNumber.class
|- AnotherNonEntity.class
- example.app.accounts.model
|- Account.class
...
..
.
----
====
Then you could configure the `@EnableEntityDefinedRegions` as follows:
.Targeting with `@EnableEntityDefinedRegions`
====
[source,java]
----
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = { NonEntity.class, Account.class } )
class SpringBootApacheGeodeClientCacheApplication { }
----
====
If `Customer`, `Address`, `PhoneNumber` and `Account` were all entity classes properly annotated with `@Region`,
the component scan would pick up all these classes and create Regions for them. The `NonEntity` class serves only as
a marker in this case, to point to where (that is, which package) the scan should begin.
Additionally, the `@EnableEntityDefinedRegions` annotation provides include and exclude filters, the same as
the core Spring Frameworks `@ComponentScan` annotation.
TIP: See the SDG reference documentation on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-regions[Configuring Regions]
for more details.
[[geode-configuration-declarative-annotations-productivity-regions-enableclusterdefined]]
===== `@EnableClusterDefinedRegions`
Sometimes, it is ideal or even necessary to pull configuration from the cluster (rather than push configuration to the
cluster). That is, you want the Regions defined on the servers to be created on the client and used by your application.
To do so, annotate your main `@SpringBootApplication` class with `@EnableClusterDefinedRegions`:
.Using `@EnableClusterDefinedRegions`
====
[source,java]
----
@SpringBootApplication
@EnableClusterDefinedRegions
class SpringBootApacheGeodeClientCacheApplication { }
----
====
Every Region that exists on the servers in the {geode-name} cluster will have a corresponding `PROXY` Region defined
and created on the client as a bean in your Spring Boot application.
If the cluster of servers defines a Region called "`ServerRegion`", you can inject a client `PROXY` Region with
the same name ("`ServerRegion`") into your Spring Boot application:
.Using a server-side Region on the client
====
[source,java]
----
@Component
class SomeApplicationComponent {
@Resource(name = "ServerRegion")
private Region<Integer, EntityType> serverRegion;
public void someMethod() {
EntityType entity = new EntityType();
this.serverRegion.put(1, entity);
// ...
}
}
----
====
SBDG auto-configures a `GemfireTemplate` for the "`ServerRegion`" Region
(see <<geode-configuration-declarative-auto-configuration-regiontemplates>>),
so a better way to interact with the client `PROXY` Region that corresponds to the "`ServerRegion`" Region on the server
is to inject the template:
.Using a server-side Region on the client with a template
====
[source,java]
----
@Component
class SomeApplicationComponent {
@Autowired
@Qualifier("serverRegionTemplate")
private GemfireTemplate serverRegionTemplate;
public void someMethod() {
EntityType entity = new EntityType();
this.serverRegionTemplate.put(1, entity);
//...
}
}
----
====
TIP: See the SDG reference documentation on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-region-cluster-defined[Configuring Cluster-defined Regions]
for more details.
[[geode-configuration-declarative-annotations-productivity-enableindexing]]
==== `@EnableIndexing` (SDG)
You can also use the `@EnableIndexing` annotation -- but only when you use `@EnableEntityDefinedRegions`. This is
because `@EnableIndexing` requires the entities to be scanned and analyzed for mapping metadata (defined on the class
type of the entity). This includes annotations such as the Spring Data Commons `@Id` annotation and the annotations
provided by SDG, such as `@Indexed` and `@LuceneIndexed`.
The `@Id` annotation identifies the (primary) key of the entity. The `@Indexed` annotation defines OQL indexes on object
fields, which can be used in the predicates of your OQL queries. The `@LuceneIndexed` annotation is used to define the
Apache Lucene Indexes required for searches.
NOTE: Lucene Indexes can only be created on `PARTITION` Regions, and `PARTITION` Regions can only be defined on
the server side.
You may have noticed that the `Customer` entity class's `name` field was annotated with `@Indexed`.
Consider the following listing:
.Customer entity class with `@Indexed` annotated `name` field
====
[source,java]
----
@Region("Customers")
class Customer {
@Id
private Long id;
@Indexed
private String name;
}
----
====
As a result, when our main `@SpringBootApplication` class is annotated with `@EnableIndexing`, an {geode-name} OQL Index
for the `Customer.name` field is created, allowing OQL queries on customers by name to use this Index:
.Using `@EnableIndexing`
====
[source,java]
----
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@EnableIndexing
class SpringBootApacheGeodeClientCacheApplication { }
----
====
NOTE: Keep in mind that OQL Indexes are not persistent between restarts (that is, {geode-name} maintains Indexes
in memory only). An OQL Index is always rebuilt when the node is restarted.
When you combine `@EnableIndexing` with either `@EnableClusterConfiguration` or `@EnableClusterAware`, the Index
definitions are pushed to the server-side Regions where OQL queries are generally executed.
TIP: See the SDG reference documentation on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-region-indexes[Configuring Indexes]
for more details.
[[geode-configuration-declarative-annotations-productivity-enableexpiration]]
==== `@EnableExpiration` (SDG)
It is often useful to define both eviction and expiration policies, particularly with a system like {geode-name},
because it primarily keeps data in memory (on the JVM Heap). Your data volume size may far exceed the amount of
available JVM Heap memory, and keeping too much data on the JVM Heap can cause Garbage Collection (GC) issues.
TIP: You can enable off-heap (or main memory usage) capabilities by declaring SDG's `@EnableOffHeap` annotation.
See the SDG reference documentation on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-region-off-heap[Configuring Off-Heap Memory]
for more details.
Defining eviction and expiration policies lets you limit what is kept in memory and for how long.
While {spring-data-geode-docs-html}/#bootstrap-annotation-config-region-eviction[configuring eviction] is easy with SDG,
we particularly want to call out expiration since
{spring-data-geode-docs-html}/#bootstrap-annotation-config-region-expiration[configuring expiration] has special support
in SDG.
With SDG, you can define the expiration policies associated with a particular application class type on the class type
itself, by using the {spring-data-geode-javadoc}/org/springframework/data/gemfire/expiration/Expiration.html[`@Expiration`],
{spring-data-geode-javadoc}/org/springframework/data/gemfire/expiration/IdleTimeoutExpiration.html[`@IdleTimeoutExpiration`]
and {spring-data-geode-javadoc}/org/springframework/data/gemfire/expiration/TimeToLiveExpiration.html[`@TimeToLiveExpiration`]
annotations.
TIP: See the {geode-name} {apache-geode-docs}/developing/expiration/how_expiration_works.html[User Guide]
for more details on the different expiration types -- that is _Idle Timeout_ (TTI) versus _Time-to-Live_ (TTL).
For example, suppose we want to limit the number of `Customers` maintained in memory for a period of time (measured in
seconds) based on the last time a `Customer` was accessed (for example, the last time a `Customer` was read). To do so,
we can define an idle timeout expiration (TTI) policy on our `Customer` class type:
.Customer entity class with Idle Timeout Expiration (TTI)
====
[source,java]
----
@Region("Customers")
@IdleTimeoutExpiration(action = "INVALIDATE", timeout = "300")
class Customer {
@Id
private Long id;
@Indexed
private String name;
}
----
====
The `Customer` entry in the `Customers` Region is `invalidated` after 300 seconds (5 minutes).
To enable annotation-based expiration policies, we need to annotate our main `@SpringBootApplication` class
with `@EnableExpiration`:
.Enabling Expiration
====
[source,java]
----
@SpringBootApplication
@EnableExpiration
class SpringBootApacheGeodeApplication { }
----
====
NOTE: Technically, this entity-class-specific annotation-based expiration policy is implemented by using {geode-name}'s
{apache-geode-javadoc}/org/apache/geode/cache/CustomExpiry.html[`CustomExpiry`] interface.
TIP: See the SDG reference doccumentation for more details on
{spring-data-geode-docs-html}/#bootstrap-annotation-config-region-expiration[configuring expiration], along with
{spring-data-geode-docs-html}/#bootstrap:region:expiration:annotation[annotation-based data expiration] in particular.
[[geode-configuration-declarative-annotations-productivity-enablemockobjects]]
==== `@EnableGemFireMockObjects` (STDG)
Software testing in general and unit testing in particular are a very important development tasks to ensure the quality
of your Spring Boot applications.
{geode-name} can make testing difficult in some cases, especially when tests have to be written as integration tests
to assert the correct behavior. This can be very costly and lengthens the feedback cycle. Fortunately, you can write
unit tests as well.
Spring provides a framework for testing Spring Boot applications that use {geode-name}. This is where the
{spring-test-data-gemfire-website}[Spring Test for {geode-name} (STDG)] project can help, particularly with
unit testing.
For example, if you do not care what {geode-name} would actually do in certain cases and only care about the "`contract`",
which is what mocking a collaborator is all about, you could effectively mock {geode-name} objects to isolate the SUT,
or "`Subject Under Test`", and focus on the interactions or outcomes you expect to happen.
With STDG, you need not change a bit of configuration to enable mock objects in the unit tests for your Spring Boot
applications. You need only annotate the test class with `@EnableGemFireMockObjects`:
.Using Mock {geode-name} Objects
====
[source,java]
----
@RunWith(SpringRunner.class)
@SpringBootTest
class MyApplicationTestClass {
@Test
public void someTestCase() {
// ...
}
@Configuration
@EnableGemFireMockObjects
static class GeodeConfiguration { }
}
----
====
Your Spring Boot configuration of {geode-name} returns mock objects for all {geode-name} objects, such as Regions.
Mocking {geode-name} objects even works for objects created from the productivity annotations discussed in the previous
sections.
For example, consider the following Spring Boot, {geode-name} `ClientCache` application class:
.Main `@SpringBootApplication` class under test
====
[source,java]
----
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
class SpringBootApacheGeodeClientCacheApplication { }
----
====
In the preceding example, the `"Customers`" Region defined by the `Customer` entity class and created by
the `@EnableEntityDefinedRegions` annotation would be a mock Region and not an actual Region. You can still inject
the Region in your test and assert interactions on the Region based on your application workflows:
.Using Mock {geode-name} Objects
====
[source,java]
----
@RunWith(SpringRunner.class)
@SpringBootTest
class MyApplicationTestClass {
@Resource(name = "Customers")
private Region<Long, Customer> customers;
@Test
public void someTestCase() {
Customer jonDoe = new Customer(1, "Jon Doe");
// Use the application in some way and test the interaction on the "Customers" Region
assertThat(this.customers).containsValue(jonDoe);
// ...
}
}
----
====
There are many more things that STDG can do for you in both unit testing and integration testing.
See the https://github.com/spring-projects/spring-test-data-geode#unit-testing-with-stdg[documentation on unit testing]
for more details.
You can https://github.com/spring-projects/spring-test-data-geode#integration-testing-with-stdg[write integration tests]
that use STDG as well. Writing integration tests is an essential concern when you need to assert whether your
application OQL queries are well-formed, for instance. There are many other valid cases where integration testing
is also applicable.

View File

@@ -0,0 +1,152 @@
[[geode-configuration-externalized]]
== Externalized Configuration
:geode-name: {apache-geode-name}
Like Spring Boot itself (see {spring-boot-docs-html}/boot-features-external-config.html[Spring Boot's documentation]),
Spring Boot for {geode-name} (SBDG) supports externalized configuration.
By externalized configuration, we mean configuration metadata stored in Spring Boot
{spring-boot-docs-html}/boot-features-external-config.html#boot-features-external-config-application-property-files[`application.properties`].
You can even separate concerns by addressing each concern in an individual properties file. Optionally, you could also
enable any given property file for only a specific {spring-boot-docs-html}/boot-features-external-config.html#boot-features-external-config-profile-specific-properties[profile].
You can do many other powerful things, such as (but not limited to) using
{spring-boot-docs-html}/boot-features-external-config.html#boot-features-external-config-placeholders-in-properties[placeholders]
in properties, {spring-boot-docs-html}/boot-features-external-config.html#boot-features-encrypting-properties[encrypting]
properties, and so on. In this section, we focus particularly on
{spring-boot-docs-html}/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties[type safety].
Like Spring Boot, Spring Boot for {geode-name} provides a hierarchy of classes that captures configuration for several
{geode-name} features in an associated `@ConfigurationProperties` annotated class. Again, the configuration metadata is
specified as well-known, documented properties in one or more Spring Boot `application.properties` files.
For instance, a Spring Boot, {geode-name} `ClientCache` application might be configured as follows:
.Spring Boot `application.properties` containing Spring Data properties for {geode-name}
====
[source,properties]
----
# Spring Boot application.properties used to configure {geode-name}
spring.data.gemfire.name=MySpringBootApacheGeodeApplication
# Configure general cache properties
spring.data.gemfire.cache.copy-on-read=true
spring.data.gemfire.cache.log-level=debug
# Configure ClientCache specific properties
spring.data.gemfire.cache.client.durable-client-id=123
spring.data.gemfire.cache.client.keep-alive=true
# Configure a log file
spring.data.gemfire.logging.log-file=/path/to/geode.log
# Configure the client's connection Pool to the servers in the cluster
spring.data.gemfire.pool.locators=10.105.120.16[11235],boombox[10334]
----
====
You can use many other properties to externalize the configuration of your Spring Boot, {geode-name} applications.
See the {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/package-frame.html[Javadoc]
for specific configuration properties. Specifically, review the `enabling` annotation attributes.
You may sometimes require access to the configuration metadata (specified in properties) in your Spring Boot
applications themselves, perhaps to further inspect or act on a particular configuration setting. You can access any
property by using Spring's {spring-framework-javadoc}/org/springframework/core/env/Environment.html[`Environment`]
abstraction:
.Using the Spring `Environment`
====
[source,java]
----
@Configuration
class GeodeConfiguration {
void readConfigurationFromEnvironment(Environment environment) {
boolean copyOnRead = environment.getProperty("spring.data.gemfire.cache.copy-on-read",
Boolean.TYPE, false);
}
}
----
====
While using `Environment` is a nice approach, you might need access to additional properties or want to access
the property values in a type-safe manner. Therefore, you can now, thanks to SBDG's auto-configured configuration
processor, access the configuration metadata by using `@ConfigurationProperties` classes.
To add to the preceding example, you can now do the following:
.Using `GemFireProperties`
====
[source,java]
----
@Component
class MyApplicationComponent {
@Autowired
private GemFireProperties gemfireProperties;
public void someMethodUsingGemFireProperties() {
boolean copyOnRead = this.gemfireProperties.getCache().isCopyOnRead();
// do something with `copyOnRead`
}
}
----
====
Given a handle to {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/configuration/GemFireProperties.html[`GemFireProperties`],
you can access any of the configuration properties that are used to configure {geode-name} in a Spring context. You need
only autowire an instance of `GemFireProperties` into your application component.
See the complete reference for the
{spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/configuration/package-frame.html[SBDG `@ConfigurationProperties` classes and supporting classes].
[[geode-configuration-externalized-session]]
=== Externalized Configuration of Spring Session
You can access the externalized configuration of Spring Session when you use {geode-name} as your (HTTP) session state
caching provider.
In this case, you need only acquire a reference to an instance of the
{spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/configuration/SpringSessionProperties.html[`SpringSessionProperties`]
class.
As shown earlier in this chapter, you can specify Spring Session for {geode-name} (SSDG) properties as follows:
.Spring Boot `application.properties` for Spring Session using {geode-name} as the (HTTP) session state caching provider
====
[source,properties]
----
# Spring Boot application.properties used to configure {geode-name} as a (HTTP) session state caching provider
# in Spring Session
spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds=300
spring.session.data.gemfire.session.region.name=UserSessions
----
====
Then, in your application, you can do something similar to the following example:
.Using `SpringSessionProperties`
====
[source,java]
----
@Component
class MyApplicationComponent {
@Autowired
private SpringSessionProperties springSessionProperties;
public void someMethodUsingSpringSessionProperties() {
String sessionRegionName = this.springSessionProperties
.getSession().getRegion().getName();
// do something with `sessionRegionName`
}
}
----
====

View File

@@ -0,0 +1,359 @@
[[geode-configuration-metadata]]
== Configuration Metadata Reference
:geode-name: {apache-geode-name}
The following reference sections cover documented and well-known properties recognized and processed by
Spring Data for {geode-name} (SDG) and Spring Session for {geode-name} (SSDG).
These properties may be used in Spring Boot `application.properties` or as JVM System properties, to configure different
aspects of or enable individual features of {geode-name} in a Spring application. When combined with the power of
Spring Boot, they give you the ability to quickly create an application that uses {geode-name}.
[[geode-configuration-metadata-springdata]]
=== Spring Data Based Properties
The following properties all have a `spring.data.gemfire.*` prefix. For example, to set the cache `copy-on-read`
property, use `spring.data.gemfire.cache.copy-on-read` in Spring Boot `application.properties`.
.`spring.data.gemfire.*` properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `name` | Name of the {geode-name}. | `SpringBasedCacheClientApplication` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#name--[`ClientCacheApplication.name`]
| `locators` | Comma-delimited list of Locator endpoints formatted as: `locator1[port1],...,locatorN[portN]`. | [] | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#locators--[`PeerCacheApplication.locators`]
| `use-bean-factory-locator` | Enable the SDG `BeanFactoryLocator` when mixing Spring config with {geode-name} native config (such as `cache.xml`) and you wish to configure {geode-name} objects declared in `cache.xml` with Spring. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#useBeanFactoryLocator--[`ClientCacheApplication.useBeanFactoryLocator`]
|=====================================================================================================================
.`spring.data.gemfire.*` _GemFireCache_ properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cache.copy-on-read` | Configure whether a copy of an object returned from `Region.get(key)` is made. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#copyOnRead--[`ClientCacheApplication.copyOnRead`]
| `cache.critical-heap-percentage` | Percentage of heap at or above which the cache is considered in danger of becoming inoperable. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#criticalHeapPercentage--[`ClientCacheApplication.criticalHeapPercentage`]
| `cache.critical-off-heap-percentage` | Percentage of off-heap at or above which the cache is considered in danger of becoming inoperable. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#criticalOffHeapPercentage--[`ClientCacheApplication.criticalOffHeapPercentage`]
| `cache.enable-auto-region-lookup` | Whether to lookup Regions configured in {geode-name} native configuration and declare them as Spring beans. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAutoRegionLookup.html#enabled--[`EnableAutoRegionLookup.enable`]
| `cache.eviction-heap-percentage` | Percentage of heap at or above which the eviction should begin on Regions configured for HeapLRU eviction. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#evictionHeapPercentage--[`ClientCacheApplication.evictionHeapPercentage`]
| `cache.eviction-off-heap-percentage` | Percentage of off-heap at or above which the eviction should begin on Regions configured for HeapLRU eviction. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#evictionOffHeapPercentage--[`ClientCacheApplication.evictionOffHeapPercentage`]
| `cache.log-level` | Configure the log-level of an {geode-name} cache. | `config` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#logLevel--[`ClientCacheApplication.logLevel`]
| `cache.name` | Alias for `spring.data.gemfire.name`. | `SpringBasedCacheClientApplication` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#name--[`ClientCacheApplication.name`]
| `cache.compression.bean-name` | Name of a Spring bean that implements `org.apache.geode.compression.Compressor`. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableCompression.html#compressorBeanName--[`EnableCompression.compressorBeanName`]
| `cache.compression.region-names` | Comma-delimited list of Region names for which compression is configured. | `[]` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableCompression.html#RegionNames--[EnableCompression.RegionNames]
| cache.off-heap.memory-size | Determines the size of off-heap memory used by {geode-name} in megabytes (m) or gigabytes (g) -- for example, `120g` | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableOffHeap.html#memorySize--[`EnableOffHeap.memorySize`]
| `cache.off-heap.region-names` | Comma-delimited list of Region names for which off-heap is configured. | `[]` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableOffHeap.html#RegionNames--[`EnableOffHeap.RegionNames`]
|=====================================================================================================================
.`spring.data.gemfire.*` _ClientCache_ properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cache.client.durable-client-id` | Used only for clients in a client/server installation. If set, this indicates that the client is durable and identifies the client. The ID is used by servers to reestablish any messaging that was interrupted by client downtime. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#durableClientId--[`ClientCacheApplication.durableClientId`]
| `cache.client.durable-client-timeout` | Used only for clients in a client/server installation. Number of seconds this client can remain disconnected from its server and have the server continue to accumulate durable events for it. | `300` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#durableClientTimeout--[`ClientCacheApplication.durableClientTimeout`]
| `cache.client.keep-alive` | Whether the server should keep the durable client's queues alive for the timeout period. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#keepAlive--[`ClientCacheApplication.keepAlive`]
|=====================================================================================================================
.`spring.data.gemfire.*` peer _Cache_ properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cache.peer.enable-auto-reconnect` | Whether a member (a Locator or Server) try to reconnect and reinitialize the cache after it has been forced out of the cluster by a network partition event or has otherwise been shunned by other members. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#enableAutoReconnect--[`PeerCacheApplication.enableAutoReconnect`]
| `cache.peer.lock-lease` | The length, in seconds, of distributed lock leases obtained by this cache. | `120` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#lockLease--[`PeerCacheApplication.lockLease`]
| `cache.peer.lock-timeout` | The number of seconds a cache operation waits to obtain a distributed lock lease. | `60` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#lockTimeout--[`PeerCacheApplication.lockTimeout`]
| `cache.peer.message-sync-interval` | The frequency (in seconds) at which a message is sent by the primary cache-server to all the secondary cache-server nodes to remove the events that have already been dispatched from the queue. | `1` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#messageSyncInterval--[`PeerCacheApplication.messageSyncInterval`]
| `cache.peer.search-timeout` | The number of seconds a cache get operation can spend searching for a value. | `300` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#searchTimeout--[`PeerCacheApplication.searchTimeout`]
| `cache.peer.use-cluster-configuration` | Whether this cache member node pulls its configuration metadata from the cluster-based cluster configuration service. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/PeerCacheApplication.html#useClusterConfiguration--[`PeerCacheApplication.useClusterConfiguration`]
|=====================================================================================================================
.`spring.data.gemfire.*` _CacheServer_ properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cache.server.auto-startup` | Whether the `CacheServer` should be started automatically at runtime. | `true` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#autoStartup--[`CacheServerApplication.autoStartup`]
| `cache.server.bind-address` | The IP address or hostname on which this cache server listens. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#bindAddress--[`CacheServerApplication.bindAddress`]
| `cache.server.hostname-for-clients` | The IP address or hostname that server locators tell to clients to indicate the IP address on which the cache server listens. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#hostnameForClients--[`CacheServerApplication.hostNameForClients`]
| `cache.server.load-poll-interval` | The frequency in milliseconds at which to poll the load probe on this cache server. | `5000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#loadPollInterval--[`CacheServerApplication.loadPollInterval`]
| `cache.server.max-connections` | The maximum client connections. | `800` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#maxConnections--[`CacheServerApplication.maxConnections`]
| `cache.server.max-message-count` | The maximum number of messages that can be in a client queue. | `230000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#maxMessageCount--[`CacheServerApplication.maxMessageCount`]
| `cache.server.max-threads` | The maximum number of threads allowed in this cache server to service client requests. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#maxThreads--[`CacheServerApplication.maxThreads`]
| `cache.server.max-time-between-pings` | The maximum amount of time between client pings. | `60000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#maxTimeBetweenPings--[`CacheServerApplication.maxTimeBetweenPings`]
| `cache.server.message-time-to-live` | The time (in seconds) after which a message in the client queue expires. | `180` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#messageTimeToLive--[`CacheServerApplication.messageTimeToLive`]
| `cache.server.port` | The port on which this cache server listens for clients. | `40404` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#port--[`CacheServerApplication.port`]
| `cache.server.socket-buffer-size` | The buffer size of the socket connection to this `CacheServer`. | `32768` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#socketBufferSize--[`CacheServerApplication.socketBufferSize`]
| `cache.server.subscription-capacity` | The capacity of the client queue. | `1` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#subscriptionCapacity--[`CacheServerApplication.subscriptionCapacity`]
| `cache.server.subscription-disk-store-name` | The name of the disk store for client subscription queue overflow. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#subscriptionDiskStoreName--[`CacheServerApplication.subscriptionDiskStoreName`]
| `cache.server.subscription-eviction-policy` | The eviction policy that is executed when the capacity of the client subscription queue is reached. | `none` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#subscriptionEvictionPolicy--[`CacheServerApplication.subscriptionEvictionPolicy`]
| `cache.server.tcp-no-delay` | The outgoing socket connection tcp-no-delay setting. | `true` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/CacheServerApplication.html#tcpNoDelay--[`CacheServerApplication.tcpNoDelay`]
|=====================================================================================================================
`CacheServer` properties can be further targeted at specific `CacheServer` instances by using an optional bean name
of the `CacheServer` bean defined in the Spring `ApplicationContext`. Consider the following example:
====
[source,properties]
----
spring.data.gemfire.cache.server.[<cacheServerBeanName>].bind-address=...
----
====
.`spring.data.gemfire.*` Cluster properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cluster.Region.type` | Specifies the data management policy used when creating Regions on the servers in the cluster. | {apache-geode-javadoc}/org/apache/geode/cache/RegionShortcut.html#PARTITION[`RegionShortcut.PARTITION`] | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html#serverRegionShortcut--[`EnableClusterConfiguration.serverRegionShortcut`]
|=====================================================================================================================
.`spring.data.gemfire.*` _DiskStore_ properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `disk.store.allow-force-compaction` | Whether to allow `DiskStore.forceCompaction()` to be called on Regions that use a disk store. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#allowForceCompaction--[`EnableDiskStore.allowForceCompaction`]
| `disk.store.auto-compact` | Whether to cause the disk files to be automatically compacted. | `true` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#autoCompact--[`EnableDiskStore.autoCompact`]
| `disk.store.compaction-threshold` | The threshold at which an oplog becomes compactible. | `50` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#compactionThreshold--[`EnableDiskStore.compactionThreshold`]
| `disk.store.directory.location` | The system directory where the `DiskStore` (oplog) files are stored. | `[]` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#diskDirectories--[`EnableDiskStore.diskDirectories.location`]
| `disk.store.directory.size` | The amount of disk space allowed to store disk store (oplog) files. | `21474883647` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#diskDirectories--[`EnableDiskStore.diskDirectories.size`]
| `disk.store.disk-usage-critical-percentage` | The critical threshold for disk usage as a percentage of the total disk volume. | `99.0` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#diskUsageCriticalPercentage--[`EnableDiskStore.diskUsageCriticalPercentage`]
| `disk.store.disk-usage-warning-percentage` | The warning threshold for disk usage as a percentage of the total disk volume. | `90.0` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#diskUsageWarningPercentage--[`EnableDiskStore.diskUsageWarningPercentage`]
| `disk.store.max-oplog-size` | The maximum size (in megabytes) a single oplog (operation log) can be. | `1024` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#maxOplogSize--[`EnableDiskStore.maxOplogSize`]
| `disk.store.queue-size` | The maximum number of operations that can be asynchronously queued. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#queueSize--[`EnableDiskStore.queueSize`]
| `disk.store.time-interval` | The number of milliseconds that can elapse before data written asynchronously is flushed to disk. | `1000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#timeInterval--[`EnableDiskStore.timeInterval`]
| `disk.store.write-buffer-size` | Configures the write buffer size in bytes. | `32768` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableDiskStore.html#writeBufferSize--[`EnableDiskStore.writeBufferSize`]
|=====================================================================================================================
`DiskStore` properties can be further targeted at specific `DiskStore` instances by setting the
{apache-geode-javadoc}/org/apache/geode/cache/DiskStore.html#getName--[`DiskStore.name`] property.
For example, you can specify directory location of the files for a specific, named `DiskStore` by using:
====
[source,properties]
----
spring.data.gemfire.disk.store.Example.directory.location=/path/to/geode/disk-stores/Example/
----
====
The directory location and size of the `DiskStore` files can be further divided into multiple locations and size
using array syntax:
====
[source,properties]
----
spring.data.gemfire.disk.store.Example.directory[0].location=/path/to/geode/disk-stores/Example/one
spring.data.gemfire.disk.store.Example.directory[0].size=4096000
spring.data.gemfire.disk.store.Example.directory[1].location=/path/to/geode/disk-stores/Example/two
spring.data.gemfire.disk.store.Example.directory[1].size=8192000
----
====
Both the name and array index are optional, and you can use any combination of name and array index. Without a name,
the properties apply to all `DiskStore` instances. Without array indexes, all named `DiskStore` files are stored in
the specified location and limited to the defined size.
.`spring.data.gemfire.*` Entity properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `entities.base-packages` | Comma-delimited list of package names indicating the start points for the entity scan. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.html#basePackages--[`EnableEntityDefinedRegions.basePackages`]
|=====================================================================================================================
.`spring.data.gemfire.*` Locator properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `locator.host` | The IP address or hostname of the system NIC to which the embedded Locator is bound to listen for connections. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLocator.html#host--[`EnableLocator.host`]
| locator.port | The network port to which the embedded Locator will listen for connections. | `10334` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLocator.html#port--[`EnableLocator.port`]
|=====================================================================================================================
.`spring.data.gemfire.*` Logging properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `logging.level` | The log level of an {geode-name} cache. Alias for 'spring.data.gemfire.cache.log-level'. | `config` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html#logLevel--[`EnableLogging.logLevel`]
| `logging.log-disk-space-limit` | The amount of disk space allowed to store log files. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html#logDiskSpaceLimit--[`EnableLogging.logDiskSpaceLimit`]
| `logging.log-file` | The pathname of the log file used to log messages. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html#logFile--[`EnableLogging.logFile`]
| `logging.log-file-size` | The maximum size of a log file before the log file is rolled. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html#logFileSizeLimit--[`EnableLogging.logFileSize`]
|=====================================================================================================================
.`spring.data.gemfire.*` Management properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `management.use-http` | Whether to use the HTTP protocol to communicate with an {geode-name} Manager. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html#useHttp--[`EnableClusterConfiguration.useHttp`]
| `management.http.host` | The IP address or hostname of the {geode-name} Manager that runs the HTTP service. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html#host--[`EnableClusterConfiguration.host`]
| `management.http.port` | The port used by the {geode-name} Manager's HTTP service to listen for connections. | `7070` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html#port--[`EnableClusterConfiguration.port`]
|=====================================================================================================================
.`spring.data.gemfire.*` Manager properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `manager.access-file` | The access control list (ACL) file used by the Manager to restrict access to the JMX MBeans by the clients. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#accessFile--[`EnableManager.accessFile`]
| manager.bind-address | The IP address or hostname of the system NIC used by the Manager to bind and listen for JMX client connections. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#bindAddress--[`EnableManager.bindAddress`]
| `manager.hostname-for-clients` | The hostname given to JMX clients to ask the Locator for the location of the Manager. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#hostnameForClients--[`EnableManager.hostNameForClients`]
| `manager.password-file` | By default, the JMX Manager lets clients without credentials connect. If this property is set to the name of a file, only clients that connect with credentials that match an entry in this file are allowed. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#passwordFile--[`EnableManager.passwordFile`]
| `manager.port` | The port used by the Manager to listen for JMX client connections. | `1099` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#port--[`EnableManager.port`]
| `manager.start` | Whether to start the Manager service at runtime. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#start--[`EnableManager.start`]
| `manager.update-rate` | The rate, in milliseconds, at which this member pushes updates to any JMX Managers. | `2000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableManager.html#updateRate--[`EnableManager.updateRate`]
|=====================================================================================================================
.`spring.data.gemfire.*` PDX properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `pdx.disk-store-name` | The name of the `DiskStore` used to store PDX type metadata to disk when PDX is persistent. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html#diskStoreName--[`EnablePdx.diskStoreName`]
| `pdx.ignore-unread-fields` | Whether PDX ignores fields that were unread during deserialization. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html#ignoreUnreadFields--[`EnablePdx.ignoreUnreadFields`]
| `pdx.persistent` | Whether PDX persists type metadata to disk. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html#persistent--[`EnablePdx.persistent`]
| `pdx.read-serialized` | Whether a Region entry is returned as a `PdxInstance` or deserialized back into object form on read. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html#readSerialized--[`EnablePdx.readSerialized`]
| `pdx.serialize-bean-name` | The name of a custom Spring bean that implements `org.apache.geode.pdx.PdxSerializer`. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePdx.html#serializerBeanName--[`EnablePdx.serializerBeanName`]
|=====================================================================================================================
.`spring.data.gemfire.*` Pool properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `pool.free-connection-timeout` | The timeout used to acquire a free connection from a Pool. | `10000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#freeConnectionTimeout--[`EnablePool.freeConnectionTimeout`]
| `pool.idle-timeout` | The amount of time a connection can be idle before expiring (and closing) the connection. | `5000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#idleTimeout--[`EnablePool.idleTimeout`]
| `pool.load-conditioning-interval` | The interval for how frequently the Pool checks to see if a connection to a given server should be moved to a different server to improve the load balance. | `300000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#loadConditioningInterval--[`EnablePool.loadConditioningInterval`]
| `pool.locators` | Comma-delimited list of locator endpoints in the format of `locator1[port1],...,locatorN[portN]` | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#locators--[`EnablePool.locators`]
| `pool.max-connections` | The maximum number of client to server connections that a Pool will create. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#maxConnections--[EnablePool.maxConnections]
| `pool.min-connections` | The minimum number of client to server connections that a Pool maintains. | `1` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#minConnections--[`EnablePool.minConnections`]
| `pool.multi-user-authentication` | Whether the created Pool can be used by multiple authenticated users. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#multiUserAuthentication--[`EnablePool.multiUserAuthentication`]
| `pool.ping-interval` | How often to ping servers to verify that they are still alive. | `10000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#pingInterval--[`EnablePool.pingInterval`]
| `pool.pr-single-hop-enabled` | Whether to perform single-hop data access operations between the client and servers. When `true`, the client is aware of the location of partitions on servers that host Regions with `DataPolicy.PARTITION`. | `true` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#prSingleHopEnabled--[`EnablePool.prSingleHopEnabled`]
| `pool.read-timeout` | The number of milliseconds to wait for a response from a server before timing out the operation and trying another server (if any are available). | `10000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#readTimeout--[`EnablePool.readTimeout`]
| `pool.ready-for-events` | Whether to signal the server that the client is prepared and ready to receive events. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#readyForEvents--[`ClientCacheApplication.readyForEvents`]
| `pool.retry-attempts` | The number of times to retry a request after timeout/exception. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#retryAttempts--[`EnablePool.retryAttempts`]
| `pool.server-group` | The group that all servers to which a Pool connects must belong. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#serverGroup--[`EnablePool.serverGroup`]
| `pool.servers` | Comma-delimited list of `CacheServer` endpoints in the format of `server1[port1],...,serverN[portN]` | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#servers--[`EnablePool.servers`]
| `pool.socket-buffer-size` | The socket buffer size for each connection made in all Pools. | `32768` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#socketBufferSize--[`EnablePool.socketBufferSize`]
| `pool.statistic-interval` | How often to send client statistics to the server. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#statisticInterval--[`EnablePool.statisticInterval`]
| pool.subscription-ack-interval | The interval in milliseconds to wait before sending acknowledgements to the `CacheServer` for events received from the server subscriptions. | `100` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#subscriptionAckInterval--[`EnablePool.subscriptionAckInterval`]
| `pool.subscription-enabled` | Whether the created Pool has server-to-client subscriptions enabled. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#subscriptionEnabled--[`EnablePool.subscriptionEnabled`]
| `pool.subscription-message-tracking-timeout` | The `messageTrackingTimeout` attribute, which is the time-to-live period, in milliseconds, for subscription events the client has received from the server. | `900000` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#subscriptionMessageTrackingTimeout--[`EnablePool.subscriptionMessageTrackingTimeout`]
| `pool.subscription-redundancy` | The redundancy level for all Pools server-to-client subscriptions. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#subscriptionRedundancy--[`EnablePool.subsriptionRedundancy`]
| `pool.thread-local-connections` | The thread local connections policy for all Pools. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnablePool.html#threadLocalConnections--[`EnablePool.threadLocalConnections`]
|=====================================================================================================================
.`spring.data.gemfire.*` Security properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `security.username` | The name of the user used to authenticate with the servers. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#securityUsername--[`EnableSecurity.securityUsername`]
| `security.password` | The user password used to authenticate with the servers. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#securityPassword--[`EnableSecurity.securityPassword`]
| `security.properties-file` | The system pathname to a properties file that contains security credentials. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#securityPropertiesFile--[`EnableAuth.propertiesFile`]
| `security.client.accessor` | X | X | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#clientAccessor--[`EnableAuth.clientAccessor`]
| `security.client.accessor-post-processor` | The callback that should be invoked in the post-operation phase, which is when the operation has completed on the server but before the result is sent to the client. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#clientAccessorPostProcessor--[`EnableAuth.clientAccessorPostProcessor`]
| `security.client.authentication-initializer` | Static creation method that returns an `AuthInitialize` object, which obtains credentials for peers in a cluster. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#clientAuthenticationInitializer--[`EnableSecurity.clientAuthentiationInitializer`]
| `security.client.authenticator` | Static creation method that returns an `Authenticator` object used by a cluster member (Locator or Server) to verify the credentials of a connecting client. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#clientAuthenticator--[`EnableAuth.clientAuthenticator`]
| `security.client.diffie-hellman-algorithm` | Used for authentication. For secure transmission of sensitive credentials (such as passwords), you can encrypt the credentials by using the Diffie-Hellman key-exchange algorithm. You can do so by setting the `security-client-dhalgo` system property on the clients to the name of a valid, symmetric key cipher supported by the JDK. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#clientDiffieHellmanAlgorithm--[`EnableAuth.clientDiffieHellmanAlgorithm`]
| `security.log.file` | The pathname to a log file used for security log messages. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#securityLogFile--[`EnableAuth.securityLogFile`]
| `security.log.level` | The log level for security log messages. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#securityLogLevel--[`EnableAuth.securityLogLevel`]
| `security.manager.class-name` | The name of a class that implements `org.apache.geode.security.SecurityManager`. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#securityManagerClassName--[`EnableSecurity.securityManagerClassName`]
| `security.peer.authentication-initializer` | Static creation method that returns an `AuthInitialize` object, which obtains credentials for peers in a cluster. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#peerAuthenticationInitializer--[`EnableSecurity.peerAuthenticationInitializer`]
| `security.peer.authenticator` | Static creation method that returns an `Authenticator` object, which is used by a peer to verify the credentials of a connecting node. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#peerAuthenticator--[`EnableAuth.peerAuthenticator`]
| security.peer.verify-member-timeout | The timeout in milliseconds used by a peer to verify membership of an unknown authenticated peer requesting a secure connection. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableAuth.html#peerVerifyMemberTimeout--[`EnableAuth.peerVerifyMemberTimeout`]
| `security.post-processor.class-name` | The name of a class that implements the `org.apache.geode.security.PostProcessor` interface that can be used to change the returned results of Region get operations. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#securityPostProcessorClassName--[`EnableSecurity.securityPostProcessorClassName`]
| `security.shiro.ini-resource-path` | The {geode-name} System property that refers to the location of an Apache Shiro INI file that configures the Apache Shiro Security Framework in order to secure {geode-name}. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSecurity.html#shiroIniResourcePath--[`EnableSecurity.shiroIniResourcePath`]
|=====================================================================================================================
.`spring.data.gemfire.*` SSL properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `security.ssl.certificate.alias.cluster` | The alias to the stored SSL certificate used by the cluster to secure communications. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.certificate.alias.default-alias` | The default alias to the stored SSL certificate used to secure communications across the entire {geode-name} system. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#defaultCertificateAlias--[`EnableSsl.defaultCertificateAlias`]
| `security.ssl.certificate.alias.gateway` | The alias to the stored SSL certificate used by the WAN Gateway Senders/Receivers to secure communications. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.certificate.alias.jmx` | The alias to the stored SSL certificate used by the Manager's JMX-based JVM MBeanServer and JMX clients to secure communications. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.certificate.alias.locator` | The alias to the stored SSL certificate used by the Locator to secure communications. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.certificate.alias.server` | The alias to the stored SSL certificate used by clients and servers to secure communications. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.certificate.alias.web` | The alias to the stored SSL certificate used by the embedded HTTP server to secure communications (HTTPS). | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#componentCertificateAliases--[`EnableSsl.componentCertificateAliases`]
| `security.ssl.ciphers` | Comma-separated list of SSL ciphers or `any`. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#ciphers--[`EnableSsl.ciphers`]
| `security.ssl.components` | Comma-delimited list of {geode-name} components (for example, WAN) to be configured for SSL communication. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#components--[`EnableSsl.components`]
| `security.ssl.keystore` | The system pathname to the Java KeyStore file storing certificates for SSL. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#keystore--[`EnableSsl.keystore`]
| `security.ssl.keystore.password` | The password used to access the Java KeyStore file. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#keystorePassword--[`EnableSsl.keystorePassword`]
| `security.ssl.keystore.type` | The password used to access the Java KeyStore file (for example, JKS). | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#keystoreType--[`EnableSsl.keystoreType`]
| `security.ssl.protocols` | Comma-separated list of SSL protocols or `any`. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#protocols--[`EnableSsl.protocols`]
| `security.ssl.require-authentication` | Whether two-way authentication is required. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#requireAuthentication--[`EnableSsl.requireAuthentication`]
| `security.ssl.truststore` | The system pathname to the trust store (Java KeyStore file) that stores certificates for SSL. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#truststore--[`EnableSsl.truststore`]
| `security.ssl.truststore.password` | The password used to access the trust store (Java KeyStore file). | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#truststorePassword--[`EnableSsl.truststorePassword`]
| `security.ssl.truststore.type` | The password used to access the trust store (Java KeyStore file -- for example, JKS). | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#truststoreType--[`EnableSsl.truststoreType`]
| `security.ssl.web-require-authentication` | Whether two-way HTTP authentication is required. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html#webRequireAuthentication--[`EnableSsl.webRequireAuthentication`]
|=====================================================================================================================
.`spring.data.gemfire.*` Service properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `service.http.bind-address` | The IP address or hostname of the system NIC used by the embedded HTTP server to bind and listen for HTTP(S) connections. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableHttpService.html#bindAddress--[`EnableHttpService.bindAddress`]
| `service.http.port` | The port used by the embedded HTTP server to listen for HTTP(S) connections. | `7070` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableHttpService.html#port--[`EnableHttpService.port`]
| `service.http.ssl-require-authentication` | Whether two-way HTTP authentication is required. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableHttpService.html#sslRequireAuthentication--[`EnableHttpService.sslRequireAuthentication`]
| `service.http.dev-rest-api-start` | Whether to start the Developer REST API web service. A full installation of {geode-name} is required, and you must set the `$GEODE` environment variable. | `false` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableHttpService.html#startDeveloperRestApi--[`EnableHttpService.startDeveloperRestApi`]
| `service.memcached.port` | The port of the embedded Memcached server (service). | `11211`| {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableMemcachedServer.html#port--[`EnableMemcachedServer.port`]
| `service.memcached.protocol` | The protocol used by the embedded Memcached server (service). | `ASCII` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableMemcachedServer.html#protocol--[`EnableMemcachedServer.protocol`]
| `service.redis.bind-address` | The IP address or hostname of the system NIC used by the embedded Redis server to bind and listen for connections. | | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableRedisServer.html#bindAddress--[`EnableRedis.bindAddress`]
| `service.redis.port` | The port used by the embedded Redis server to listen for connections. | `6479` | {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableRedisServer.html#port--[`EnableRedisServer.port`]
|=====================================================================================================================
[[geode-configuration-metadata-springsession]]
=== Spring Session Based Properties
The following properties all have a `spring.session.data.gemfire.*` prefix. For example, to set the session Region name,
set `spring.session.data.gemfire.session.region.name` in Spring Boot `application.properties`.
.`spring.session.data.gemfire.*` properties
[width="90%",options="header"]
|=====================================================================================================================
| Name | Description | Default | From
| `cache.client.pool.name` | Name of the pool used to send data access operations between the client and servers. | `gemfirePool` | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#poolName--[`EnableGemFireHttpSession.poolName`]
| `cache.client.Region.shortcut` | The `DataPolicy` used by the client Region to manage (HTTP) session state. | {apache-geode-javadoc}/org/apache/geode/cache/client/ClientRegionShortcut.html#PROXY[`ClientRegionShortcut.PROXY`] | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#clientRegionShortcut--[`EnableGemFireHttpSession.clientRegionShortcut`]
| `cache.server.Region.shortcut` | The `DataPolicy` used by the server Region to manage (HTTP) session state. | {apache-geode-javadoc}/org/apache/geode/cache/RegionShortcut.html#PARTITION[`RegionShortcut.PARTITION`] | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#serverRegionShortcut--[`EnableGemFireHttpSession.serverRegionShortcut`]
| `session.attributes.indexable` | The names of session attributes for which an Index is created. | `[]` | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#indexableSessionAttributes--[`EnableGemFireHttpSession.indexableSessionAttributes`]
| `session.expiration.max-inactive-interval-seconds` | Configures the number of seconds in which a session can remain inactive before it expires. | `1800` | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#maxInactiveIntervalSeconds--[`EnableGemFireHttpSession.maxInactiveIntervalSeconds`]
| `session.Region.name` | The name of the (client/server) Region used to manage (HTTP) session state. | `ClusteredSpringSessions` | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#RegionName--[`EnableGemFireHttpSession.RegionName`]
| `session.serializer.bean-name` | The name of a Spring bean that implements `org.springframework.session.data.gemfire.serialization.SessionSerializer`. | | {spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.html#sessionSerializerBeanName--[`EnableGemFireHttpSession.sessionSerializerBeanName`]
|=====================================================================================================================
[[geode-configuration-metadata-apachegeode]]
=== {geode-name} Properties
While we do not recommend using {geode-name} properties directly in your Spring applications, SBDG does not prevent you
from doing so. See the {apache-geode-docs}/reference/topics/gemfire_properties.html[complete reference to the {geode-name} specific properties].
WARNING: {geode-name} is very strict about the properties that may be specified in a `gemfire.properties` file.
You cannot mix Spring properties with `gemfire.*` properties in an {geode-name} `gemfire.properties` file.

View File

@@ -0,0 +1,90 @@
[[geode-continuous-query]]
== Continuous Query
:geode-name: {apache-geode-name}
Some applications must process a stream of events as they happen and intelligently react in (near) real-time to
the countless changes in the data over time. Those applications need frameworks that can make processing a stream
of events as they happen as easy as possible.
Spring Boot for {geode-name} does just that, without users having to perform any complex setup or configure any
necessary infrastructure components to enable such functionality. Developers can define the criteria for the
data of interest and implement a handler (listener) to process the stream of events as they occur.
{apache-geode-docs}/developing/continuous_querying/chapter_overview.html[Continuous Query (CQ)] lets you
easily define your criteria for the data you need. With CQ, you can express the criteria that match the data you need
by specifying a query predicate. {geode-name} implements the
{apache-geode-docs}/developing/querying_basics/query_basics.html[Object Query Language (OQL)]
for defining and executing queries. OQL resembles SQL and supports projections, query predicates, ordering,
and aggregates. Also, when used in CQs, they execute continuously, firing events when the data changes in such ways
as to match the criteria expressed in the query predicate.
Spring Boot for {geode-name} combines the ease of identifying the data you need by using an OQL query statement with
implementing the listener callback (handler) in one easy step.
For example, suppose you want to perform some follow-up action when a customer's financial loan application is either
approved or denied.
First, the application model for our `EligibilityDecision` class might look something like the following:
.EligibilityDecision class
====
[source,java]
----
@Region("EligibilityDecisions")
class EligibilityDecision {
private final Person person;
private Status status = Status.UNDETERMINED;
private final Timespan timespan;
enum Status {
APPROVED,
DENIED,
UNDETERMINED,
}
}
----
====
Then we can implement and declare our CQ event handler methods to be notified when an eligibility decision is either
`APPROVED` or `DENIED`:
====
[source,java]
----
@Component
class EligibilityDecisionPostProcessor {
@ContinuousQuery(name = "ApprovedDecisionsHandler",
query = "SELECT decisions.*
FROM /EligibilityDecisions decisions
WHERE decisions.getStatus().name().equalsIgnoreCase('APPROVED')")
public void processApprovedDecisions(CqEvent event) {
// ...
}
@ContinuousQuery(name = "DeniedDecisionsHandler",
query = "SELECT decisions.*
FROM /EligibilityDecisions decisions
WHERE decisions.getStatus().name().equalsIgnoreCase('DENIED')")
public void processDeniedDecisions(CqEvent event) {
// ...
}
}
----
====
Thus, when eligibility is processed and a decision has been made, either approved or denied, our application gets
notified, and as an application developer, you are free to code your handler and respond to the event any way you like.
Also, because our Continuous Query (CQ) handler class is a component (or a bean in the Spring `ApplicationContext`)
you can auto-wire any other beans necessary to carry out the application's intended function.
This is not unlike Spring's {spring-framework-docs}/integration.html#jms-annotated[annotation-driven listener endpoints],
which are used in (JMS) message listeners and handlers, except in Spring Boot for {geode-name}, you need not do anything
special to enable this functionality. You can declare the `@ContinuousQuery` annotation on any POJO method and go to
work on other things.

View File

@@ -0,0 +1,153 @@
[[geode-data-serialization]]
== Data Serialization with PDX
:geode-name: {apache-geode-name}
Anytime data is overflowed or persisted to disk, transferred between clients and servers, transferred between peers
in a cluster or between different clusters in a multi-site WAN topology, all data stored in {geode-name} must be
serializable.
To serialize objects in Java, object types must implement the `java.io.Serializable` interface. However, if you have a
large number of application domain object types that currently do not implement `java.io.Serializable`, refactoring
hundreds or even thousands of class types to implement `java.io.Serializable` would be a tedious task just to store
and manage those objects in {geode-name}.
Additionally, it is not only your application domain object types you necessarily need to consider. If you used
third-party libraries in your application domain model, any types referred to by your application domain object types
stored in {geode-name} must also be serializable. This type explosion may bleed into class types for which you may have
no control over.
Furthermore, Java serialization is not the most efficient format, given that metadata about your types is stored with
the data itself. Therefore, even though Java serialized bytes are more descriptive, it adds a great deal of overhead.
Then, along came serialization using {geode-name}'s
{apache-geode-docs}/developing/data_serialization/gemfire_pdx_serialization.html[PDX] format.
PDX stands for Portable Data Exchange and achieves four goals:
* Separates type metadata from the data itself, streamlining the bytes during transfer. {geode-name} maintains a type
registry that stores type metadata about the objects serialized with PDX.
* Supports versioning as your application domain types evolve. It is common to have old and new versions of the same
application deployed to production, running simultaneously, sharing data, and possibly using different versions of the
same domain types. PDX lets fields be added or removed while still preserving interoperability between old and new
application clients without loss of data.
* Enables objects stored as PDX to be queried without being de-serialized. Constant serialization and deserialization of
data is a resource-intensive task that adds to the latency of each data request when redundancy is enabled. Since data
is replicated across peers in the cluster to preserve High Availability (HA) and must be serialized to be transferred,
keeping data serialized is more efficient when data is updated frequently, since it is likely the data will need to be
transferred again in order to maintain consistency in the face of redundancy and availability.
* Enables interoperability between native language clients (such as C, C++ and C#) and Java language clients, with each
being able to access the same data set regardless from where the data originated.
However, PDX does have limitations.
For instance, unlike Java serialization, PDX does not handle cyclic dependencies. Therefore, you must be careful how you
structure and design your application domain object types.
Also, PDX cannot handle field type changes.
Furthermore, while {geode-name}'s general {apache-geode-docs}/developing/data_serialization/gemfire_data_serialization.html[Data Serialization]
handles {apache-geode-docs}/developing/delta_propagation/chapter_overview.html[Deltas], this is not achievable without
de-serializing the object, since it involves a method invocation, which defeats one of the key benefits of PDX:
preserving format to avoid the cost of serialization and deserialization.
However, we think the benefits of using PDX outweigh the limitations and, therefore, have enabled PDX by default.
You need do nothing special. You can code your domain types and rest assured that objects of those domain types are
properly serialized when overflowed and persisted to disk, transferred between clients and servers, transferred between
peers in a cluster, and even when data is transferred over the network when you use {geode-name}'s multi-site WAN
topology.
.EligibilityDecision is automatically serialiable without implementing Java Serializable.
====
[source,java]
----
@Region("EligibilityDecisions")
class EligibilityDecision {
// ...
}
----
====
NOTE: {geode-name} does {apache-geode-docs}/developing/data_serialization/java_serialization.html[support] the standard
Java Serialization format.
=== SDG `MappingPdxSerializer` vs. {geode-name}'s `ReflectionBasedAutoSerializer`
Under-the-hood, Spring Boot for {geode-name} {spring-data-geode-docs-html}/#bootstrap-annotation-config-pdx[enables]
and uses Spring Data for {geode-name}'s
{spring-data-geode-javadoc}/org/springframework/data/gemfire/mapping/MappingPdxSerializer.html[`MappingPdxSerializer`]
to serialize your application domain objects with PDX.
TIP: See the SDG {spring-data-geode-docs-html}/#mapping.pdx-serializer[Reference Guide] for more details
on the `MappingPdxSerializer` class.
The `MappingPdxSerializer` class offers several advantages above and beyond {geode-name}'s own
{apache-geode-javadoc}/org/apache/geode/pdx/ReflectionBasedAutoSerializer.html[`ReflectionBasedAutoSerializer`] class.
TIP: See {geode-name}'s {apache-geode-docs}/developing/data_serialization/auto_serialization.html[User Guide]
for more details about the `ReflectionBasedAutoSerializer`.
The SDG `MappingPdxSerializer` class offers the following benefits and capabilities:
* PDX serialization is based on Spring Data's powerful mapping infrastructure and metadata.
* Includes support for both `includes` and `excludes` with first-class
{spring-data-geode-docs-html}/#mapping.pdx-serializer.type-filtering[type filtering].
Additionally, you can implement type filters by using Java's `java.util.function.Predicate` interface as opposed to
the limited regex capabilities provided by {geode-name}'s `ReflectionBasedAutoSerializer` class. By default,
`MappingPdxSerializer` excludes all types in the following packages: `java`, `org.apache.geode`, `org.springframework`
and `com.gemstone.gemfire`.
* Handles {spring-data-geode-docs-html}/#mapping.pdx-serializer.transient-properties[transient object fields
and properties] when either Java's `transient` keyword or Spring Data's `@Transient` annotation is used.
* Handles {spring-data-geode-docs-html}/#mapping.pdx-serializer.read-only-properties[read-only object properties].
* Automatically determines the identifier of your entities when you annotate the appropriate entity field or property
with Spring Data's {spring-data-commons-javadoc}/org/springframework/data/annotation/Id.html[`@Id`] annotation.
* Lets additional `o.a.g.pdx.PdxSerializers` be registered to
{spring-data-geode-docs-html}/#mapping.pdx-serializer.custom-serialization[customize the serialization]
of nested entity/object field and property types.
The support for `includes` and `excludes` deserves special attention, since the `MappingPdxSerializer` excludes all Java,
Spring, and {geode-name} types, by default. However, what happens when you need to serialize one of those types?
For example, suppose you need to serialize objects of type `java.security.Principal`. Then you can override the excludes
by registering an `include` type filter:
====
[source,java]
----
package example.app;
import java.security.Principal;
@SpringBootApplication
@EnablePdx(serializerBeanName = "myCustomMappingPdxSerializer")
class SpringBootApacheGeodeClientCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeClientCacheApplication.class, args);
}
@Bean
MappingPdxSerializer myCustomMappingPdxSerializer() {
MappingPdxSerializer customMappingPdxSerializer =
MappingPdxSerializer.newMappginPdxSerializer();
customMappingPdxSerializer.setIncludeTypeFilters(
type -> Principal.class.isAssignableFrom(type));
return customMappingPdxSerializer;
}
}
----
====
TIP: Normally, you need not explicitly declare SDG's `@EnablePdx` annotation to enable and configure PDX. However,
if you want to override auto-configuration, as we have demonstrated above, you must do this.

View File

@@ -0,0 +1,756 @@
[[geode-data-using]]
== Using Data
:geode-name: {apache-geode-name}
One of the most important tasks during development is ensuring your Spring Boot application handles data correctly.
To verify the accuracy, integrity, and availability of your data, your application needs data with which to work.
For those of you already familiar with Spring Boot's support for
{spring-boot-docs-html}/howto.html#howto-initialize-a-database-using-spring-jdbc[SQL database initialization],
the approach when using {geode-name} should be easy to understand.
{geode-name} provides built-in support, similar in function to Spring Boot's SQL database initialization, by using:
* Gfsh's {apache-geode-docs}/tools_modules/gfsh/quick_ref_commands_by_area.html#topic_C7DB8A800D6244AE8FF3ADDCF139DCE4[import/export] data commands.
* {apache-geode-docs}/managing/cache_snapshots/chapter_overview.html[Snapshot service]
* {apache-geode-docs}/developing/storing_data_on_disk/chapter_overview.html[Persistence] with {apache-geode-docs}/managing/disk_storage/chapter_overview.html[disk storage]
For example, by enabling persistence with disk storage, you could
{apache-geode-docs}/managing/disk_storage/backup_restore_disk_store.html[backup and restore]
persistent `DiskStore` files from one cluster to another.
Alternatively, using {geode-name}'s Snapshot Service, you can export data contained in targeted `Regions` from one
cluster during shutdown and import the data into another cluster on startup. The Snapshot Service lets you filter data
while it is being imported and exported.
Finally, you can use {geode-name} shell (Gfsh) commands to
{spring-data-geode-docs-html}/tools_modules/gfsh/command-pages/export.html#topic_263B70069BFC4A7185F86B3272011734[export data]
and {apache-geode-docs}/tools_modules/gfsh/command-pages/import.html#topic_jw2_2ld_2l[import data].
TIP: Spring Data for {geode-name} (SDG) contains dedicated support for
{spring-data-geode-docs-html}/#bootstrap:region:persistence[persistence]
and the {spring-data-geode-docs-html}/#bootstrap:snapshot[Snapshot Service].
In all cases, the files generated by persistence, the Snapshot Service and Gfsh's `export` command are in a proprietary
binary format.
Furthermore, none of these approaches are as convenient as Spring Boot's database initialization automation. Therefore,
Spring Boot for {geode-name} (SBDG) offers support to import data from JSON into {geode-name} as PDX.
Unlike Spring Boot, SBDG offers support to export data as well. By default, data is imported and exported in JSON format.
NOTE: SBDG does not provide an equivalent to Spring Boot's `schema.sql` file. The best way to define the data structures
(the `Region` instances) that manage your data is with SDG's annotation-based configuration support for defining cache
`Region` instances from your application's {spring-data-geode-docs-html}/#bootstrap-annotation-config-regions[entity classes]
or indirectly from Spring and JSR-107 or JCache {spring-data-geode-docs-html}/#bootstrap-annotation-config-caching[caching annotations].
TIP: See SBDG's <<geode-configuration-declarative-annotations-productivity-regions,documentation>> on the same.
WARNING: While this feature works and many edge cases were thought through and tested thoroughly, there are still some
limitations that need to be ironed out. See https://github.com/spring-projects/spring-boot-data-geode/issues/82[issue-82]
and https://github.com/spring-projects/spring-boot-data-geode/issues/83[issue-83] for more details. The Spring team
strongly recommends that this feature be used only for development and testing purposes.
[[geode-data-using-import]]
=== Importing Data
You can import data into a `Region` by defining a JSON file that contain the JSON objects you wish to load. The JSON
file must follow a predefined naming convention and be placed in the root of your application classpath:
`data-<regionName>.json`
NOTE: `<regionName>` refers to the lowercase "name" of the `Region`, as defined by
{apache-geode-javadoc}/org/apache/geode/cache/Region.html#getName--[`Region.getName()`].
For example, if you have a `Region` named "Orders", you would create a JSON file called `data-orders.json` and place it
in the root of your application classpath (for example, in `src/test/resources`).
Create JSON files for each `Region` that is implicitly defined (for example, by using `@EnableEntityDefinedRegions`)
or explicitly defined (with `ClientRegionFactoryBean` in Java configuration) in your Spring Boot application
configuration that you want to load with data.
The JSON file that contains JSON data for the "Orders" `Region` might appear as follows:
.`data-orders.json`
====
[source,json]
----
[{
"@type": "example.app.pos.model.PurchaseOrder",
"id": 1,
"lineItems": [
{
"@type": "example.app.pos.model.LineItem",
"product": {
"@type": "example.app.pos.model.Product",
"name": "Apple iPad Pro",
"price": 1499.00,
"category": "SHOPPING"
},
"quantity": 1
},
{
"@type": "example.app.pos.model.LineItem",
"product": {
"@type": "example.app.pos.model.Product",
"name": "Apple iPhone 11 Pro Max",
"price": 1249.00,
"category": "SHOPPING"
},
"quantity": 2
}
]
}, {
"@type": "example.app.pos.model.PurchaseOrder",
"id": 2,
"lineItems": [
{
"@type": "example.app.pos.model.LineItem",
"product": {
"@type": "example.app.pos.model.Product",
"name": "Starbucks Vente Carmel Macchiato",
"price": 5.49,
"category": "SHOPPING"
},
"quantity": 1
}
]
}]
----
====
The application entity classes that matches the JSON data from the JSON file might look something like the following
listing:
.Point-of-Sale (POS) Application Domain Model Classes
====
[source,java]
----
@Region("Orders")
class PurchaseOrder {
@Id
Long id;
List<LineItem> lineItems;
}
class LineItem {
Product product;
Integer quantity;
}
@Region("Products")
class Product {
String name;
Category category;
BigDecimal price;
}
----
====
As the preceding listings show, the object model and corresponding JSON can be arbitrarily complex with a hierarchy of
objects that have complex types.
[[geode-data-using-import-metadata]]
==== JSON metadata
We want to draw your attention to a few other details contained in the object model and JSON shown
<<geode-data-using-import,earlier>>.
[[geode-data-using-import-metadata-attype]]
===== The `@type` metadata field
First, we declared a `@type` JSON metadata field. This field does not map to any specific field or property of
the application domain model class (such as `PurchaseOrder`). Rather, it tells the framework and {geode-name}'s JSON/PDX
converter the type of object the JSON data would map to if you were to request an object (by calling
`PdxInstance.getObject()`).
Consider the following example:
.Deserializing PDX as an Object
====
[source,java]
----
@Repository
class OrdersRepository {
@Resource(name = "Orders")
Region<Long, PurchaseOrder> orders;
PurchaseOrder findBy(Long id) {
Object value = this.orders.get(id);
return value instanceof PurchaseOrder ? (PurchaseOrder) value
: value instanceof PdxInstance ? ((PdxInstance) value).getObject()
: null;
}
}
----
====
Basically, the `@type` JSON metadata field informs the `PdxInstance.getObject()` method about the type of Java object
to which the JSON object maps. Otherwise, the `PdxInstance.getObject()` method would silently return a `PdxInstance`.
It is possible for {geode-name}'s PDX serialization framework to return a `PurchaseOrder` from `Region.get(key)` as well,
but it depends on the value of PDX's `read-serialized`, cache-level configuration setting, among other factors.
NOTE: When JSON is imported into a `Region` as PDX, the
{apache-geode-javadoc}/org/apache/geode/pdx/PdxInstance.html#getClassName--[`PdxInstance.getClassName()`]
does not refer to a valid Java class. It is
{apache-geode-javadoc}/org/apache/geode/pdx/JSONFormatter.html#JSON_CLASSNAME[`JSONFormatter.JSON_CLASSNAME`].
As a result, `Region` data access operations, such as `Region.get(key)`, return a `PdxInstance` and not a Java object.
TIP: You may need to proxy `Region` read data access operations (such as `Region.get(key)`) by setting the SBDG property
`spring.boot.data.gemfire.cache.region.advice.enabled` to `true`. When this property is set, `Region` instances are
proxied to wrap a `PdxInstance` in a `PdxInstanceWrapper` to appropriately handle the `PdxInstance.getObject()` call
in your application code.
[[geode-data-using-import-metadata-id]]
===== The `id` field and the `@identifier` metadata field
Top-level objects in your JSON must have an identifier, such as an `id` field. This identifier is used as the identity
and key of the object (or `PdxInstance`) when stored in the `Region` (for example, `Region.put(key, object)`).
You may have noticed that the JSON for the "Orders" `Region` shown earlier declared an `id` field as the identifier:
.PurchaseOrder identifier ("id")
====
[source,text]
----
[{
"@type": "example.app.pos.model.PurchaseOrder",
"id": 1,
...
----
====
This follows the same convention used in Spring Data. Typically, Spring Data mapping infrastructure looks for a POJO
field or property annotated with {spring-data-commons-javadoc}/org/springframework/data/annotation/Id.html[`@Id`]. If no
field or property is annotated with `@Id`, the framework falls back to searching for a field or property named `id`.
In Spring Data for {geode-name}, this `@Id`-annotated or `id`-named field or property is used as the identifier
and as the key for the object when storing it into a `Region`.
However, what happens when an object or entity does not have a surrogate ID defined? Perhaps the application domain
model class is appropriately using natural identifiers, which is quite common in practice.
Consider a `Book` class defined as follows:
.Book class
====
[source,java]
----
@Region("Books")
class Book {
Author author;
@Id
ISBN isbn;
LocalDate publishedDate;
Sring title;
}
----
====
As declared in the `Book` class, the identifier for `Book` is its `ISBN`, since the `isbn` field was annotated with
Spring Data's `@Id` mapping annotation. However, we cannot know this by searching for an `@Id` annotation in JSON.
You might be tempted to argue that if the `@type` metadata field is set, we would know the class type and could load
the class definition to learn about the identifier. That is all fine until the class is not actually on the application
classpath in the first place. This is one of the reasons why SBDG's JSON support serializes JSON to {geode-name}'s PDX
format. There might not be a class definition, which would lead to a `NoClassDefFoundError` or `ClassNotFoundException`.
So, what then?
In this case, SBDG lets you declare the `@identifier` JSON metadata field to inform the framework what to use as
the identifier for the object.
Consider the following example:
.Using "@identifer"
====
[source,json]
----
{
"@type": "example.app.books.model.Book",
"@identifier": "isbn",
"author": {
"id": 1,
"name": "Josh Long"
},
"isbn": "978-1-449-374640-8",
"publishedDate": "2017-08-01",
"title": "Cloud Native Java"
}
----
====
The `@identifier` JSON metadata field informs the framework that the `isbn` field is the identifier for a `Book`.
[[geode-data-using-import-conditional]]
==== Conditionally Importing Data
While the Spring team recommends that users should only use this feature when developing and testing their Spring Boot
applications with {geode-name}, you may still occasionally use this feature in production.
You might use this feature in production to preload a (REPLICATE) Region with reference data. Reference data is largely
static, infrequently changing, and non-transactional. Preloading reference data is particularly useful when you want to
warm the cache.
When you use this feature for development and testing purposes, you can put your `Region`-specific JSON files in
`src/test/resources`. This ensures that the files are not included in your application artifact (such as a JAR or WAR)
when built and deployed to production.
However, if you must use this feature to preload data in your production environment, you can still conditionally load
data from JSON. To do so, configure the `spring.boot.data.gemfire.cache.data.import.active-profiles` property set to
the Spring profiles that must be active for the import to take effect.
Consider the following example:
.Conditional Importing JSON
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.import.active-profiles=DEV, QA
----
====
For import to have an effect in this example, you must specifically set the `spring.profiles.active` property to one of
the valid, `active-profiles` listed in the import property (such as `QA`). Only one needs to match.
NOTE: There are many ways to conditionally build application artifacts. You might prefer to handle this concern in your
Gradle or Maven build.
[[geode-data-using-export]]
=== Exporting Data
Certain data stored in your application's `Regions` may be sensitive or confidential, and keeping the data secure is of
the utmost concern and priority. Therefore, exporting data is *disabled* by default.
However, if you use this feature for development and testing purposes, enabling the export capability may be useful to
move data from one environment to another. For example, if your QA team finds a bug in the application that uses a
particular data set, they can export the data and pass it back to the development team to import in their local
development environment to help debug the issue.
To enable export, set the `spring.boot.data.gemfire.cache.data.export.enabled` property to `true`:
.Enable Export
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.export.enabled=true
----
====
SBDG is careful to export data to JSON in a format that {geode-name} expects on import and includes things such as
`@type` metadata fields.
WARNING: The `@identifier` metadata field is not generated automatically. While it is possible for POJOs stored in a
`Region` to include an `@identifier` metadata field when exported to JSON, it is not possible when the `Region` value
is a `PdxInstance` that did not originate from JSON. In this case, you must manually ensure that the `PdxInstance`
includes an `@identifier` metadata field before it is exported to JSON if necessary (for example, `Book.isbn`). This is
only necessary if your entity classes do not declare an explicit identifier field, such as with the `@Id` mapping
annotation, or do not have an `id` field. This scenario can also occur when inter-operating with native clients
that model the application domain objects differently and then serialize the objects by using PDX, storing them in
Regions on the server that are then later consumed by your Java-based, Spring Boot application.
WARNING: You may need to set the `-Dgemfire.disableShutdownHook` JVM System property to `true` before your Spring
Boot application starts up when using export. Unfortunately, this Java runtime shutdown hook is registered and enabled
in {geode-name} by default, which results in the cache and the Regions being closed before the SBDG Export
functionality can export the data, thereby resulting in a `CacheClosedException`. SBDG
{github-url}/spring-geode-autoconfigure/src/main/java/org/springframework/geode/boot/autoconfigure/DataImportExportAutoConfiguration.java#L173-L183[makes a best effort]
to disable the {geode-name} JVM shutdown hook when export is enabled, but it is at the mercy of the JVM `ClassLoader`,
since {geode-name}'s JVM shutdown hook
{apache-geode-src}/geode-core/src/main/java/org/apache/geode/distributed/internal/InternalDistributedSystem.java#L2185-L2223[registration]
is declared in a `static` initializer.
[[geode-data-using-import-export-api-extensions]]
=== Import/Export API Extensions
The API in SBDG for import and export functionality is separated into the following concerns:
* Data Format
* Resource Resolving
* Resource Reading
* Resource Writing
By breaking each of these functions apart into separate concerns, a developer can customize each aspect of the import
and export functions.
For example, you could import XML from the filesystem and then export JSON to a REST-based Web Service. By default, SBDG
imports JSON from the classpath and exports JSON to the filesystem.
However, not all environments expose a filesystem, such as cloud environments like PCF. Therefore, giving users control
over each aspect of the import and export processes is essential for performing the functions in any environment.
[[geode-data-using-import-export-api-extensions-data-format]]
==== Data Format
The primary interface to import data into a `Region` is `CacheDataImporter`.
`CacheDataImporter` is a `@FunctionalInterface` that extends Spring's
{spring-framework-javadoc}/org/springframework/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
interface to trigger the import of data after the `Region` has been initialized.
The interface is defined as follows:
.`CacheDataImporter`
====
[source,java]
----
interface CacheDataImporter extends BeanPostProcessor {
Region importInto(Region region);
}
----
====
You can code the `importInto(:Region)` method to handle any data format (JSON, XML, and others) you prefer. Register a
bean that implements the `CacheDataImporter` interface in the Spring container, and the importer does its job.
On the flip side, the primary interface to export data from a `Region` is the `CacheDataExporter`.
`CacheDataExporter` is a `@FunctionalInterface` that extends Spring's
{spring-framework-javadoc}/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.html[`DestructionAwareBeanPostProcessor`]
interface to trigger the export of data before the `Region` is destroyed.
The interface is defined as follows:
.`CacheDataExporter`
====
[source,java]
----
interface CacheDataExporter extends DestructionAwareBeanPostProcessor {
Region exportFrom(Region region);
}
----
====
You can code the `exportFrom(:Region)` method to handle any data format (JSON, XML, and others) you prefer. Register a
bean implementing the `CacheDataExporter` interface in the Spring container, and the exporter does its job.
For convenience, when you want to implement both import and export functionality, SBDG provides the
`CacheDataImporterExporter` interface, which extends both `CacheDataImporter` and `CacheDataExporter`:
.`CacheDataImporterExporter`
====
[source,java]
----
interface CacheDataImporterExporter extends CacheDataExporter, CacheDataImporter { }
----
====
For added support, SBDG also provides the `AbstractCacheDataImporterExporter` abstract base class to simplify
the implementation of your importer/exporter.
[[geode-data-using-import-export-api-extensions-data-format-lifecycle-management]]
===== Lifecycle Management
Sometimes, it is necessary to precisely control when data is imported or exported.
This is especially true on import, since different `Region` instances may be collocated or tied together through a
cache callback, such as a `CacheListener`. In these cases, the other `Region` may need to exist before the import
on the dependent `Region` proceeds, particularly if the dependencies were loosely defined.
Controlling the import is also important when you use SBDG's `@EnableClusterAware` annotation to push configuration
metadata from the client to the cluster in order to define server-side `Region` instances that match the client-side
`Region` instances, especially client `Region` instances targeted for import. The matching `Region` instances on the
server side must exist before data is imported into client (`PROXY`) `Region` instances.
In all cases, SBDG provides the `LifecycleAwareCacheDataImporterExporter` class to wrap your `CacheDataImporterExporter`
implementation. This class implements Spring's
{spring-framework-javadoc}/https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/SmartLifecycle.html[`SmartLifecycle`]
interface.
By implementing the `SmartLifecycle` interface, you can control in which `phase` of the Spring container the import
occurs. SBDG also exposes two more properties to control the lifecycle:
.Lifecycle Management Properties
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.import.lifecycle=[EAGER|LAZY]
spring.boot.data.gemfire.cache.data.import.phase=1000000
----
====
`EAGER` acts immediately, after the `Region` is initialized (the default behavior). `LAZY` delays the import until the
`start()` method is called, which is invoked according to the `phase`, thereby ordering the import relative to the other
lifecycle-aware components that are registered in the Spring container.
The following example shows how to make your `CacheDataImporterExporter` lifecycle-aware:
====
[source,java]
----
@Configuration
class MyApplicationConfiguration {
@Bean
CacheDataImporterExporter importerExporter() {
return new LifecycleAwareCacheDataImporterExporter(new MyCacheDataImporterExporter());
}
}
----
====
[[geode-data-using-import-export-api-extensions-resource-resolution]]
==== Resource Resolution
Resolving resources used for import and export results in the creation of a Spring
{spring-framework-javadoc}/https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/io/Resource.html[`Resource`]
handle.
Resource resolution is a vital step to qualifying a resource, especially if the resource requires special logic
or permissions to access it. In this case, specific `Resource` handles can be returned and used by the reader
and writer of the `Resource` as appropriate for import or export operation.
SBDG encapsulates the algorithm for resolving `Resources` in the `ResourceResolver`
(https://en.wikipedia.org/wiki/Strategy_pattern[Strategy]) interface:
.ResourceResolver
====
[source,java]
----
@FunctionalInterface
interface ResourceResolver {
Optional<Resource> resolve(String location);
default Resouce required(String location) {
// ...
}
}
----
====
Additionally, SBDG provides the `ImportResourceResolver` and `ExportResourceResolver` marker interfaces and the
`AbstractImportResourceResolver` and `AbstractExportResourceResolver` abstract base classes for implementing the
resource resolution logic used by both import and export operations.
If you wish to customize the resolution of `Resources` used for import or export, your `CacheDataImporterExporter`
implementation can extend the `ResourceCapableCacheDataImporterExporter` abstract base class, which provides the
aforementioned interfaces and base classes.
As stated earlier, SBDG resolves resources on import from the classpath and resources on export to the filesystem.
You can customize this behavior by providing an implementation of `ImportResourceResolver`, `ExportResourceResolver`,
or both interfaces and declare instances as beans in the Spring context:
.Import & Export ResourceResolver beans
====
[source,java]
----
@Configuration
class MyApplicationConfiguration {
@Bean
ImportResourceResolver importResourceResolver() {
return new MyImportResourceResolver();
}
@Bean
ExportResourceResolver exportResourceResolver() {
return new MyExportResourceResolver();
}
}
----
====
TIP: If you need to customize the resource resolution process for each location (or `Region`) on import or export,
you can use the https://en.wikipedia.org/wiki/Composite_pattern[Composite software design pattern].
[[geode-data-using-import-export-api-extensions-resource-resolution-default-customization]]
===== Customize Default Resource Resolution
If you are content with the provided defaults but want to target specific locations on the classpath or filesystem
used by the import or export, SBDG additionally provides the following properties:
.Import/Export Resource Location Properties
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.import.resource.location=...
spring.boot.data.gemfire.cache.data.export.resource.location=...
----
====
The properties accept any valid resource string, as specified in the Spring
{spring-framework-docs}/core.html#resources-resourceloader[documentation] (see *Table 10. Resource strings*).
This means that, even though import defaults from the classpath, you can change the location from classpath
to filesystem, or even network (for example, https://) by changing the prefix (or protocol).
Import/export resource location properties can refer to other properties through property placeholders, but SBDG
further lets you use SpEL inside the property values.
Consider the following example:
.Using SpEL
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.import.resource.location=\
https://#{#env['user.name']}:#{someBean.lookupPassword(#env['user.name'])}@#{host}:#{port}/cache/#{#regionName}/data/import
----
====
In this case, the import resource location refers to a rather sophisticated resource string by using a complex SpEL
expression.
SBDG populates the SpEL `EvaluationContext` with three sources of information:
* Access to the Spring `BeanFactory`
* Access to the Spring `Environment`
* Access to the current `Region`
Simple Java System properties or environment variables can be accessed with the following expression:
====
[source,text]
----
#{propertyName}
----
====
You can access more complex property names (including properties that use dot notation, such as the `user.home`
Java System property), directly from the `Environment` by using map style syntax as follows:
====
[source,text]
----
#{#env['property.name']}
----
====
The `#env` variable is set in the SpEL `EvaluationContext` to the Spring `Environment`.
Because the SpEL `EvaluationContext` is evaluated with the Spring `ApplicationContext` as the root object, you also have
access to the beans declared and registered in the Spring container and can invoke methods on them, as shown earlier
with `someBean.lookupPassword(..)`. `someBean` must be the name of the bean as declared and registered in the Spring
container.
CAUTION: Be careful when accessing beans declared in the Spring container with SpEL, particularly when using `EAGER`
import, as it may force those beans to be eagerly (or even prematurely) initialized.
SBDG also sets the `#regionName` variable in the `EvaluationContext` to the name of the `Region`, as determined by
{apache-geode-javadoc}/https://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/Region.html#getName--[`Region.getName()`],
targeted for import and export.
This lets you not only change the location of the resource but also change the resource name (such as a filename).
Consider the following example:
.Using `#regionName`
====
[source,properties]
----
# Spring Boot application.properties
spring.boot.data.gemfire.cache.data.export.resource.location=\
file://#{#env['user.home']}/gemfire/cache/data/custom-filename-for-#{#regionName}.json
----
====
NOTE: By default, the exported file is stored in the working directory (`System.getProperty("user.dir")`)
of the Spring Boot application process.
TIP: See the Spring Framework {spring-framework-docs}/core.html#expressions[documentation] for more information on SpEL.
[[geode-data-using-import-export-api-extensions-resource-reading-writing]]
==== Reading & Writing Resources
The Spring {spring-framework-javadoc}/org/springframework/core/io/Resource.html[`Resource`] handle
specifies tion of a resource, not how the resource is read or written. Even the Spring
{spring-framework-javadoc}/org/springframework/core/io/ResourceLoader.html[`ResourceLoader`],
which is an interface for loading `Resources`, does not specifically read or write any content to the `Resource`.
SBDG separates these concerns into two interfaces: `ResourceReader` and `ResourceWriter`, respectively.
The design follows the same pattern used by Java's `InputStream/OutputStream` and `Reader/Writer` classes
in the `java.io` package.
The `ResourceReader` interfaces is defined as:
.ResourceReader
====
[source,java]
----
@FunctionalInterface
interface ResourceReader {
byte[] read(Resource resource);
}
----
====
The `ResourceWriter` interfaces is defined as:
.ResourceWriter
====
[source,java]
----
@FunctionalInterface
interface ResourceWriter {
void write(Resource resource, byte[] data);
}
----
====
Both interfaces provide additional methods to compose readers and writers, much like Java's `Consumer` and `Function`
interfaces in the `java.util.function` package. If a particular reader or writer is used in a composition and is unable
to handle the given `Resource`, it should throw a `UnhandledResourceException` to let the next reader or writer in the
composition try to read from or write to the `Resource`.
The reader or writer are free to throw a `ResourceReadException` or `ResourceWriteException` to break the chain of
reader and writer invocations in the composition.
To override the default export/import reader and writer used by SBDG, you can implement the `ResourceReader`
or `ResourceWriter` interfaces as appropriate and declare instances of these classes as beans in the Spring container:
.Custom `ResourceReader` & `ResourceWriter` beans
====
[source,java]
----
@Configuration
class MyApplicationConfiguration {
@Bean
ResourceReader myResourceReader() {
return new MyResourceReader()
.thenReadFrom(new MyOtherResourceReader());
}
@Bean
ResourceWriter myResourceWriter() {
return new MyResourceWriter();
}
}
----
====

View File

@@ -0,0 +1,401 @@
[[geode-docker]]
== Docker
:docker-docs-url: https://docs.docker.com
:docker-site-url: https://www.docker.com/
:geode-name: {apache-geode-name}
:testcontainers-url: https://www.testcontainers.org
The state of modern software application development is moving towards https://www.docker.com/resources/what-container[containerization].
Containers offer a controlled environment to predictably build (compile, configure and package), run, and manage your
applications in a reliable and repeatable manner, regardless of context. In many situations, the intrinsic benefit of
using containers is obvious.
Understandably, {docker-site-url}[Docker's] popularity took off like wildfire, given its highly powerful and simplified
model for creating, using and managing containers to run packaged applications.
Docker's ecosystem is also quite impressive, with the advent of {testcontainers-url}[Testcontainers] and Spring Boot's
now {spring-boot-docs-html}/spring-boot-features.html#building-docker-images[dedicated support] to create packaged
Spring Boot applications in {docker-docs-url}/get-started/overview/#docker-objects[Docker images] that are then later
run in a Docker container.
TIP: See also {spring-boot-docs-html}/deployment.html#containers-deployment["`Deploying to Containers`"] to learn more.
{geode-name} can also run in a controlled, containerized environment. The goal of this chapter is to get you started
running {geode-name} in a container and interfacing to a containerized {geode-name} cluster from your Spring Boot,
{geode-name} client applications.
This chapter does not cover how to run your Spring Boot, {geode-name} client applications in a container, since that is
already covered by Spring Boot (again, see the Spring Boot documentation for
{spring-boot-docs-html}/spring-boot-features.html#building-docker-images[Docker images]
and {spring-boot-docs-html}/deployment.html#containers-deployment[container deployment],
along with Docker's {docker-docs-url}/get-started/overview/[documentation]).
Instead, our focus is on how to run an {geode-name} cluster in a container and connect to it from a Spring Boot,
{geode-name} client application, regardless of whether the application runs in a container or not.
[[geode-docker-image]]
=== Acquiring the {geode-name} Docker Image
To run an {geode-name} cluster inside a Docker container, you must first acquire the Docker image. You can get
the {geode-name} Docker image from https://hub.docker.com/r/apachegeode/geode/[Docker Hub].
While {geode-name}'s {apache-geode-docs}[official documentation] is less than clear on how to use {geode-name} in Docker,
we find a bit of relief in the {apache-geode-wiki}/How+to+use+Geode+on+Docker[Wiki]. However, for a complete
and comprehensive write up, see the instructions in
the https://github.com/markito/geode-docker#building-the-container-image[README]
from this https://github.com/markito/geode-docker[GitHub Repo].
NOTE: You must have {docker-docs-url}/get-docker[Docker] installed on your computer to complete the following steps.
Effectively, the high-level steps are as follows:
1) Acquire the {geode-name} Docker image from Docker Hub by using the `docker pull` command (shown with typical output)
from the command-line:
.Download/Install the {geode-name} Docker Image
====
[source,text]
----
$ docker pull apachegeode/geode
Using default tag: latest
latest: Pulling from apachegeode/geode
Digest: sha256:6a6218f22a2895bb706175727c7d76f654f9162acac22b2d950d09a2649f9cf4
Status: Image is up to date for apachegeode/geode:latest
docker.io/apachegeode/geode:latest
----
====
Instead of pulling from the `nightly` tag as suggested, the Spring team highly recommends that you pull from
the `latest` tag, which pulls a stable, production-ready {geode-name} Docker image based on the latest {geode-name}
GA version.
2) Verify that the {geode-name} Docker image was downloaded and installed successfully:
====
[source,text]
----
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
apachegeode/geode latest a2e210950712 2 months ago 224MB
cloudfoundry/run base-cnb 3a7d172559c2 8 weeks ago 71.2MB
open-liberty 19.0.0.9-webProfile8 dece75feff1a 3 months ago 364MB
tomee 11-jre-8.0.0-M3-webprofile 0d03e4d395e6 3 months ago 678MB
...
----
====
Now you are ready to run {geode-name} in a Docker container.
[[geode-docker-container]]
=== Running {geode-name} in a Docker Container
Now that you have acquired the {geode-name} Docker image, you can run {geode-name} in a Docker container.
Use the following `docker run` command to start {geode-name} in a Docker container:
.Start the {geode-name} Docker Container
====
[source,text]
----
$ docker run -it -p 10334:10334 -p 40404:40404 -p 1099:1099 -p 7070:7070 -p 7575:7575 apachegeode/geode
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.12.0
Monitor and Manage Apache Geode
gfsh>
----
====
Since the {geode-name} Docker container was started in interactive mode, you must open a separate command-line shell
to verify that the {geode-name} Docker container is in fact running:
.Verify the {geode-name} Docker Container is Running
====
[source,text]
----
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b30b9ffc5dc apachegeode/geode "gfsh" 44 seconds ago Up 43 seconds 0.0.0.0:1099->1099/tcp, 0.0.0.0:7070->7070/tcp, 0.0.0.0:7575->7575/tcp, 0.0.0.0:10334->10334/tcp, 0.0.0.0:40404->40404/tcp, 8080/tcp awesome_khorana
----
====
You know that the {geode-name} Docker container is running since we ended up at a Gfsh command prompt in
the interactive shell.
We also mapped ports between the Docker container and the host system, exposing well-known ports used by {geode-name}
server-side cluster processes, such as Locators and CacheServers:
.{geode-name} Ports
[width=30%, options="header", cols="2,1"]
|===
| Process | Port
| HTTP | 7070
| Locator | 10334
| Manager | 1099
| Server | 40404
|===
It is unfortunate that the {geode-name} Docker image gives you only a Gfsh command prompt, leaving you with the task of
provisioning a cluster. It would have been more useful to provide preconfigured Docker images with different {geode-name}
cluster configurations, such as one Locator and one server or two Locators and four servers, and so on. However, we can
start the cluster ourselves.
[[geode-docker-cluster]]
=== Start an {geode-name} Cluster in Docker
From inside the {geode-name} Docker container, we can start a Locator and a server:
.Start {geode-name} Locator & Server
====
[source, text]
----
gfsh>start locator --name=LocatorOne --log-level=config --hostname-for-clients=localhost
Starting a Geode Locator in /LocatorOne...
.........
Locator in /LocatorOne on 3b30b9ffc5dc[10334] as LocatorOne is currently online.
Process ID: 167
Uptime: 9 seconds
Geode Version: 1.12.0
Java Version: 1.8.0_212
Log File: /LocatorOne/LocatorOne.log
JVM Arguments: -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.log-level=config -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /geode/lib/geode-core-1.12.0.jar:/geode/lib/geode-dependencies.jar
Successfully connected to: JMX Manager [host=3b30b9ffc5dc, port=1099]
Cluster configuration service is up and running.
gfsh>start server --name=ServerOne --log-level=config --hostname-for-clients=localhost
Starting a Geode Server in /ServerOne...
.......
Server in /ServerOne on 3b30b9ffc5dc[40404] as ServerOne is currently online.
Process ID: 267
Uptime: 7 seconds
Geode Version: 1.12.0
Java Version: 1.8.0_212
Log File: /ServerOne/ServerOne.log
JVM Arguments: -Dgemfire.default.locators=172.17.0.2[10334] -Dgemfire.start-dev-rest-api=false -Dgemfire.use-cluster-configuration=true -Dgemfire.log-level=config -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /geode/lib/geode-core-1.12.0.jar:/geode/lib/geode-dependencies.jar
gfsh>list members
Member Count : 2
Name | Id
---------- | --------------------------------------------------------------
LocatorOne | 172.17.0.2(LocatorOne:167:locator)<ec><v0>:41000 [Coordinator]
ServerOne | 172.17.0.2(ServerOne:267)<v1>:41001
gfsh>describe member --name=LocatorOne
Name : LocatorOne
Id : 172.17.0.2(LocatorOne:167:locator)<ec><v0>:41000
Host : 3b30b9ffc5dc
Regions :
PID : 167
Groups :
Used Heap : 50M
Max Heap : 443M
Working Dir : /LocatorOne
Log file : /LocatorOne/LocatorOne.log
Locators : 172.17.0.2[10334]
gfsh>describe member --name=ServerOne
Name : ServerOne
Id : 172.17.0.2(ServerOne:267)<v1>:41001
Host : 3b30b9ffc5dc
Regions :
PID : 267
Groups :
Used Heap : 77M
Max Heap : 443M
Working Dir : /ServerOne
Log file : /ServerOne/ServerOne.log
Locators : 172.17.0.2[10334]
Cache Server Information
Server Bind :
Server Port : 40404
Running : true
Client Connections : 0
----
====
We now have an {geode-name} cluster running with one Locator and one server inside a Docker container. We deliberately
started the cluster with a minimal configuration. For example, we have no Regions in which to store data:
====
[source,text]
----
gfsh>list regions
No Regions Found
----
====
However, that is OK. Once more, we want to show the full power of SBDG and let the Spring Boot application drive
the configuration of the {geode-name} cluster that runs in the Docker container, as required by the application.
Let's have a quick look at our Spring Boot application.
[[geode-docker-application]]
=== Spring Boot, {geode-name} Client Application Explained
The Spring Boot, {geode-name} `ClientCache` application we use to connect to our {geode-name} cluster that runs in
the Docker container appears as follows:
.Spring Boot, {geode-name} Docker client application
====
[source,java]
----
include::{docs-src-dir}/org/springframework/geode/docs/example/app/docker/SpringBootApacheGeodeDockerClientCacheApplication.java[tags=class]
----
====
Our `Customer` application domain model object type is defined as:
.`Customer` class
====
[source,java]
----
@Region("Customers")
class Customer {
@Id
private Long id;
private String name;
}
----
====
Also, we define a Spring Data CRUD Repository to persist and access `Customers` stored in the `/Customers` Region:
.`CustomerRepository` interface
====
[source,java]
----
interface CustomerRepository extends CrudRepository<Customer, Long> {
Customer findByNameLike(String name);
}
----
====
Our main class is annotated with `@SpringBootApplication`, making it be a proper Spring Boot application.
We additionally annotate the main class with SBDG's `@EnableClusterAware` annotation to automatically detect
the {geode-name} cluster that runs in the Docker container and to push cluster configuration metadata from
the application to the cluster as required by the application.
Specifically, the application requires that a Region called "`Customers`", as defined by the `@Region` mapping
annotation on the `Customer` application domain model class, exists on the servers in the cluster, to store `Customer`
data.
We use the SDG `@EnableEntityDefinedRegions` annotation to define the matching client `PROXY` "`Customers`" Region.
Optionally, we have also annotated our main class with SBDG's `@UseMemberName` annotation to give the `ClientCache`
a name, which we assert in the `assertClientCacheAndConfigureMappingPdxSerializer(:ClientCache)` method.
The primary work performed by this application is done in the Spring Boot `ApplicationRunner` bean definition. We create
a `Customer` instance (`Jon Doe`), save it to the "`Customers`" Region that is managed by the server in the cluster,
and then query for `Jon Doe` using OQL, asserting that the result is equal to what we expect.
We log the output from the application's operations to see the application in action.
[[geode-docker-application-run]]
=== Running the Spring Boot, {geode-name} client application
When you run the Spring Boot, {geode-name} client application, you should see output similar to the following:
.Application log output
====
[source,text]
----
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java ...
org.springframework.geode.docs.example.app.docker.SpringBootApacheGeodeDockerClientCacheApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
Saving Customer [Customer(name=Jon Doe)]...
Querying for Customer [SELECT * FROM /Customers WHERE name LIKE '%Doe']...
Customer was [Customer(name=Jon Doe)]
Process finished with exit code 0
----
====
When we review the configuration of the cluster, we see that the `/Customers` Region was created when the application
ran:
./Customers Region Configuration
====
[source,text]
----
gfsh>list regions
List of regions
---------------
Customers
gfsh>describe region --name=/Customers
Name : Customers
Data Policy : partition
Hosting Members : ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 1
| data-policy | PARTITION
----
====
Our `/Customers` Region contains a value (`Jon Doe`), and we can verify this by running the following OQL Query
with Gfsh:
.Query the `/Customers` Region
====
[source,text]
----
gfsh>query --query="SELECT customer.name FROM /Customers customer"
Result : true
Limit : 100
Rows : 1
Result
-------
Jon Doe
----
====
Our application ran successfully.
[[geode-docker-application-conclusion]]
=== Conclusion
In this chapter, we saw how to connect a Spring Boot, {geode-name} `ClientCache` application to an {geode-name} cluster
that runs in a Docker container.
Later, we provide more information on how to scale up, or rather scale out, our {geode-name} cluster that runs in Docker.
Additionally, we provide details on how you can use {geode-name}'s Docker image with Testcontainers when you write
integration tests, which formally became part of the Spring Test for {geode-name} (STDG) project.

View File

@@ -0,0 +1,119 @@
[[geode-functions]]
== Function Implementations & Executions
:geode-name: {apache-geode-name}
This chapter is about using {geode-name} in a Spring context for distributed computing use cases.
=== Background
Distributed computing, particularly in conjunction with data access and mutation operations, is a very effective
and efficient use of clustered computing resources. This is similar to {wikipedia-docs}/MapReduce[MapReduce].
A naively conceived query returning potentially hundreds of thousands (or even millions) of rows of data in a result set
to the application that queried and requested the data can be very costly, especially under load. Therefore, it is
typically more efficient to move the processing and computations on the predicated data set to where the data resides,
perform the required computations, summarize the results, and then send the reduced data set back to the client.
Additionally, when the computations are handled in parallel, across the cluster of computing resources, the operation
can be performed much more quickly. This typically involves intelligently organizing the data using various partitioning
(a.k.a. sharding) strategies to uniformly balance the data set across the cluster.
{geode-name} addresses this very important application concern in its
{apache-geode-docs}/developing/function_exec/chapter_overview.html[Function execution] framework.
Spring Data for {geode-name} {spring-data-geode-docs-html}/#function-annotations[builds] on this Function execution
framework by letting developers {spring-data-geode-docs-html}/#function-implementation[implement]
and {spring-data-geode-docs-html}/#function-execution[execute] {geode-name} functions with a simple POJO-based
annotation configuration model.
TIP: See {spring-data-geode-docs-html}/#_implementation_vs_execution[the section about implementation versus execution]
for the difference between Function implementation and execution.
Taking this a step further, Spring Boot for {geode-name} auto-configures and enables both Function implementation
and execution out-of-the-box. Therefore, you can immediately begin writing Functions and invoking them without having to
worry about all the necessary plumbing to begin with. You can rest assured that it works as expected.
=== Applying Functions
Earlier, when we talked about <<geode-caching-provider,caching>>, we described a `FinancialLoanApplicationService` class
that could process eligibility when someone (represented by a `Person` object) applied for a financial loan.
This can be a very resource intensive and expensive operation, since it might involve collecting credit and employment
history, gathering information on outstanding loans, and so on. We applied caching in order to not have to recompute
or redetermine eligibility every time a loan office may want to review the decision with the customer.
But, what about the process of computing eligibility in the first place?
Currently, the application's `FinancialLoanApplicationService` class seems to be designed to fetch the data and perform
the eligibility determination in place. However, it might be far better to distribute the processing and even determine
eligibility for a larger group of people all at once, especially when multiple, related people are involved in a single
decision, as is typically the case.
We can implement an `EligibilityDeterminationFunction` class by using SDG:
.Function implementation
====
[source,java]
----
@Component
class EligibilityDeterminationFunction {
@GemfireFunction(HA = true, hasResult = true, optimizeForWrite=true)
public EligibilityDecision determineEligibility(FunctionContext functionContext, Person person, Timespan timespan) {
// ...
}
}
----
====
By using the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/function/annotation/GemfireFunction.html[`@GemfireFunction`]
annotation, we can implement our Function as a POJO method. SDG appropriately handles registering this POJO method
as a proper Function with {geode-name}.
If we now want to call this function from our Spring Boot `ClientCache` application, we can define
a function execution interface with a method name that matches the function name and that targets the execution
on the `EligibilityDecisions` Region:
.Function execution
====
[source,java]
----
@OnRegion("EligibilityDecisions")
interface EligibilityDeterminationExecution {
EligibilityDecision determineEligibility(Person person, Timespan timespan);
}
----
====
We can then inject an instance of the `EligibilityDeterminationExecution` interface into our
`FinancialLoanApplicationService`, as we would any other object or Spring bean:
.Function use
====
[source,java]
----
@Service
class FinancialLoanApplicationService {
private final EligibilityDeterminationExecution execution;
public LoanApplicationService(EligibilityDeterminationExecution execution) {
this.execution = execution;
}
@Cacheable("EligibilityDecisions")
EligibilityDecision processEligibility(Person person, Timespan timespan) {
return this.execution.determineEligibility(person, timespan);
}
}
----
====
As with caching, no additional configuration is required to enable and find your application Function implementations
and executions. You can simply build and run. Spring Boot for {geode-name} handles the rest.
TIP: It is common to "implement" and register your application Functions on the server and "execute" them from
the client.

View File

@@ -0,0 +1,317 @@
[[geode-configuration-gemfire-properties]]
== Using Geode Properties
:geode-name: {apache-geode-name}
As of Spring Boot for {geode-name} (SBDG) 1.3, you can declare {geode-name} properties from `gemfire.properties`
in Spring Boot `application.properties`.
TIP: See the {apache-geode-docs}/reference/topics/gemfire_properties.html[User Guide] for a complete list
of valid {geode-name} properties.
Note that you can declare only valid Geode properties in `gemfire.properties` or, alternatively,
`gfsecurity.properties`.
The following example shows how to declare properties in `gemfire.properties`:
.Valid `gemfire.properties`
====
[source,properties]
----
# Geode Properties in gemfire.properties
name=ExampleCacheName
log-level=TRACE
enable-time-statistics=true
durable-client-id=123
# ...
----
====
All of the properties declared in the preceding example correspond to valid Geode properties. It is illegal to declare
properties in `gemfire.properties` that are not valid Geode properties, even if those properties are prefixed with a
different qualifier (such as `spring.*`). {geode-name} throws an `IllegalArgumentException` for invalid properties.
Consider the following `gemfire.properties` file with an `invalid-property`:
.Invalid `gemfire.properties`
====
[source,properties]
----
# Geode Properties in gemfire.properties
name=ExampleCacheName
invalid-property=TEST
----
====
{geode-name} throws an `IllegalArgumentException`:
.`IllegalArgumentException` thrown by {geode-name} for Invalid Property (Full Text Omitted)
====
[source,txt]
----
Exception in thread "main" java.lang.IllegalArgumentException: Unknown configuration attribute name invalid-property.
Valid attribute names are: ack-severe-alert-threshold ack-wait-threshold archive-disk-space-limit ...
at o.a.g.internal.AbstractConfig.checkAttributeName(AbstractConfig.java:333)
at o.a.g.distributed.internal.AbstractDistributionConfig.checkAttributeName(AbstractDistributionConfig.java:725)
at o.a.g.distributed.internal.AbstractDistributionConfig.getAttributeType(AbstractDistributionConfig.java:887)
at o.a.g.internal.AbstractConfig.setAttribute(AbstractConfig.java:222)
at o.a.g.distributed.internal.DistributionConfigImpl.initialize(DistributionConfigImpl.java:1632)
at o.a.g.distributed.internal.DistributionConfigImpl.<init>(DistributionConfigImpl.java:994)
at o.a.g.distributed.internal.DistributionConfigImpl.<init>(DistributionConfigImpl.java:903)
at o.a.g.distributed.internal.ConnectionConfigImpl.lambda$new$2(ConnectionConfigImpl.java:37)
at o.a.g.distributed.internal.ConnectionConfigImpl.convert(ConnectionConfigImpl.java:73)
at o.a.g.distributed.internal.ConnectionConfigImpl.<init>(ConnectionConfigImpl.java:36)
at o.a.g.distributed.internal.InternalDistributedSystem$Builder.build(InternalDistributedSystem.java:3004)
at o.a.g.distributed.internal.InternalDistributedSystem.connectInternal(InternalDistributedSystem.java:269)
at o.a.g.cache.client.ClientCacheFactory.connectInternalDistributedSystem(ClientCacheFactory.java:280)
at o.a.g.cache.client.ClientCacheFactory.basicCreate(ClientCacheFactory.java:250)
at o.a.g.cache.client.ClientCacheFactory.create(ClientCacheFactory.java:216)
at org.example.app.ApacheGeodeClientCacheApplication.main(...)
----
====
It is inconvenient to have to separate {geode-name} properties from other application properties, or to have to declare
only {geode-name} properties in a `gemfire.properties` file and application properties in a separate properties file,
such as Spring Boot `application.properties`.
Additionally, because of {geode-name}'s constraint on properties, you cannot use the full power of Spring Boot when you
compose `application.properties`.
You can include certain properties based on a Spring profile while excluding other properties. This is essential when
properties are environment- or context-specific.
Spring Data for {geode-name} already provides a wide range of properties mapping to {geode-name} properties.
For example, the SDG `spring.data.gemfire.locators` property maps to the `gemfire.locators` property
(`locators` in `gemfire.properties`) from {geode-name}. Likewise, there are a full set of SDG properties that map to
the corresponding {geode-name} properties in the <<geode-configuration-metadata-springdata,Appendix>>.
You can express the Geode properties shown earlier as SDG properties in Spring Boot `application.properties`,
as follows:
.Configuring Geode Properties using SDG Properties
====
[source,properties]
----
# Spring Data for {geode-name} properties in application.properties
spring.data.gemfire.name=ExampleCacheName
spring.data.gemfire.cache.log-level=TRACE
spring.data.gemfire.cache.client.durable-client-id=123
spring.data.gemfire.stats.enable-time-statistics=true
# ...
----
====
However, some {geode-name} properties have no equivalent SDG property, such as `gemfire.groups` (`groups` in
`gemfire.properties`). This is partly due to the fact that many {geode-name} properties are applicable only when
configured on the server (such as `groups` or `enforce-unique-host`).
TIP: See the `@EnableGemFireProperties` annotation
({spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableGemFireProperties.html[attributes])
from SDG for a complete list of {geode-name} properties with no corresponding SDG property.
Furthermore, many of the SDG properties also correspond to API calls.
For example, {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#keepAlive[`spring.data.gemfire.cache.client.keep-alive`]
translates to the {apache-geode-javadoc}/org/apache/geode/cache/client/ClientCache.html#close-boolean[`ClientCache.close(boolean keepAlive)`]
API call.
Still, it would be convenient to be able to declare application and {geode-name} properties together, in a single
properties file, such as Spring Boot `application.properties`. After all, it is not uncommon to declare JDBC Connection
properties in a Spring Boot `application.properties` file.
Therefore, as of SBDG 1.3, you can now declare {geode-name} properties in Spring Boot `application.properties` directly,
as follows:
.Geode Properties declared in Spring Boot `application.properties`
====
[source,properties]
----
# Spring Boot application.properties
server.port=8181
spring.application.name=ExampleApp
gemfire.durable-client-id=123
gemfire.enable-time-statistics=true
----
====
This is convenient and ideal for several reasons:
* If you already have a large number of {geode-name} properties declared as `gemfire.` properties (either in
`gemfire.properties` or `gfsecurity.properties`) or declared on the Java command-line as JVM System properties
(such as `-Dgemfire.name=ExampleCacheName`), you can reuse these property declarations.
* If you are unfamiliar with SDG's corresponding properties, you can declare Geode properties instead.
* You can take advantage of Spring features, such as Spring profiles.
* You can also use property placeholders with Geode properties (such as
`gemfire.log-level=${external.log-level.property}`).
TIP: We encourage you to use the SDG properties, which cover more than {geode-name} properties.
However, SBDG requires that the Geode property must have the `gemfire.` prefix in Spring Boot `application.properties`.
This indicates that the property belongs to {geode-name}. Without the `gemfire.` prefix, the property is not
appropriately applied to the {geode-name} cache instance.
It would be ambiguous if your Spring Boot applications integrated with several technologies, including {geode-name},
and they too had matching properties, such as `bind-address` or `log-file`.
SBDG makes a best attempt to log warnings when a Geode property is invalid or is not set. For example, the following
Geode property would result in logging a warning:
.Invalid {geode-name} Property
====
[source,properties]
----
# Spring Boot application.properties
spring.application.name=ExampleApp
gemfire.non-existing-property=TEST
----
====
The resulting warning in the log would read:
.Invalid Geode Property Warning Message
====
[source,text]
----
[gemfire.non-existing-property] is not a valid Apache Geode property
----
====
If a Geode Property is not properly set, the following warning is logged:
.Invalide Geode Property Value Warning Message
====
[source,text]
----
Apache Geode Property [gemfire.security-manager] was not set
----
====
With regards to the third point mentioned earlier, you can now compose and declare Geode properties based on a context
(such as your application environment) using Spring profiles.
For example, you might start with a base set of properties in Spring Boot `application.properties`:
.Base Properties
====
[source,properties]
----
server.port=8181
spring.application.name=ExampleApp
gemfire.durable-client-id=123
gemfire.enable-time-statistics=false
----
====
Then you can vary the properties by environment, as the next two listings (for QA and production) show:
.QA Properties
====
[source,properties]
----
# Spring Boot application-qa.properties
server.port=9191
spring.application.name=TestApp
gemfire.enable-time-statistics=true
gemfire.enable-network-partition-detection=true
gemfire.groups=QA
# ...
----
====
.Production Properties
====
[source,properties]
----
# Spring Boot application-prod.properties
server.port=80
spring.application.name=ProductionApp
gemfire.archive-disk-space-limit=1000
gemfire.archive-file-size-limit=50
gemfire.enforce-unique-host=true
gemfire.groups=PROD
# ...
----
====
You can then apply the appropriate set of properties by configuring the Spring profile with
`-Dspring.profiles.active=prod`. You can also enable more than one profile at a time with
`-Dspring.profiles.active=profile1,profile2,...,profileN`
If both `spring.data.gemfire.*` properties and the matching {geode-name} properties are declared in Spring Boot
`application.properties`, the SDG properties take precedence.
If a property is specified more than once, as would potentially be the case when composing multiple Spring Boot
`application.properties` files and you enable more than one Spring profile at time, the last property declaration wins.
In the example shown earlier, the value for `gemfire.groups` would be `PROD` when `-Dspring.profiles.active=qa,prod`
is configured.
Consider the following Spring Boot `application.properties`:
.Property Precedence
====
[source,properties]
----
# Spring Boot application.properties
gemfire.durable-client-id=123
spring.data.gemfire.cache.client.durable-client-id=987
----
====
The `durable-client-id` is `987`. It does not matter which order the SDG or {geode-name} properties are declared in
Spring Boot `application.properties`. The matching SDG property overrides the {geode-name} property when duplicates
are found.
Finally, you cannot refer to Geode properties declared in Spring Boot `application.properties` with the SBDG
`GemFireProperties` class (see the {spring-boot-data-geode-javadoc}/org/springframework/geode/boot/autoconfigure/configuration/GemFireProperties.html[Javadoc]).
Consider the following example:
.Geode Properties declared in Spring Boot `application.properties`
====
[source,properties]
----
# Spring Boot application.properties
gemfire.name=TestCacheName
----
====
Given the preceding property, the following assertion holds:
====
[source,java]
----
import org.springframework.geode.boot.autoconfigure.configuration.GemFireProperties;
@RunWith(SpringRunner.class)
@SpringBootTest
class GemFirePropertiesTestSuite {
@Autowired
private GemFireProperties gemfireProperties;
@Test
public void gemfirePropertiesTestCase() {
assertThat(this.gemfireProperties.getCache().getName()).isNotEqualTo("TestCacheName");
}
}
----
====
TIP: You can declare `application.properties` in the `@SpringBootTest` annotation. For example, you could have declared
`gemfire.name` in the annotation by setting `@SpringBootTest(properties = { "gemfire.name=TestCacheName" })`
for testing purposes instead of declaring the property in a separate Spring Boot `application.properties` file.
Only `spring.data.gemfire.*` prefixed properties are mapped to the SBDG `GemFireProperties` class hierarchy.
TIP: Prefer SDG properties over Geode properties. See the SDG properties reference
in the <<geode-configuration-metadata-springdata,Appendix>>.

View File

@@ -0,0 +1,398 @@
[[geode-api-extensions]]
== Apache Geode API Extensions
:geode-name: {apache-geode-name}
:images-dir: ./images
When using the Spring programming model and abstractions, it should not be necessary to use {geode-name}
{apache-geode-javadoc}[APIs] at all -- for example, when using the Spring Cache Abstraction for caching
or the Spring Data Repository abstraction for DAO development. There are many more examples.
For certain use cases, users may require low level access to fine-grained functionally. Spring Boot for {geode-name}'s
`org.springframework.geode:apache-geode-extensions` module and library builds on {geode-name}'s APIs by including
several extensions with enhanced functionality to offer an experience familiar to Spring users inside a Spring context.
TIP: Spring Data for {geode-name} (SDG) also {spring-data-geode-docs-html}/#apis[includes] additional extensions to
{geode-name}'s APIs.
[[geode-api-extensions-cacheresolver]]
=== `SimpleCacheResolver`
In some cases, it is necessary to acquire a reference to the cache instance in your application components at runtime.
For example, you might want to create a temporary `Region` on the fly to aggregate data for analysis.
Typically, you already know the type of cache your application is using, since you must declare your application to be
either a client (`ClientCache`) in the {apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[client/server topology],
or a {apache-geode-docs}/topologies_and_comm/p2p_configuration/chapter_overview.html[peer member or node] (`Cache`) in
the cluster on startup. This is expressed in configuration when creating the cache instance required to interact with
the {geode-name} data management system. In most cases, your application will be a client. SBDG makes this decision easy,
since it auto-configures a `ClientCache` instance, <<geode-clientcache-applications,by default>>.
In a Spring context, the cache instance created by the framework is a managed bean in the Spring container.
You can inject a reference to the https://en.wikipedia.org/wiki/Singleton_pattern[_Singleton_] cache bean
into any other managed application component:
.Autowired Cache Reference using Dependency Injection (DI)
====
[source,java]
----
@Service
class CacheMonitoringService {
@Autowired
ClientCache clientCache;
// use the clientCache object reference to monitor the cache as necessary
}
----
====
However, in cases where your application component or class is not managed by Spring and you need a reference to the
cache instance at runtime, SBDG provides the abstract `org.springframework.geode.cache.SimpleCacheResolver` class
(see its {spring-boot-data-geode-javadoc}/org/springframework/geode/cache/SimpleCacheResolver.html[Javadoc]).
.`SimpleCacheResolver` API
====
[source, java ]
----
package org.springframework.geode.cache;
abstract class SimpleCacheResolver {
<T extends GemFireCache> T require() { }
<T extends GemFireCache> Optional<T> resolve() { }
Optional<ClientCache> resolveClientCache() { }
Optional<Cache> resolvePeerCache() { }
}
----
====
`SimpleCacheResolver` adheres to https://en.wikipedia.org/wiki/SOLID[SOLID OO Principles]. This class is abstract
and extensible so that you can change the algorithm used to resolve client or peer cache instances as well as mock
its methods in unit tests.
Additionally, each method is precise. For example, `resolveClientCache()` resolves a reference to a cache only if
the cache instance is a "`client.`" If a cache exists but is a "`peer`" cache instance, `resolveClientCache()`
returns `Optional.EMPTY`. The behavior of `resolvePeerCache()` is similar.
`require()` returns a non-`Optional` reference to a cache instance and throws an `IllegalStateException` if a cache
is not present.
[[geode-api-extensions-cacheutils]]
=== `CacheUtils`
Under the hood, `SimpleCacheResolver` delegates some of its functions to the
{spring-boot-data-geode-javadoc}/org/springframework/geode/util/CacheUtils.html[`CacheUtils`]
abstract utility class, which provides additional, convenient capabilities when you use a cache.
While there are utility methods to determine whether a cache instance (that is, a `GemFireCache`) or Region is a client
or a peer, one of the more useful functions is to extract all the values from a Region.
To extract all the values stored in a Region, call `CacheUtils.collectValues(:Region<?, T>)`. This method returns a
`Collection<T>` that contains all the values stored in the given `Region`. The method is smart and knows how to handle
the `Region` appropriately regardless of whether the `Region` is a client or a peer. This distinction is important,
since client `PROXY` Regions store no values.
WARNING: Caution is advised when you get all values from a Region. While getting filtered reference values from a
non-transactional, reference data only [`REPLICATE`] Region is quite useful, getting all values from a transactional,
[`PARTITION`] Region can prove quite detrimental, especially in production. Getting all values from a Region can be
useful during testing.
[[geode-api-extensions-membership]]
=== `MembershipListenerAdapter` and `MembershipEvent`
Another useful API hidden by {geode-name} is the membership events and listener interface. This API is especially useful
on the server side when your Spring Boot application serves as a peer member of an {geode-name} distributed system.
When a peer member is disconnected from the distributed system, perhaps due to a network failure, the member is forcibly
removed from the cluster. This node immediately enters a reconnecting state, trying to establish a connection back to
the cluster. Once reconnected, the peer member must rebuild all cache objects (`Cache`, `Region` instances, `Index`
instances, `DiskStore` instances, and so on). All previous cache objects are now invalid, and their references are stale.
In a Spring context, this is particularly problematic since most {geode-name} objects are _Singleton_ beans declared in
and managed by the Spring container. Those beans may be injected and used in other framework and application components.
For instance, `Region` instances are injected into SDG's `GemfireTemplate`, Spring Data Repositories and possibly
application-specific data access objects (https://en.wikipedia.org/wiki/Data_access_object[DAOs]).
If references to those cache objects become stale on a forced disconnect event, there is no way to auto-wire fresh
object references into the dependent application or framework components when the peer member is reconnected, unless the
Spring `ApplicationContext` is "`refreshed`". In fact, there is no way to even know that this event has occurred, since
the {geode-name} `MembershipListener` API and corresponding events are "`internal`".
NOTE: The Spring team explored the idea of creating proxies for all types of cache objects (`Cache`, `Region`, `Index`,
`DiskStore`, `AsyncEventQueue`, `GatewayReceiver`, `GatewaySender`, and others) used by Spring. The proxies would know
how to obtain a fresh reference on a reconnect event. However, this turns out to be more problematic than it is worth.
It is easier to "`refresh`" the Spring `ApplicationContext`, although doing so is no less expensive. Neither way is
ideal. See https://jira.spring.io/browse/SGF-921[SGF-921] and https://jira.spring.io/browse/SGF-227[SGF-227]
for further details.
In the case where membership events are useful to the Spring Boot application, SBDG provides the following
{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/package-frame.html[API]:
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipListenerAdapter.html[`MembershipListenerAdapter`]
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.html[`MembershipEvent`]
The abstract `MembershipListenerAdapter` class implements {geode-name}'s `org.apache.geode.distributed.internal.MembershipListener`
interface to simplify the event handler method signatures by using an appropriate `MembershipEvent` type to encapsulate
the actors in the event.
The abstract `MembershipEvent` class is further subclassed to represent specific membership event types that occur
within the {geode-name} system:
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberDepartedEvent.html[`MemberDepartedEvent`]
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberJoinedEvent.html[`MemberJoinedEvent`]
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberSuspectEvent.html[`MemberSuspectEvent`]
* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/QuorumLostEvent.html[`QuorumLostEvent`]
The API is depicted in the following UML diagram:
image::{images-dir}/membership-api-uml.png[]
The membership event type is further categorized with an appropriate enumerated value,
{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.Type.html[`MembershipEvent.Type`],
as a property of the `MembershipEvent` itself (see {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.html#getType--[`getType()`]).
The type hierarchy is useful in `instanceof` expressions, while the `Enum` is useful in `switch` statements.
You can see one particular implementation of the `MembershipListenerAdapter` with the
{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/ApplicationContextMembershipListener.html[`ApplicationContextMembershipListener`] class,
which does exactly as we described earlier, handling forced-disconnect/auto-reconnect membership events inside a
Spring container in order to refresh the Spring `ApplicationContext`.
[[geode-api-extensions-pdx]]
=== PDX
{geode-name}'s PDX serialization framework is yet another API that falls short of a complete stack.
For instance, there is no easy or direct way to serialize an object as PDX bytes. It is also not possible to modify an
existing `PdxInstance` by adding or removing fields, since doing so would require a new PDX type. In this case, you must
create a new `PdxInstance` and copy from an existing `PdxInstance`. Unfortunately, the {geode-name} API offers no help
in this regard. It is also not possible to use PDX in a client, local-only mode without a server, since the PDX type
registry is only available and managed on servers in a cluster.
[[geode-api-extensions-pdx-builder]]
==== `PdxInstanceBuilder`
In such cases, SBDG conveniently provides the
{spring-boot-data-geode-javadoc}/org/springframework/geode/pdx/PdxInstanceBuilder.html[`PdxInstanceBuilder`] class,
appropriately named after the https://en.wikipedia.org/wiki/Builder_pattern[Builder software design pattern].
The `PdxInstanceBuilder` also offers a fluent API for constructing `PdxInstances`:
.`PdxInstanceBuilder` API
====
[source,java]
----
class PdxInstanceBuilder {
PdxInstanceFactory copy(PdxInstance pdx);
Factory from(Object target);
}
----
====
For example, you could serialize an application domain object as PDX bytes with the following code:
.Serializing an Object to PDX
====
[source,java]
----
@Component
class CustomerSerializer {
PdxInstance serialize(Customer customer) {
return PdxInstanceBuilder.create()
.from(customer)
.create();
}
}
----
====
You could then modify the `PdxInstance` by copying from the original:
.Copy `PdxInstance`
====
[source,java]
----
@Component
class CustomerDecorator {
@Autowired
CustomerSerializer serializer;
PdxIntance decorate(Customer customer) {
PdxInstance pdxCustomer = serializer.serialize(customer);
return PdxInstanceBuilder.create()
.copy(pdxCustomer)
.writeBoolean("vip", isImportant(customer))
.create();
}
}
----
====
[[geode-api-extensions-pdx-wrapper]]
==== `PdxInstanceWrapper`
SBDG also provides the {spring-boot-data-geode-javadoc}/org/springframework/geode/pdx/PdxInstanceWrapper.html[`PdxInstanceWrapper`]
class to wrap an existing `PdxInstance` in order to provide more control during the conversion from PDX to JSON and from
JSON back into a POJO. Specifically, the wrapper gives you more control over the configuration of Jackson's
`ObjectMapper`.
The `ObjectMapper` constructed by {geode-name}'s own `PdxInstance` implementation (`PdxInstanceImpl`) is not
configurable, nor was it configured correctly. Unfortunately, since `PdxInstance` is not extensible, the `getObject()`
method fails when converting the JSON generated from PDX back into a POJO for any practical application domain model
type.
The following example wraps an existing `PdxInstance`:
.Wrapping an existing `PdxInstance`
====
[source,java]
----
PdxInstanceWrapper wrapper = PdxInstanceWrapper.from(pdxInstance);
----
====
For all operations on `PdxInstance` except `getObject()`, the wrapper delegates to the underlying `PdxInstance` method
implementation called by the user.
In addition to the decorated `getObject()` method, the `PdxInstanceWrapper` provides a thorough implementation of the
`toString()` method. The state of the `PdxInstance` is output in a JSON-like `String`.
Finally, the `PdxInstanceWrapper` class adds a `getIdentifier()` method. Rather than put the burden on the user to have
to iterate the field names of the `PdxInstance` to determine whether a field is the identity field and then call
`getField(name)` with the field name to get the ID (value) -- assuming an identity field was marked in the first place
-- the `PdxInstanceWrapper` class provides the `getIdentifier()` method to return the ID of the `PdxInstance` directly.
The `getIdentifier()` method is smart in that it first iterates the fields of the `PdxInstance`, asking each field if it
is the identity field. If no field was marked as the identity field, the algorithm searches for a field named `id`. If
no field with the name `id` exists, the algorithm searches for a metadata field called `@identifier`, which refers to
the field that is the identity field of the `PdxInstance`.
The `@identifier` metadata field is useful in cases where the `PdxInstance` originated from JSON and the application
domain object uses a natural identifier, rather than a surrogate ID, such as `Book.isbn`.
NOTE: {geode-name}'s `JSONFormatter` class is not capable of marking the identity field of a `PdxInstance` originating
from JSON.
WARNING: It is not currently possible to implement the `PdxInstance` interface and store instances of this type as a
value in a Region. {geode-name} assumes all `PdxInstance` objects are an implementation created by {geode-name} itself
(that is, `PdxInstanceImpl`), which has a tight coupling to the PDX type registry. An `Exception` is thrown if you try
to store instances of your own `PdxInstance` implementation.
[[geode-api-extensions-pdx-adapter]]
==== `ObjectPdxInstanceAdapter`
In rare cases, you may need to treat an `Object` as a `PdxInstance`, depending on the context without incurring
the overhead of serializing an `Object` to PDX. For such cases, SBDG offers the `ObjectPdxInstanceAdapter` class.
This might be true when calling a method with a parameter expecting an argument of, or returning an instance of,
type `PdxInstance`, particularly when {geode-name}'s `read-serialized` PDX configuration property is set to `true`
and only an object is available in the current context.
Under the hood, SBDG's `ObjectPdxInstanceAdapter` class uses Spring's
{spring-framework-javadoc}/org/springframework/beans/BeanWrapper.html[`BeanWrapper`] class along with Java's
introspection and reflection functionality to adapt the given `Object` and access it with the full
{apache-geode-javadoc}/org/apache/geode/pdx/PdxInstance.html[`PdxInstance`] API. This includes the use of the
{apache-geode-javadoc}/org/apache/geode/pdx/WritablePdxInstance.html[`WritablePdxInstance`] API, obtained from
{apache-geode-javadoc}/org/apache/geode/pdx/PdxInstance.html#createWriter--[`PdxInstance.createWriter()`], to modify
the underlying `Object` as well.
Like the `PdxInstanceWrapper` class, `ObjectPdxInstanceAdapter` contains special logic to resolve the identity field
and ID of the `PdxInstance`, including consideration for Spring Data's
{spring-data-commons-javadoc}/org/springframework/data/annotation/Id.html[`@Id`] mapping annotation,
which can be introspected in this case, given that the underlying `Object` backing the `PdxInstance` is a POJO.
The `ObjectPdxInstanceAdapter.getObject()` method returns the wrapped `Object` used to construct
the `ObjectPdxInstanceAdapter` and is, therefore, automatically deserializable, as determined by the
{apache-geode-javadoc}/org/apache/geode/pdx/PdxInstance.html#isDeserializable--[`PdxInstance.isDeseriable()`] method,
which always returns `true`.
You can adapt any `Object` as a `PdxInstance`:
.Adapt an `Object` as a `PdxInstance`
====
[source,java]
----
class OfflineObjectToPdxInstanceConverter {
@NonNull PdxInstance convert(@NonNull Object target) {
return ObjectPdxInstanceAdapter.from(target);
}
}
----
====
Once the https://en.wikipedia.org/wiki/Adapter_pattern[Adapter] is created, you can use it to access data
on the underlying `Object`.
Consider the following example of a `Customer` class:
.`Customer` class
====
[source,java]
----
@Region("Customers")
class Customer {
@Id
private Long id;
String name;
// constructors, getters and setters omitted
}
----
====
Then you can access an instance of `Customer` by using the `PdxInstance` API:
.Accessing an `Object` using the `PdxInstance` API
====
[source,java]
----
class ObjectPdxInstanceAdapterTest {
@Test
public void getAndSetObjectProperties() {
Customer jonDoe = new Customer(1L, "Jon Doe");
PdxInstance adapter = ObjectPdxInstanceAdapter.from(jonDoe);
assertThat(jonDoe.getName()).isEqualTo("Jon Doe");
assertThat(adapter.getField("name")).isEqualTo("Jon Doe");
adapter.createWriter().setField("name", "Jane Doe");
assertThat(adapter.getField("name")).isEqualTo("Jane Doe");
assertThat(jonDoe.getName()).isEqualTo("Jane Doe");
}
}
----
====
[[geode-api-extensions-security]]
=== Security
For testing purposes, SBDG provides a test implementation of
{geode-name}'s {apache-geode-javadoc}/org/apache/geode/security/SecurityManager.html[`SecurityManager`] interface,
which expects the password to match the username (case-sensitive) when authenticating.
By default, all operations are authorized.
To match the expectations of SBDG's `TestSecurityManager`, SBDG additionally provides a test implementation of
{geode-name}'s {apache-geode-javadoc}/org/apache/geode/security/AuthInitialize.html[`AuthInitialize`] interface,
which supplies matching credentials for both the username and password.

View File

@@ -0,0 +1,411 @@
[[geode-logging]]
== Logging
:geode-name: {apache-geode-name}
{geode-name} `1.9.2` was modularized to separate its use of the Apache Log4j API to log output in {geode-name} code
from the underlying implementation of logging, which uses Apache Log4j as the logging provider by default.
Prior to `1.9.2`, the Apache Log4j API (`log4j-api`) and the Apache Log4j service provider (`log4j-core`)
were automatically pulled in by {geode-name} core (`org.apache.geode:geode-core`), thereby making it problematic
to change logging providers when using {geode-name} in Spring Boot applications.
However, now, in order to get any log output from {geode-name} whatsoever, {geode-name} requires a logging provider
declared on your Spring Boot application classpath. Consequently, this also means the old {geode-name} `Properties`
(such as `log-level`) no longer have any effect, regardless of whether the property is specified in `gemfire.properties`,
in Spring Boot `application.properties`, or even as a JVM System Property (`-Dgemfire.log-level`).
TIP: See {geode-name}'s {apache-geode-docs}/reference/topics/gemfire_properties.html[documentation] for a complete list
of valid `Properties`, including the `Properties` used to configure logging.
Unfortunately, this also means the Spring Data for {geode-name}
{spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableLogging.html[`@EnableLogging`]
annotation no longer has any effect on {geode-name} logging either. Consequently, it has been
https://jira.spring.io/browse/DATAGEODE-299[deprecated]. The reason `@EnableLogging` no longer has any effect on logging
is because this annotation's attributes and associated SDG properties indirectly set the corresponding {geode-name}
properties, which, again, are useless from {geode-name} `1.9.2` onward.
By way of example, and to make this concrete, *none* of the following approaches have any effect on {geode-name} logging:
.Command-line configuration
====
[source,txt]
----
$ java -classpath ...:/path/to/MySpringBootApacheGeodeClientCacheApplication.jar -Dgemfire.log-level=DEBUG
example.app.MySpringBootApacheGeodeClientCacheApplication
----
====
.Externalized configuration using {geode-name} `gemfire.properties`
====
[source,properties]
----
# {geode-name} only/specific properties
log-level=INFO
----
====
.Externalized configuration using Spring Boot `application.properties`
====
[source,properties]
----
spring.data.gemfire.cache.log-level=DEBUG
spring.data.gemfire.logging.level=DEBUG
----
====
.Java configuration using SDG's `@EnableLogging` annotation
====
[source,java]
----
@SpringBootApplication
@EnableLogging(logLevel = "DEBUG")
class MySpringBootApacheGeodeClientApplication {
}
----
====
None of the preceding approaches have any effect without the *new* SBDG logging starter.
[[geode-logging-configuration]]
=== Configure {geode-name} Logging
So, how do you configure logging for {geode-name}?
Three things are required to get {geode-name} to log output:
. You must declare a logging provider (such as Logback) on your Spring Boot application classpath.
. (optional) You can declare an adapter (a bridge JAR) between Log4j and your logging provider if your declared
logging provider is not Apache Log4j.
+
For example, if you use the SLF4J API to log output from your Spring Boot application and use Logback as your
logging provider or implementation, you must include the `org.apache.logging.log4j.log4j-to-slf4j` adapter or
bridge JAR as well.
+
Internally, {geode-name} uses the Apache Log4j API to log output from Geode components. Therefore, you must bridge Log4j
to any other logging provider (such as Logback) that is not Log4j (`log4j-core`). If you use Log4j as your
logging provider, you need not declare an adapter or bridge JAR on your Spring Boot application classpath.
. Finally, you must supply logging provider configuration to configure Loggers, Appenders, log levels, and other details.
+
For example, when you use Logback, you must provide a `logback.xml` configuration file on your Spring Boot application
classpath or in the filesystem. Alternatively, you can use other means to configure your logging provider and get
{geode-name} to log output.
NOTE: {geode-name}'s `geode-log4j` module covers the required configuration for steps 1-3 above and uses Apache Log4j
(`org.apache.logging.log4j:log4j-core`) as the logging provider. The `geode-log4j` module even provides a default
`log4j2.xml` configuration file to configure Loggers, Appenders, and log levels for {geode-name}.
If you declare Spring Boot's own `org.springframework.boot:spring-boot-starter-logging` on your application classpath,
it covers steps 1 and 2 above.
The `spring-boot-starter-logging` dependency declares Logback as the logging provider and automatically adapts (bridges)
`java.util.logging` (JUL) and Apache Log4j to SLF4J. However, you still need to supply logging provider configuration
(such as a `logback.xml` file for Logback) to configure logging not only for your Spring Boot application but for
{geode-name} as well.
SBDG has simplified the setup of {geode-name} logging. You need only declare the
`org.springframework.geode:spring-geode-starter-logging` dependency on your Spring Boot application classpath.
Unlike {geode-name}'s default Log4j XML configuration file (`log4j2.xml`), SBDG's provided `logback.xml` configuration
file is properly parameterized, letting you adjust log levels as well as add Appenders.
In addition, SBDG's provided Logback configuration uses templates so that you can compose your own logging configuration
while still including snippets from SBDG's provided logging configuration metadata, such as Loggers and Appenders.
[[geode-logging-configuration-log-levels]]
==== Configuring Log Levels
One of the most common logging tasks is to adjust the log level of one or more Loggers or the ROOT Logger. However,
you may want to only adjust the log level for specific components of your Spring Boot application, such as for
{geode-name}, by setting the log level for only the Logger that logs {geode-name} events.
SBDG's Logback configuration defines three Loggers to control the log output from {geode-name}:
.{geode-name} Loggers by Name
====
[source,xml]
----
<comfiguration>
<logger name="com.gemstone.gemfire" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.apache.geode" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.jgroups" level="${spring.boot.data.gemfire.jgroups.log.level:-ERROR}"/>
</comfiguration>
----
====
The `com.gemstone.gemfire` Logger covers old GemFire components that are still present in {geode-name} for backwards
compatibility. By default, it logs output at `INFO`. This Logger's use should be mostly unnecessary.
The `org.apache.geode` Logger is the primary Logger used to control log output from all {geode-name} components
during the runtime operation of {geode-name}. By default, it logs output at `INFO`.
The `org.jgroups` Logger is used to log output from {geode-name}'s message distribution and membership system.
{geode-name} uses JGroups for membership and message distribution between peer members (nodes) in the cluster
(distributed system). By default, JGroups logs output at `ERROR`.
You can configure the log level for the `com.gemstone.gemfire` and `org.apache.geode` Loggers by setting
the `spring.boot.data.gemfire.log.level` property. You can independently configure the `org.jgroups` Logger by setting
the `spring.boot.data.gemfire.jgroups.log.level` property.
You can set the SBDG logging properties on the command line as JVM System properties when you run
your Spring Boot application:
.Setting the log-level from the CLI
====
[source,text]
----
$ java -classpath ...:/path/to/MySpringBootApplication.jar -Dspring.boot.data.gemfire.log.level=DEBUG
package.to.MySpringBootApplicationClass
----
====
NOTE: Setting JVM System properties by using
`$ java -jar MySpringBootApplication.jar -Dspring.boot.data.gemfire.log.level=DEBUG`
is not supported by the Java Runtime Environment (JRE).
Alternatively, you can configure and control {geode-name} logging in Spring Boot `application.properties`:
.Setting the log-level in Spring Boot `application.properties`
====
[source,properties]
----
spring.boot.data.gemfire.log.level=DEBUG
----
====
For backwards compatibility, SBDG additionally supports the Spring Data for {geode-name} (SDG) logging properties
as well, by using either of the following properties:
.Setting log-level using SDG Properties
====
[source,properties]
----
spring.data.gemfire.cache.log-level=DEBUG
spring.data.gemfire.logging.level=DEBUG
----
====
If you previously used either of these SDG-based logging properties, they continue to work as designed in SBDG `1.3`
or later.
[[geode-logging-configuration-composition]]
==== Composing Logging Configuration
As mentioned earlier, SBDG lets you compose your own logging configuration from SBDG's default Logback configuration
metadata.
SBDG conveniently bundles the Loggers and Appenders from SBDG's logging starter into a template file that you can
include into your own custom Logback XML configuration file.
The Logback template file appears as follows:
.logback-include.xml
====
[source,xml]
----
include::{starter-logging-resources-dir}/logback-include.xml[]
----
====
Then you can include this Logback configuration snippet in an application-specific Logback XML configuration file,
as follows:
.logback.xml
====
[source,xml]
----
include::{starter-logging-resources-dir}/logback.xml[]
----
====
[[geode-logging-slf4j-logback-api-support]]
=== SLF4J and Logback API Support
SBDG provides additional support when working with the SLF4J and Logback APIs. This support is available when you
declare the `org.springframework.geode:spring-geode-starter-logging` dependency on your Spring Boot application
classpath.
One of the main supporting classes from the `spring-geode-starter-logger`
is the `org.springframework.geode.logging.slf4j.logback.LogbackSupport` class. This class provides methods to:
* Resolve a reference to the Logback `LoggingContext`.
* Resolve the SLF4J ROOT `Logger` as a Logback `Logger`.
* Look up `Appenders` by name and required type.
* Add or remove `Appenders` to `Loggers`.
* Reset the state of the Logback logging system, which can prove to be most useful during testing.
`LogbackSupport` can even suppress the auto-configuration of Logback performed by Spring Boot on startup, which is
another useful utility during automated testing.
In addition to the `LogbackSupport` class, SBDG also provides some custom Logback `Appenders`.
[[geode-logging-slf4j-logback-api-support-appender-composite]]
==== CompositeAppender
The `org.springframework.geode.logging.slf4j.logback.CompositeAppender` class is an implementation of the Logback
`Appender` interface and the https://en.wikipedia.org/wiki/Composite_pattern[Composite software design pattern].
`CompositeAppender` lets developers compose multiple Appenders and use them as if they were a single `Appender`.
For example, you could compose both the Logback `ConsoleAppender` and `FileAppender` into one `Appender`:
.Composing multiple `Appenders`
====
[source,java]
----
class LoggingConfiguration {
Appender<ILoggingEvent> compositeAppender() {
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
FileAppender<ILoggingEvent> fileAppender = new FileApender<>();
Appender<ILoggingEvent> compositeAppender = CompositeAppender.compose(consoleAppender, fileAppender);
return compositeAppender;
}
}
// do something with the compositeAppender
----
====
You could then add the `CompositeAppender` to a named `Logger`:
.Register `CompositeAppender` on "named" `Logger`
====
[source,java]
----
class LoggerConfiguration {
void registerAppenderOnLogger() {
Logger namedLogger = LoggerFactory.getLogger("loggerName");
LogbackSupport.toLogbackLogger(namedLogger)
.ifPresent(it -> LogbackSupport.addAppender(it, compositeAppender));
}
}
----
====
In this case, the named `Logger` logs events (or log messages) to both the console and file Appenders.
You can compose an array or `Iterable` of `Appenders` by using either the
`CompositeAppender.compose(:Appender<T>[])` method or the `CompositeAppender.compose(:Iterable<Appender<T>>)` method.
[[geode-logging-slf4j-logback-api-support-appender-delegate]]
==== DelegatingAppender
The `org.springframework.geode.logging.slf4j.logback.DelegatingAppender` is a pass-through Logback `Appender`
implementation that wraps another Logback `Appender` or collection of `Appenders`, such as the `ConsoleAppender`,
a `FileAppender`, a `SocketAppender`, or others. By default, the `DelegatingAppender` delegates to the `NOPAppender`,
thereby doing no actual work.
By default, SBDG registers the `org.springframework.geode.logging.slfj4.logback.DelegatingAppender` with the ROOT
`Logger`, which can be useful for testing purposes.
With a reference to a `DelegatingAppender`, you can add any `Appender` (even a `CompositeAppender`) as the delegate:
.Add `ConsoleAppender` as the "delegate" for the `DelegatingAppender`
====
[source,java]
----
class LoggerConfiguration {
void setupDelegation() {
ConsoleAppender consoleAppender = new ConsoleAppender();
LogbackSupport.resolveLoggerContext().ifPresent(consoleAppender::setContext);
consoleAppender.setImmediateFlush(true);
consoleAppender.start();
LogbackSupport.resolveRootLogger()
.flatMap(LogbackSupport::toLogbackLogger)
.flatMap(rootLogger -> LogbackSupport.resolveAppender(rootLogger,
LogbackSupport.DELEGATE_APPENDER_NAME, DelegatingAppender.class))
.ifPresent(delegateAppender -> delegateAppender.setAppender(consoleAppender));
}
}
----
====
[[geode-logging-slf4j-logback-api-support-appender-string]]
==== StringAppender
The `org.springframework.geode.logging.slf4j.logback.StringAppender` stores a log message in-memory, appended to
a `String`.
The `StringAppender` is useful for testing purposes. For instance, you can use the `StringAppender` to assert that
a `Logger` used by certain application components logged messages at the appropriately configured log level
while other log messages were not logged.
Consider the following example:
.`StringAppender` in Action
====
[source,java]
----
class ApplicationComponent {
private final Logger logger = LoggerFactory.getLogger(getClass());
public void someMethod() {
logger.debug("Some debug message");
// ...
}
public void someOtherMethod() {
logger.info("Some info message");
}
}
// Assuming the ApplicationComponent Logger was configured with log-level 'INFO', then...
class ApplicationComponentUnitTests {
private final ApplicationComponent applicationComponent = new ApplicationComponent();
private final Logger logger = LoggerFactory.getLogger(ApplicationComponent.class);
private StringAppender stringAppender;
@Before
public void setup() {
LogbackSupport.toLogbackLogger(logger)
.map(Logger::getLevel)
.ifPresent(level -> assertThat(level).isEqualTo(Level.INFO));
stringAppender = new StringAppender.Builder()
.applyTo(logger)
.build();
}
@Test
public void someMethodDoesNotLogDebugMessage() {
applicationComponent.someMethod();
assertThat(stringAppender.getLogOutput).doesNotContain("Some debug message");
}
@Test
public void someOtherMethodLogsInfoMessage() {
applicationComponent.someOtherMethod();
assertThat(stringAppender.getLogOutput()).contains("Some info message");
}
}
----
====
There are many other uses for the `StringAppender` and you can use it safely in a multi-Threaded context by calling
`StringAppender.Builder.useSynchronization()`.
When combined with other SBDG provided `Appenders` in conjunction with the `LogbackSupport` class, you have a lot of
power both in application code as well as in your tests.

View File

@@ -0,0 +1,88 @@
[[geode-repositories]]
== Spring Data Repositories
:geode-name: {apache-geode-name}
Using Spring Data Repositories with {geode-name} makes short work of data access operations when you use {geode-name}
as your System of Record (SoR) to persist your application's state.
{spring-data-commons-docs-html}/#repositories[Spring Data Repositories] provide a convenient and powerful way to define
basic CRUD and simple query data access operations by specifying the contract of those data access operations in a Java
interface.
Spring Boot for {geode-name} auto-configures the Spring Data for {geode-name}
{spring-data-geode-docs-html}/#gemfire-repositories[Repository extension]
when either is declared on your application's classpath. You need not do anything special to enable it. You can start
coding your application-specific Repository interfaces.
The following example defines a `Customer` class to model customers and map it to the {geode-name} `Customers` Region
by using the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/mapping/annotation/Region.html[`@Region`]
mapping annotation:
.`Customer` entity class
====
[source,java]
----
package example.app.crm.model;
@Region("Customers")
class Customer {
@Id
private Long id;
private String name;
}
----
====
The following example shows how to declare your Repository (a.k.a. {wikipedia-docs}/Data_access_object[Data Access Object (DAO)])
for `Customers`:
.`CustomerRepository` for peristing and accessing `Customers`
====
[source,java]
----
package example.app.crm.repo;
interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastNameLikeOrderByLastNameDescFirstNameAsc(String customerLastNameWildcard);
}
----
====
Then you can use the `CustomerRepository` in an application service class:
.Inject and use the `CustomerRepository`
====
[source,java]
----
package example.app;
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
class SpringBootApacheGeodeClientCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApacheGeodeClientCacheApplication.class, args);
}
@Bean
ApplicationRunner runner(CustomerRepository customerRepository) {
// Matches Williams, Wilson, etc.
List<Customer> customers =
customerRepository.findByLastNameLikeOrderByLastNameDescFirstNameAsc("Wil%");
// process the list of matching customers...
}
}
----
====
See Spring Data Commons' {spring-data-commons-docs-html}/#repositories[Repositories abstraction]
and Spring Data for {geode-name}'s {spring-data-geode-docs-html}/#gemfire-repositories[Repositories extension]
for more detail.

View File

@@ -0,0 +1,61 @@
[[geode-samples]]
== Samples
:geode-name: {apache-geode-name}
This section contains working examples that show how to use Spring Boot for {geode-name} (SBDG) effectively.
Some examples focus on specific use cases (such as (HTTP) session state caching), while other examples show how SBDG
works under the hood, to give you a better understanding of what is actually happening and how to debug problems with
your Spring Boot {geode-name} applications.
.Example Spring Boot applications using {geode-name}
|===
| Guide | Description | Source
| link:guides/getting-started.html[Getting Started with Spring Boot for {geode-name}]
| Explains how to get started quickly, easily, and reliably building {geode-name} powered applications with Spring Boot.
| {github-samples-url}/intro/getting-started[Getting Started]
| link:guides/boot-configuration.html[Spring Boot Auto-Configuration for {geode-name}]
| Explains what auto-configuration is provided by SBDG and what the auto-configuration does.
| {github-samples-url}/boot/configuration[Spring Boot Auto-Configuration]
| link:guides/boot-actuator.html[Spring Boot Actuator for {geode-name}]
| Explains how to use Spring Boot Actuator for {geode-name} and how it works.
| {github-samples-url}/boot/actuator[Spring Boot Actuator]
| link:guides/boot-security.html[Spring Boot Security for {geode-name}]
| Explains how to configure auth and TLS with SSL when you use {geode-name} in your Spring Boot applications.
| {github-samples-url}/boot/security[Spring Boot Security]
| link:guides/caching-look-aside.html[Look-Aside Caching with Spring's Cache Abstraction and {geode-name}]
| Explains how to enable and use Spring's Cache Abstraction with {geode-name} as the caching provider
for look-aside caching.
| {github-samples-url}/caching/look-aside[Look-Aside Caching]
| link:guides/caching-inline.html[Inline Caching with Spring's Cache Abstraction and {geode-name}]
| Explains how to enable and use Spring's Cache Abstraction with {geode-name} as the caching provider for inline caching.
This sample builds on the look-aside caching sample.
| {github-samples-url}/caching/inline[Inline Caching]
| link:guides/caching-inline-async.html[Asynchronous Inline Caching with Spring's Cache Abstraction and {geode-name}]
| Explains how to enable and use Spring's Cache Abstraction with {geode-name} as the caching provider for asynchronous
inline caching. This sample builds on the look-aside and inline caching samples.
| {github-samples-url}/caching/inline-async[Asynchronous Inline Caching]
| link:guides/caching-near.html[Near Caching with Spring's Cache Abstraction and {geode-name}]
| Explains how to enable and use Spring's Cache Abstraction with {geode-name} as the caching provider for near caching.
This sample builds on the look-aside caching sample.
| {github-samples-url}/caching/near[Near Caching]
| link:guides/caching-multi-site.html[Multi-Site Caching with Spring's Cache Abstraction and {geode-name}]
| Explains how to enable and use Spring's Cache Abstraction with {geode-name} as the caching provider for multi-site
caching. This sample builds on the look-aside caching sample.
| {github-samples-url}/caching/multi-site[Multi-Site Caching]
| link:guides/caching-http-session.html[HTTP Session Caching with Spring Session and {geode-name}]
| Explains how to enable and use Spring Session with {geode-name} to manage HTTP session state.
| {github-samples-url}/caching/http-session[HTTP Session Caching]
|===

View File

@@ -0,0 +1,235 @@
[[geode-security]]
== Security
:geode-name: {apache-geode-name}
This chapter covers security configuration for {geode-name}, which includes both authentication and authorization
(collectively, auth) as well as Transport Layer Security (TLS) using SSL.
NOTE: Securing data at rest is not supported by {geode-name}.
TIP: See the corresponding sample link:guides/boot-security.html[guide] and {github-samples-url}/boot/security[code]
to see Spring Boot Security for {geode-name} in action.
[[geode-security-auth]]
=== Authentication and Authorization
{geode-name} employs username- and password-based {apache-geode-docs}/managing/security/authentication_overview.html[authentication]
and role-based {apache-geode-docs}/managing/security/authorization_overview.html[authorization] to secure your client to
server data exchanges and operations.
Spring Data for {geode-name} provides {spring-data-geode-docs-html}/#bootstrap-annotation-config-security[first-class support]
for {geode-name}'s Security framework, which is based on the
{apache-geode-javadoc}/org/apache/geode/security/SecurityManager.html[`SecurityManager`] interface.
Additionally, {geode-name}'s Security framework is integrated with https://shiro.apache.org/[Apache Shiro].
NOTE: SBDG will eventually provide support for and integration with
https://spring.io/projects/spring-security[Spring Security].
When you use Spring Boot for {geode-name}, which builds Spring Data for {geode-name}, it makes short work of
enabling auth in both your clients and servers.
[[geode-security-auth-servers]]
==== Auth for Servers
The easiest and most standard way to enable auth in the servers of your cluster is to simply define one or more
Apache Shiro https://shiro.apache.org/realm.html[Realms] as beans in the Spring `ApplicationContext`.
Consider the following example:
.Declaring an Apache Shiro Realm
====
[source,java]
----
@Configuration
class ApacheGeodeSecurityConfiguration {
@Bean
DefaultLdapRealm ldapRealm() {
return new DefaultLdapRealm();
}
// ...
}
----
====
When an Apache Shiro Realm (such as `DefaultLdapRealm`) is declared and registered in the Spring `ApplicationContext`
as a Spring bean, Spring Boot automatically detects this `Realm` bean (or `Realm` beans if more than one is configured),
and the servers in the {geode-name} cluster are automatically configured with authentication and authorization enabled.
Alternatively, you can provide a custom, application-specific implementation of {geode-name}'s
{apache-geode-javadoc}/org/apache/geode/security/SecurityManager.html[`SecurityManager`] interface,
declared and registered as a bean in the Spring `ApplicationContext`:
.Declaring a custom {geode-name} `SecurityManager`
====
[source,java]
----
@Configuration
class ApacheGeodeSecurityConfiguration {
@Bean
CustomSecurityManager customSecurityManager() {
return new CustomSecurityManager();
}
// ...
}
----
====
Spring Boot discovers your custom, application-specific `SecurityManager` implementation and configures the servers
in the {geode-name} cluster with authentication and authorization enabled.
TIP: The Spring team recommends that you use Apache Shiro to manage the authentication and authorization of your
servers over implementing {geode-name}'s `SecurityManager` interface.
[[geode-security-auth-clients]]
==== Auth for Clients
When servers in an {geode-name} cluster have been configured with authentication and authorization enabled, clients
must authenticate when connecting.
Spring Boot for {geode-name} makes this easy, regardless of whether you run your Spring Boot `ClientCache` applications
in a local, non-managed environment or run in a cloud-managed environment.
[[geode-security-auth-clients-non-managed]]
===== Non-Managed Auth for Clients
To enable auth for clients that connect to a secure {geode-name} cluster, you need only set a username and password
in Spring Boot `application.properties`:
.Spring Boot `application.properties` for the client
====
[source,txt]
----
# Spring Boot client application.properties
spring.data.gemfire.security.username = jdoe
spring.data.gemfire.security.password = p@55w0rd
----
====
Spring Boot for {geode-name} handles the rest.
[[geode-secuirty-auth-clients-managed]]
===== Managed Auth for Clients
Enabling auth for clients that connect to a {pivotal-cloudcache-name} service instance (PCC)
in {pivotal-cloudfoundry-name} (PCF) is even easier: You need do nothing.
If your Spring Boot application uses SBDG and is bound to PCC, when you deploy (that is, `cf push`) your application
to PCF, Spring Boot for {geode-name} extracts the required auth credentials from the environment that you set up when
you provisioned a PCC service instance in your PCF organization and space. PCC automatically assigns two users with
roles of `cluster_operator` and `developer`, respectively, to any Spring Boot application bound to the PCC service
instance.
By default, SBDG auto-configures your Spring Boot application to run with the user that has the `cluster_operator` role.
This ensures that your Spring Boot application has the necessary permission (authorization) to perform all data access
operations on the servers in the PCC cluster, including, for example, pushing configuration metadata from the client
to the servers in the PCC cluster.
See the <<cloudfoundry-cloudcache-security-auth-runtime-user-configuration,Running Spring Boot applications as a specific user>> section
in the <<cloudfoundry,Pivotal CloudFoundry>> chapter for additional details on user authentication and authorization.
See the <<cloudfoundry,chapter>> (titled "`Pivotal CloudFoundry`") for more general details.
See the {pivotal-cloudcache-docs}/security.html[Pivotal Cloud Cache documentation] for security details
when you use PCC and PCF.
[[geode-security-ssl]]
=== Transport Layer Security using SSL
Securing data in motion is also essential to the integrity of your Spring [Boot] applications.
For instance, it would not do much good to send usernames and passwords over plain text socket connections
between your clients and servers nor to send other sensitive data over those same connections.
Therefore, {geode-name} supports SSL between clients and servers, between JMX clients (such as Gfsh) and the Manager,
between HTTP clients when you use the Developer REST API or Pulse, between peers in the cluster, and when you use
the WAN Gateway to connect multiple sites (clusters).
Spring Data for {geode-name} provides
https://docs.spring.io/spring-data/geode/docs/current/reference/html/#bootstrap-annotation-config-ssl[first-class support]
for configuring and enabling SSL as well. Still, Spring Boot makes it even easier to configure and enable SSL,
especially during development.
{geode-name} requires certain properties to be configured. These properties translate to the appropriate
`javax.net.ssl.*` properties required by the JRE to create secure socket connections by using
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html[JSSE].
However, ensuring that you have set all the required SSL properties correctly is an error prone and tedious task.
Therefore, Spring Boot for {geode-name} applies some basic conventions for you.
You can create a `trusted.keystore` as a JKS-based `KeyStore` file and place it in one of three well-known locations:
* In your application JAR file at the root of the classpath.
* In your Spring Boot application's working directory.
* In your user home directory (as defined by the `user.home` Java System property).
When this file is named `trusted.keystore` and is placed in one of these three well-known locations, Spring Boot
for {geode-name} automatically configures your client to use SSL socket connections.
If you use Spring Boot to configure and bootstrap an {geode-name} server:
.Spring Boot configured and bootstrapped {geode-name} server
====
[source,java]
----
@SpringBootApplication
@CacheServerApplication
class SpringBootApacheGeodeCacheServerApplication {
// ...
}
----
====
Then Spring Boot also applies the same procedure to enable SSL on the servers (between peers).
TIP: During development, it is convenient to *not* set a `trusted.keystore` password when accessing the keys in the JKS
file. However, it is highly recommended that you secure the `trusted.keystore` file when deploying your application to
a production environment.
If your `trusted.keystore` file is secured with a password, you need to additionally specify the following property:
.Accessing a secure `trusted.keystore`
====
[source,txt]
----
# Spring Boot application.properties
spring.data.gemfire.security.ssl.keystore.password=p@55w0rd!
----
====
You can also configure the location of the keystore and truststore files, if they are separate and have not been placed
in one of the default, well-known locations searched by Spring Boot:
.Accessing a secure `trusted.keystore` by location
====
[source,txt]
----
# Spring Boot application.properties
spring.data.gemfire.security.ssl.keystore = /absolute/file/system/path/to/keystore.jks
spring.data.gemfire.security.ssl.keystore.password = keystorePassword
spring.data.gemfire.security.ssl.truststore = /absolute/file/system/path/to/truststore.jks
spring.data.gemfire.security.ssl.truststore.password = truststorePassword
----
====
See the SDG {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableSsl.html[`EnableSsl`]
annotation for all the configuration attributes and the corresponding properties expressed in `application.properties`.
[[geode-security-encryption]]
=== Securing Data at Rest
Currently, neither {geode-name} nor Spring Boot nor Spring Data for {geode-name} offer any support for securing your
data while at rest (for example, when your data has been overflowed or persisted to disk).
To secure data at rest when using {geode-name}, with or without Spring, you must employ third-party solutions, such as
disk encryption, which is usually highly contextual and technology-specific.
For example, to secure data at rest when you use Amazon EC2, see
https://aws.amazon.com/blogs/security/how-to-protect-data-at-rest-with-amazon-ec2-instance-store-encryption/[Instance Store Encryption].

View File

@@ -0,0 +1,305 @@
[[geode-session]]
== Spring Session
:geode-name: {apache-geode-name}
:pcc-name: {pivotal-cloudcache-name}
:vmw-tas-name: {pivotal-cloudfoundry-name}
This chapter covers auto-configuration of Spring Session for {geode-name} to manage (HTTP) session state in a reliable
(consistent), highly available (replicated), and clustered manner.
{spring-session-website}[Spring Session] provides an API and several implementations for managing a user's session
information. It has the ability to replace the `javax.servlet.http.HttpSession` in an application container-neutral way
and provide session IDs in HTTP headers to work with RESTful APIs.
Furthermore, Spring Session provides the ability to keep the `HttpSession` alive even when working with `WebSockets`
and reactive Spring WebFlux `WebSessions`.
A complete discussion of Spring Session is beyond the scope of this document. You can learn more by reading
the {spring-session-docs}[docs] and reviewing the {spring-session-docs}/#samples[samples].
Spring Boot for {geode-name} provides auto-configuration support to configure {geode-name} as the session management
provider and store when {spring-session-data-gemfire-website}[Spring Session for {geode-name}] is on your Spring Boot
application's classpath.
TIP: You can learn more about Spring Session for {geode-name} in the {spring-session-data-gemfire-docs}[docs].
TIP: See the corresponding sample link:guides/caching-http-session.html.html[guide]
and {github-samples-url}/caching/http-session[code] to see Spring Session for {geode-name} in action.
[[geode-session-configuration]]
=== Configuration
You need do nothing special to use {geode-name} as a Spring Session provider implementation, managing the (HTTP) session
state of your Spring Boot application.
To do so, include the appropriate Spring Session dependency on your Spring Boot application's classpath:
.Maven dependency declaration
====
[source,xml]
[subs="verbatim,attributes"]
----
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-geode</artifactId>
<version>{spring-session-data-gemfire-version}</version>
</dependency>
----
====
Alternatively, you may declare the provided `spring-geode-starter-session` dependency in your Spring Boot application
Maven POM (shown here) or Gradle build file:
.Maven dependency declaration
====
[source,xml]
[subs="verbatim,attributes"]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter-session</artifactId>
<version>{version}</version>
</dependency>
----
====
After declaring the required Spring Session dependency, you can begin your Spring Boot application as you normally would:
.Spring Boot Application
====
[source,java]
----
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
// ...
}
----
====
You can then create application-specific Spring Web MVC `Controllers` to interact with the `HttpSession` as needed
by your application:
.Spring Boot Application `Controller` using `HttpSession`
====
[source,java]
----
@Controller
class MyApplicationController {
@GetRequest("...")
public String processGet(HttpSession session) {
// interact with HttpSession
}
}
----
====
The `HttpSession` is replaced by a Spring managed `Session` that is stored in {geode-name}.
[[geode-session-configuration-custom]]
=== Custom Configuration
By default, Spring Boot for {geode-name} (SBDG) applies reasonable and sensible defaults when configuring {geode-name}
as the provider in Spring Session.
For instance, by default, SBDG sets the session expiration timeout to 30 minutes. It also uses a
`ClientRegionShortcut.PROXY` as the data management policy for the {geode-name} client Region that managing the (HTTP)
session state when the Spring Boot application is using a `ClientCache`, which it does
by <<geode-clientcache-applications,default>>.
However, what if the defaults are not sufficient for your application requirements?
In that case, see the next section.
[[geode-session-configuration-custom-properties]]
==== Custom Configuration using Properties
Spring Session for {geode-name} publishes
{spring-session-data-gemfire-docs}/#httpsession-gemfire-configuration-properties[well-known configuration properties]
for each of the various Spring Session configuration options when you use {geode-name} as the (HTTP) session state
management provider.
You can specify any of these properties in Spring Boot `application.properties` to adjust Spring Session's configuration
when using {geode-name}.
In addition to the properties provided in and by Spring Session for {geode-name}, Spring Boot for {geode-name} also
recognizes and respects the `spring.session.timeout` property and the `server.servlet.session.timeout` property, as
discussed {spring-boot-docs-html}/boot-features-session.html[the Spring Boot documentation].
TIP: `spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds` takes precedence over
`spring.session.timeout`, which takes precedence over `server.servlet.session.timeout` when any combination of
these properties have been simultaneously configured in the Spring `Environment` of your application.
[[geode-session-configuration-custom-configurer]]
==== Custom Configuration using a Configurer
Spring Session for {geode-name} also provides the
{spring-session-data-gemfire-javadoc}/org/springframework/session/data/gemfire/config/annotation/web/http/support/SpringSessionGemFireConfigurer.html[`SpringSessionGemFireConfigurer`]
callback interface, which you can declare in your Spring `ApplicationContext` to programmatically control
the configuration of Spring Session when you use {geode-name}.
The `SpringSessionGemFireConfigurer`, when declared in the Spring `ApplicationContext`, takes precedence over any of
the Spring Session (for {geode-name}) configuration properties and effectively overrides them when both are present.
More information on using the `SpringSessionGemFireConfigurer` can be found in the
{spring-session-data-gemfire-docs}/#httpsession-gemfire-configuration-configurer[docs].
[[geode-session-disable]]
=== Disabling Session State Caching
There may be cases where you do not want your Spring Boot application to manage (HTTP) session state by using
{geode-name}.
In certain cases, you may be using another Spring Session provider implementation, such as Redis, to cache and manage
your Spring Boot application's (HTTP) session state. In other cases, you do not want to use Spring Session to manage
your (HTTP) session state at all. Rather, you prefer to use your Web Server's (such as Tomcat's) built-in `HttpSession`
state management capabilities.
Either way, you can specifically call out your Spring Session provider implementation by using
the `spring.session.store-type` property in Spring Boot `application.properties`:
.Use Redis as the Spring Session Provider Implementation
====
[source,txt]
----
#application.properties
spring.session.store-type=redis
...
----
====
If you prefer not to use Spring Session to manage your Spring Boot application's (HTTP) session state at all, you can do
the following:
.Use Web Server Session State Management
====
[source,txt]
----
#application.properties
spring.session.store-type=none
...
----
====
Again, see the Spring Boot {spring-boot-docs-html}/boot-features-session.html[documentation] for more detail.
TIP: You can include multiple provider implementations on the classpath of your Spring Boot application. For instance,
you might use Redis to cache your application's (HTTP) session state while using {geode-name} as your application's
transactional persistent store (System of Record).
NOTE: Spring Boot does not properly recognize `spring.session.store-type=[gemfire|geode]` even though
Spring Boot for {geode-name} is set up to handle either of these property values
(that is, either `gemfire` or `geode`).
[[geode-session-pcc]]
=== Using Spring Session with {pcc-name} (PCC)
Whether you use Spring Session in a Spring Boot, {geode-name} `ClientCache` application to connect to an standalone,
externally managed cluster of {geode-name} servers or to connect to a cluster of servers in a {pcc-name} service
instance managed by a {vmw-tas-name} environment, the setup is the same.
Spring Session for {geode-name} expects there to be a cache Region in the cluster that can store and manage (HTTP)
session state when your Spring Boot application is a `ClientCache` application in the client/server topology.
By default, the cache Region used to store and manage (HTTP) session state is called `ClusteredSpringSessions`.
We recommend that you configure the cache Region name by using the well-known and documented property
in Spring Boot `application.properties`:
.Using properties
====
[source,properties]
----
spring.session.data.gemfire.session.region.name=MySessions
----
====
Alternatively, you can set the name of the cache Region used to store and manage (HTTP) session state by explicitly
declaring the `@EnableGemFireHttpSession` annotation on your main `@SpringBootApplication` class:
.Using `@EnableGemfireHttpSession
====
[source,java]
----
@SpringBootApplication
@EnableGemFireHttpSession(regionName = "MySessions")
class MySpringBootSpringSessionApplication {
// ...
}
----
====
Once you decide on the cache Region name used to store and manage (HTTP) sessions, you must create the cache Region
in the cluster somehow.
On the client, doing so is simple, since SBDG's auto-configuration automatically creates the client `PROXY` Region
that is used to send and receive (HTTP) session state between the client and server for you when either Spring Session
is on the application classpath (for example, `spring-geode-starter-session`) or you explicitly declare
the `@EnableGemFireHttpSession` annotation on your main `@SpringBootApplication` class.
However, on the server side, you currently have a couple of options.
First, you can manually create the cache Region by using Gfsh:
.Create the Sessions Region using Gfsh
====
[source,txt]
----
gfsh> create region --name=MySessions --type=PARTITION --entry-idle-time-expiration=1800
--entry-idle-time-expiration-action=INVALIDATE
----
====
You must create the cache Region with the appropriate name and an expiration policy.
In this case, we created an idle expiration policy with a timeout of `1800` seconds (30 minutes), after which the entry
(session object) is `invalidated`.
NOTE: Session expiration is managed by the Expiration Policy set on the cache Region that is used to store session state.
The Servlet container's (HTTP) session expiration configuration is not used, since Spring Session replaces the Servlet
container's session management capabilities with its own, and Spring Session delegates this behavior to the individual
providers, such as {geode-name}.
Alternatively, you could send the definition for the cache Region from your Spring Boot `ClientCache` application
to the cluster by using the SBDG {spring-boot-data-geode-javadoc}/org/springframework/geode/config/annotation/EnableClusterAware.html[`@EnableClusterAware`] annotation,
which is meta-annotated with SDG's `@EnableClusterConfiguration` annotation:
.Using `@EnableClusterAware`
====
[source,java]
----
@SpringBootApplication
@EnableClusterAware
class MySpringBootSpringSessionApacheGeodeApplication {
// ...
}
----
====
TIP: See the {spring-data-geode-javadoc}/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html[Javadoc]
on the `@EnableClusterConfiguration` annotation and the {spring-data-geode-docs-html}/#bootstrap-annotation-config-cluster[documentation]
for more detail.
However, you cannot currently send expiration policy configuration metadata to the cluster. Therefore, you must manually
alter the cache Region to set the expiration policy:
.Using Gfsh to Alter Region
====
[source,txt]
----
gfsh> alter region --name=MySessions --entry-idle-time-expiration=1800
--entry-idle-time-expiration-action=INVALIDATE
----
====
Now your Spring Boot `ClientCache` application that uses Spring Session in a client/server topology is configured to
store and manage user (HTTP) session state in the cluster. This works for either standalone, externally managed
{geode-name} clusters or when you use PCC running in a {vmw-tas-name} environment.

View File

@@ -0,0 +1,330 @@
[[geode-data-access-region-templates]]
== Data Access with GemfireTemplate
:geode-name: {apache-geode-name}
There are several ways to access data stored in {geode-name}.
For instance, you can use the {apache-geode-javadoc}/org/apache/geode/cache/Region.html[Region API] directly. If you
are driven by the application's domain context, you can use the power of
{spring-data-commons-docs-html}/#repositories[Spring Data Repositories] instead.
While the Region API offers flexibility, it couples your application to {geode-name}, which is usually undesirable
and unnecessary. While using Spring Data Repositories provides a very powerful and convenient abstraction, you give up
the flexibility provided by a lower-level Region API.
A good compromise is to use the https://en.wikipedia.org/wiki/Template_method_pattern[Template software design pattern].
This pattern is consistently and widely used throughout the entire Spring portfolio.
For example, the Spring Framework provides {spring-framework-javadoc}/org/springframework/jdbc/core/JdbcTemplate.html[`JdbcTemplate`]
and {spring-framework-javadoc}/org/springframework/jms/core/JmsTemplate.html[`JmsTemplate`].
Other Spring Data modules, such as Spring Data Redis, offer the
https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/core/RedisTemplate.html[`RedisTemplate`],
and Spring Data for {geode-name} (SDG) itself offers the
{spring-data-gemfire-javadoc}/org/springframework/data/gemfire/GemfireTemplate.html[`GemfireTemplate`].
The `GemfireTemplate` provides a highly consistent and familiar API to perform data access operations on {geode-name}
cache `Regions`.
`GemfireTemplate` offers:
* A simple and convenient data access API to perform basic CRUD and simple query operations on cache Regions.
* Use of Spring Framework's consistent data access {spring-framework-docs}/data-access.html#dao-exceptions[Exception hierarchy].
* Automatic enlistment in the presence of local cache transactions.
* Consistency and protection from {apache-geode-javadoc}/org/apache/geode/cache/Region.html[Region API] breaking changes.
Given these advantages, Spring Boot for {geode-name} (SBDG) auto-configures `GemfireTemplate` beans for each Region
present in the {geode-name} cache.
Additionally, SBDG is careful not to create a `GemfireTemplate` if you have already declared a `GemfireTemplate` bean
in the Spring `ApplicationContext` for a given Region.
[[geode-data-access-region-templates-explicit-declaration]]
=== Explicitly Declared Regions
Consider an explicitly declared Region bean definition:
. Explicitly Declared Region Bean Definition
====
[source,java]
----
@Configuration
class GeodeConfiguration {
@Bean("Example")
ClientRegionFactoryBean<?, ?> exampleRegion(GemFireCache gemfireCache) {
// ...
}
}
----
====
SBDG automatically creates a `GemfireTemplate` bean for the `Example` Region by using the bean name `exampleTemplate`.
SBDG names the `GemfireTemplate` bean after the Region by converting the first letter in the Region's name to lower case
and appending `Template` to the bean name.
In a managed Data Access Object (DAO), you can inject the Template:
====
[source,java]
----
@Repository
class ExampleDataAccessObject {
@Autowired
@Qualifier("exampleTemplate")
private GemfireTemplate exampleTemplate;
}
----
====
You should use the `@Qualifier` annotation to qualify which `GemfireTemplate` bean you are specifically referring,
especially if you have more than one Region bean definition.
[[geode-data-access-region-templates-entity-defined]]
=== Entity-defined Regions
SBDG auto-configures `GemfireTemplate` beans for entity-defined Regions.
Consider the following entity class:
.Customer class
====
[source,java]
----
@Region("Customers")
class Customer {
// ...
}
----
====
Further consider the following configuration:
.Apache Geode Configuration
====
[source,java]
----
@Configuration
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
class GeodeConfiguration {
// ...
}
----
====
SBDG auto-configures a `GemfireTemplate` bean for the `Customers` Region named `customersTemplate`, which you can then
inject into an application component:
.CustomerService application component
====
[source,java]
----
@Service
class CustomerService {
@Bean
@Qualifier("customersTemplate")
private GemfireTemplate customersTemplate;
}
----
====
Again, be careful to qualify the `GemfireTemplate` bean injection if you have multiple Regions, whether declared
explicitly or implicitly, such as when you use the `@EnableEntityDefineRegions` annotation.
[[geode-data-access-region-templates-caching-defined]]
=== Caching-defined Regions
SBDG auto-configures `GemfireTemplate` beans for caching-defined Regions.
When you use Spring Framework's {spring-framework-docs}/integration.html#cache[Cache Abstraction] backed by {geode-name},
one requirement is to configure Regions for each of the caches specified in the
{spring-framework-docs}integration.html#cache-annotations[caching annotations] of your application service components.
Fortunately, SBDG makes enabling and configuring caching easy and <<geode-caching-provider,automatic>>.
Consider the following cacheable application service component:
.Cacheable `CustomerService` class
====
[source,java]
----
@Service
class CacheableCustomerService {
@Bean
@Qualifier("customersByNameTemplate")
private GemfireTemplate customersByNameTemplate;
@Cacheable("CustomersByName")
public Customer findBy(String name) {
return toCustomer(customersByNameTemplate.query("name = " + name));
}
}
----
====
Further consider the following configuration:
.Apache Geode Configuration
====
[source,java]
----
@Configuration
@EnableCachingDefinedRegions
class GeodeConfiguration {
@Bean
public CustomerService customerService() {
return new CustomerService();
}
}
----
====
SBDG auto-configures a `GemfireTemplate` bean named `customersByNameTemplate` to perform data access operations on
the `CustomersByName` (`@Cacheable`) Region. You can then inject the bean into any managed application component,
as shown in the preceding application service component example.
Again, be careful to qualify the `GemfireTemplate` bean injection if you have multiple Regions, whether declared
explicitly or implicitly, such as when you use the `@EnableCachingDefineRegions` annotation.
WARNING: Autowiring (that is, injecting) `GemfireTemplate` beans auto-configured by SBDG for caching-defined Regions
into your application components does not always work. This has to do with the Spring container bean creation process.
In those cases, you may need to lazily lookup the `GemfireTemplate` by using
`applicationContext.getBean("customersByNameTemplate", GemfireTemplate.class)`. This is not ideal, but it works when
autowiring does not.
[[geode-data-access-region-templates-native-defined]]
=== Native-defined Regions
SBDG even auto-configures `GemfireTemplate` beans for Regions that have been defined with {geode-name} native
configuration metadata, such as `cache.xml`.
Consider the following {geode-name} native `cache.xml`:
.Client `cache.xml`
====
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<client-cache xmlns="http://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<region name="Example" refid="LOCAL"/>
</client-cache>
----
====
Further consider the following Spring configuration:
.Apache Geode Configuration
====
[source,java]
----
@Configuration
@EnableGemFireProperties(cacheXmlFile = "cache.xml")
class GeodeConfiguration {
// ...
}
----
====
SBDG auto-configures a `GemfireTemplate` bean named `exampleTemplate` after the `Example` Region defined in `cache.xml`.
You can inject this template as you would any other Spring-managed bean:
.Injecting the `GemfireTemplate`
====
[source,java]
----
@Service
class ExampleService {
@Autowired
@Qualifier("exampleTemplate")
private GemfireTemplate exampleTemplate;
}
----
====
The rules described earlier apply when multiple Regions are present.
[[geode-data-access-region-templates-rules]]
=== Template Creation Rules
Fortunately, SBDG is careful not to create a `GemfireTemplate` bean for a Region if a template by the same name already
exists.
For example, consider the following configuration:
.Apache Geode Configuration
====
[source,java]
----
@Configuration
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
class GeodeConfiguration {
@Bean
public GemfireTemplate customersTemplate(GemFireCache cache) {
return new GemfireTemplate(cache.getRegion("/Customers"));
}
}
----
====
Further consider the following example:
.Customer class
====
[source,java]
----
@Region("Customers")
class Customer {
// ...
}
----
====
Because you explicitly defined and declared the `customersTemplate` bean, SBDG does not automatically create a template
for the `Customers` Region. This applies regardless of how the Region was created, whether by using
`@EnableEntityDefinedRegions`, `@EnableCachingDefinedRegions`, explicitly declaring Regions,
or natively defining Regions.
Even if you name the template differently from the Region for which the template was configured, SBDG conserves
resources and does not create the template.
For example, suppose you named the `GemfireTemplate` bean `vipCustomersTemplate`, even though the Region name
is `Customers`, based on the `@Region` annotated `Customer` class, which specified the `Customers` Region.
With the following configuration, SBDG is still careful not to create the template:
.Apache Geode Configuration
====
[source,java]
----
@Configuration
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
class GeodeConfiguration {
@Bean
public GemfireTemplate vipCustomersTemplate(GemFireCache cache) {
return new GemfireTemplate(cache.getRegion("/Customers"));
}
}
----
====
SBDG identifies that your `vipCustomersTemplate` is the template used with the `Customers` Region, and SBDG does not
create the `customersTemplate` bean, which would result in two `GemfireTemplate` beans for the same Region.
NOTE: The name of your Spring bean defined in Java configuration is the name of the method if the Spring bean is not
explicitly named by using the `name` attribute or the `value` attribute of the `@Bean` annotation.

View File

@@ -0,0 +1,261 @@
[[geode-testing]]
== Testing
:gemfire-name: {pivotal-gemfire-name}
:geode-name: {apache-geode-name}
:stdg-website: https://github.com/spring-projects/spring-test-data-geode
Spring Boot for {geode-name} (SBDG), with help from {stdg-website}[Spring Test for {geode-name} (STDG)], offers
first-class support for both unit and integration testing with {geode-name} in your Spring Boot applications.
TIP: See the Spring Test for Apache Geode (STDG) {stdg-website}/#stdg-in-a-nutshell[documentation] for more details.
[[geode-testing-unit]]
=== Unit Testing
Unit testing with {geode-name} using mock objects in a Spring Boot Test requires only that you declare the STDG
`@EnableGemFireMockObjects` annotation in your test configuration:
.Unit Test with {geode-name} using Spring Boot
====
[source,java]
----
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootApacheGeodeUnitTest extends IntegrationTestsSupport {
@Autowired
private UserRepository userRepository;
@Test
public void saveAndFindUserIsSuccessful() {
User jonDoe = User.as("jonDoe");
assertThat(this.userRepository.save(jonDoe)).isNotNull();
User jonDoeFoundById = this.userRepository.findById(jonDoe.getName()).orElse(null);
assertThat(jonDoeFoundById).isEqualTo(jonDoe);
}
@SpringBootApplication
@EnableGemFireMockObjects
@EnableEntityDefinedRegions(basePackageClasses = User.class)
static class TestConfiguration { }
}
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "as")
@Region("Users")
class User {
@Id
@lombok.NonNull
private String name;
}
interface UserRepository extends CrudRepository<User, String> { }
----
====
This test class is not a "`pure`" unit test, particularly since it bootstraps an actual Spring `ApplicationContext`
using Spring Boot. However, it does mock all {geode-name} objects, such as the `Users` `Region` declared by the `User`
application entity class, which was annotated with SDG's `@Region` mapping annotation.
This test class conveniently uses Spring Boot's auto-configuration to auto-configure an {geode-name} `ClientCache`
instance. In addition, SDG's `@EnableEntityDefinedRegions` annotation was used to conveniently create the {geode-name}
"Users` `Region` to store instances of `User`.
Finally, Spring Data's Repository abstraction was used to conveniently perform basic CRUD (such as `save`) and simple
(OQL) query (such as `findById`) data access operations on the `Users` `Region`.
Even though the {geode-name} objects (such as the `Users` `Region`) are "`mock objects`", you can still perform many of
the data access operations required by your Spring Boot application's components in an {geode-name} API-agnostic way
-- that is, by using Spring's powerful programming model and constructs.
TIP: By extending STDG's `org.springframework.data.gemfire.tests.integration.IntegrationTestSupport` class, you ensure
that all {geode-name} mock objects and resources are properly released after the test class runs, thereby preventing
any interference with downstream tests.
While STDG tries to {stdg-website}/#mock-regions-with-data[mock the functionality and behavior] for many `Region`
operations, it is not pragmatic to mock them all. For example, it would not be practical to mock `Region` query
operations involving complex OQL statements that have sophisticated predicates.
If such functional testing is required, the test might be better suited as an integration test. Alternatively, you can
follow the advice in this section about {stdg-website}/#mocking-unsupported-region-operations[unsupported Region operations].
In general, STDG provides the following capabilities when mocking {geode-name} objects:
* {stdg-website}#mock-object-scope--lifecycle-management[Mock Object Scope & Lifecycle Management]
* {stdg-website}#mock-regions-with-data[Support for Mock Regions with Data]
* {stdg-website}#mock-region-callbacks[Support for Mocking Region Callbacks]
* {stdg-website}#mocking-unsupported-region-operations[Support for Mocking Unsupported Region Operations]
TIP: See the documentation on {stdg-website}/#unit-testing-with-stdg[Unit Testing with STDG] for more details.
[[geode-testing-integration]]
=== Integration Testing
Integration testing with {geode-name} in a Spring Boot Test is as simple as *not* declaring STDG's
`@EnableGemFireMockObjects` annotation in your test configuration. You may then want to use SBDG's `@EnableClusterAware`
annotation to conditionally detect the presence of a {geode-name} cluster:
.Using `@EnableClusterAware` in test configuration
====
[source,java]
----
@SpringBootApplication
@EnableClusterAware
@EnableEntityDefinedRegions(basePackageClasses = User.class)
static class TestConfiguration { }
----
====
The SBDG `@EnableClusterAware` annotation conveniently toggles your auto-configured `ClientCache` instance between
local-only mode and client/server. It even pushes configuration metadata (such as `Region` definitions) up to
the servers in the cluster that are required by the application to store data.
In most cases, in addition to testing with "`live`" {geode-name} objects (such as Regions), we also want to test in
a client/server capacity. This unlocks the full capabilities of the {geode-name} data management system in a Spring
context and gets you as close as possible to production from the comfort of your IDE.
Building on our example from the section on <<geode-testing-unit>>, you can modify the test to use "`live`" {geode-name}
objects in a client/server topology as follows:
.Integration Test with {geode-name} using Spring Boot
====
[source,java]
----
@ActiveProfiles("client")
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.data.gemfire.management.use-http=false")
public class SpringBootApacheGeodeIntegrationTest extends ForkingClientServerIntegrationTestsSupport {
@BeforeClass
public static void startGeodeServer() throws IOException {
startGemFireServer(TestGeodeServerConfiguration.class);
}
@Autowired
private UserRepository userRepository;
@Test
public void saveAndFindUserIsSuccessful() {
User jonDoe = User.as("jonDoe");
assertThat(this.userRepository.save(jonDoe)).isNotNull();
User jonDoeFoundById = this.userRepository.findById(jonDoe.getName()).orElse(null);
assertThat(jonDoeFoundById).isEqualTo(jonDoe);
assertThat(jonDoeFoundById).isNotSameAs(jonDoe);
}
@SpringBootApplication
@EnableClusterAware
@EnableEntityDefinedRegions(basePackageClasses = User.class)
@Profile("client")
static class TestGeodeClientConfiguration { }
@CacheServerApplication
@Profile("server")
static class TestGeodeServerConfiguration {
public static void main(String[] args) {
new SpringApplicationBuilder(TestGeodeServerConfiguration.class)
.web(WebApplicationType.NONE)
.profiles("server")
.build()
.run(args);
}
}
}
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "as")
@Region("Users")
class User {
@Id
@lombok.NonNull
private String name;
}
interface UserRepository extends CrudRepository<User, String> { }
----
====
The application client/server-based integration test class extend STDG's
`org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport` class.
This ensures that all {geode-name} objects and resources are properly cleaned up after the test class runs. In addition,
it coordinates the client and server components of the test (for example connecting the client to the server using a
random port).
The {geode-name} server is started in a `@BeforeClass` setup method:
.Start the {geode-name} server
[source,java]
----
class SpringBootApacheGeodeIntegrationTest extends ForkingClientServerIntegrationTestsSupport {
@BeforeClass
public static void startGeodeServer() throws IOException {
startGemFireServer(TestGeodeServerConfiguration.class);
}
}
----
STDG lets you configure the {geode-name} server with Spring configuration, specified in
the `TestGeodeServerConfiguration` class. The Java class needs to provide a `main` method. It uses
the `SpringApplicationBuilder` to bootstrap the {geode-name} `CacheServer` application:
.{geode-name} server configuration
====
[source,java]
----
@CacheServerApplication
@Profile("server")
static class TestGeodeServerConfiguration {
public static void main(String[] args) {
new SpringApplicationBuilder(TestGeodeServerConfiguration.class)
.web(WebApplicationType.NONE)
.profiles("server")
.build()
.run(args);
}
}
----
====
In this case, we provide minimal configuration, since the configuration is determined and pushed up to the server
by the client. For example, we do not need to explicitly create the `Users` `Region` on the server since it is
implicitly handled for you by the SBDG/STDG frameworks from the client.
We take advantage of Spring profiles in the test setup to distinguish between the client and server configuration.
Keep in mind that the test is the "`client`" in this arrangement.
The STDG framework does what the supporting class demands: "`forking`" the Spring Boot-based, {geode-name} `CacheServer`
application in a separate JVM process. Subsequently, the STDG framework stops the server upon completion of the tests
in the test class.
You are free to start your servers or cluster however you choose. STDG provides this capability as a convenience for you,
since it is a common concern.
This test class is simple. STDG can handle much more complex test scenarios.
TIP: Review SBDG's test suite to witness the full power and functionality of the STDG framework for yourself.
NOTE: See the documentation on {stdg-website}/#integration-testing-with-stdg[Integration Testing with STDG]
for more details.

View File

@@ -0,0 +1,410 @@
[[geode-samples-boot-actuator]]
= Spring Boot Actuator for Apache Geode
:geode-version: {apache-geode-doc-version}
:geode-name: Apache Geode
:geode-docs: https://geode.apache.org/docs/guide/{geode-version}
:images-dir: ./images
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/html
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks through using {spring-boot-docs}/production-ready.html[Spring Boot Actuator] to assess the state
of your running {geode-name}, Spring Boot application.
The goal for SBDG's Spring Boot Actuator integration is to enable users to effectively manage and monitor their
Spring Boot applications using {geode-name} in a production environment.
In particular, SBDG's integration with Spring Boot Actuator currently focuses on enabling
{spring-boot-docs}/production-ready-endpoints.html#production-ready-health[Health Information]
for your application. In the future, SBDG will provide dedicated support for https://micrometer.io/[Micrometer] metrics.
This guide assumes you are already familiar with Spring Boot and {geode-name}.
TIP: Refer to the link:../index.html#actuator[Spring Boot Actuator] chapter in the reference documentation
for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geode-samples-boot-actuator-example]]
== Example Application
This guide additionally provides a working {github-samples-url}/boot/actuator[example application] to review some of
the Actuator Health Endpoints.
The example application is a simple Temperature Monitoring Service, simulating a real-world, Internet of Things (IOT)
Use Case (UC). Essentially, the application collects temperature readings from different temperature sensors (devices)
and uses the readings to monitor fluctuations in temperatures so that someone or something can respond accordingly.
The sensor devices could be located around the world in multiple locations in the planet's oceans.
[[geode-samples-boot-actuator-example-temperaturereading]]
=== TemperatureReading
First, we start by modeling a temperature reading, which stores the temperature in Fahrenheit along with a timestamp
for when the reading was measured.
Clearly, a more practical example would store the temperature measurement as a `double` or `BigDecimal` along with
the ability to support different scales (Celsius, Fahrenheit, Kelvin).
However in this case, we simply model the `TemperatureReading` as:
.TemperatureReading class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/model/TemperatureReading.java[tags=class]
----
The `TemperatureReading` class is annotated with SDG's `@Region` mapping annotation to declare the {geode-name} Region
in which `TemperatureReadings` will be persisted.
[[geode-samples-boot-actuator-example-temperatureevent]]
=== TemperatureEvent
A key concern of our application is to "monitor" temperature readings. Therefore, we need some way to capture
temperature change events.
For that, we introduce the simple `TemperatureEvent` base class:
.TemperatureEvent class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/event/TemperatureEvent.java[tags=class]
----
`TemperatureEvent` is a Spring `ApplicationEvent` capable of being published to to Spring `@EventListeners` registered
in the Spring application context.
Additionally, we provide 2 subclasses: `BoilingTemperatureEvent` and `FreezingTemperatureEvent` to further classify
temperature changes.
[[geode-samples-boot-actuator-example-temperaturereadingrepository]]
=== TemperatureReadingRepository
To perform basic data access operations (e.g. CRUD) and simple queries on `TemperatureReadings`, we create
a Spring Data _Repository_:
.TemperatureReadingRepository interface
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/repo/TemperatureReadingRepository.java[tags=class]
----
The _Repository_ showcases a an example, derived query methods, `findByTimestampGreaterThanAndTimestampLessThan(..)`
along with 2 `@Query` annotated query methods using raw OQL queries.
[[geode-samples-boot-actuator-example-temperaturemonitor]]
=== TemperatureMonitor service class
To receive temperature change events, we need a class to monitor changes. This capability is built on {geode-name}'s
{geode-docs}/developing/continuous_querying/chapter_overview.html[Continuous Query (CQ)] functionality.
With {geode-name} you can register an (OQL) Query with the servers in the cluster that runs continuously, sending
notifications back to the client anytime data changes to match the predicate in our query, or queries.
For our purposes, we will simply monitor the temperature anytime it rises above boiling (212 °F)
or drops below freezing (32 °F):
.TemperatureMonitor class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/service/TemperatureMonitor.java[tags=class]
----
When the temperature changes, triggering our queries, we receive an event and publish an appropriate `TemperatureEvent`
by using the Spring container's `ApplicationEventPublisher`, which has been injected into the monitor class.
The 2 OQL queries have been defined to query the "TemperatureReadings" Region and fire anytime the temperature
rises is above boiling (212 °F) or drops below freezing (32 °F).
[[geode-samples-boot-actuator-example-temperaturesensor]]
=== TemperatureSensor service class
Of course, we need a simulator to generate temperatures.
For that we have the `TemperatureSensor` class:
.TemperatureSensor class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/service/TemperatureSensor.java[tags=class]
----
To generate a stream of temperatures, we use Spring's {spring-framework-docs}/integration.html#scheduling[Scheduling Service]
along with a `Random` stream of `ints` provided via an `Iterator`, as seen in the `readTemperature()` method.
The `readTemperature()` method is then storing the *new* `TemperatureReading` in the "TemperatureReadings" Region
as designated by our `TemperatureReading` model class's, `@Region` annotation declaration:
.@Region declaration
[source,java]
----
@Region("TemperatureReadings")
public class TemperatureReading { }
----
Additionally, the `readTempeature()` method uses our `TemperatureReadingRepository` to perform the necessary
data access operations.
[[geode-samples-boot-actuator-example-server]]
=== Server
Now, we need a couple of main application classes to actually have the application do something useful.
We start with an Spring Boot, {geode-name} Server application functioning as the temperature sensor (device)
using the `TemperatureSensor` class:
.BootGeodeServerApplication main class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/geode/server/BootGeodeServerApplication.java[tags=class]
----
This class is annotated with `@SpringBootApplication` making it a proper Spring Boot application. It uses Spring Boot's
`SpringApplicationBuilder` to configure and bootstrap the server application.
This class is also annotated with SDG's `@CacheServerApplication` making it a proper {geode-name} Server with a peer
`Cache` instance along with a `CacheServer` to accept client connections. This effectively overrides SBDG's default
`ClientCache` instance.
Additionally, the class enables several other features, such as Spring's _Scheduling Service_, {geode-name} Statistics,
and entity-defined Regions making the creation of our server-side, partitioned "TemperatureReadings" Region simple.
By enabling {geode-name} statistics, we allow Spring Boot's Actuator, `HealthIndicators` to collect metrics about
our running {geode-name} Server as well.
Because we have enabled scheduling (with `@EnableScheduling`) and declared the `TemperatureSensor` class as a bean
in the Spring application context, the application will immediately start generating temperature readings, which are
recorded to the "TemperatureReadings" Region.
Finally, our class registers a couple {geode-name} Region Indexes to make the Actuator `HealthIndicator` information
more interesting, particularly since we are running continuous queries.
This is a Servlet-based application as well since our Actuator, `HealthIndicator` endpoints are exposed via HTTP.
TIP: There are multiple ways to configure and bootstrap an {geode-name} Server, and the server-side of our application.
Using Spring Boot is one of the easier ways.
[[geode-samples-boot-actuator-example-client]]
=== Client
Next, we need a client functioning as the "monitor" in our Temperature Service application.
.BootGeodeClientApplication main class
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/java/example/app/temp/geode/client/BootGeodeClientApplication.java[tags=class]
----
Again, this class is also annotated with `@SpringBootApplication` making it a proper Spring Boot application. It too
uses Spring Boot's `SpringApplicationBuilder` class to configure and bootstrap the client application.
Unlike our server, this class is not annotated with any SDG `@*Cache*Application` annotations since SBDG provides us
a `ClientCache` instance by default. We want this application to be a client in our setup.
Like our server, we enable entity-defined Regions to easily and quickly create the client `PROXY` Region
for "TemperatureReadings", which will send/receive data to/from the server-side Region by the same name.
And, like our server application, we declare a bean of type `TemperatureMonitor` in the Spring application context
to receive the `TemperatureReading` events. This enables the CQ registration and event handlers
to sets things in motion.
Probably the most interesting part is that we an `@EventListener` to receive the `TemperatureEvents` to log
the `TemperatureReadings` to `System.err`.
[[geode-samples-boot-actuator-example-run]]
== Run the Example
It is time to run the example.
First, we need to start the server.
You can run the server from your IDE (e.g. IntelliJ IDEA) by creating a run profile configuration for the
`example.app.temp.geode.server.BootGeodeServerApplication` class. Make sure to set the JVM argument to activate the
"_server_" Spring Profile: `-Dspring.profiles.active=server`.
Alternatively, you can run the server from the command-line using the `gradlew` command:
`$ gradlew :spring-geode-samples-boot-actuator:runServer`
The `gradlew` command and `runServer` Gradle Task sets the configuration (e.g. Spring Profile) for you. The `gradlew`
command is ran in the directory where you cloned the `spring-boot-data-geode` project (**not** in
`spring-boot-data-geode/spring-geode-samples/boot/actuator/`).
If you wish to adjust the log levels of {geode-name} or Spring Boot while running the client and server applications,
then you can set the log levels of the individual Loggers (i.e. `org.apache` and `org.springframework`)
in `src/main/resources/logback.xml`:
.spring-geode-samples/boot/actuator/src/main/resources/logback.xml
[source,java]
----
include::{samples-dir}/boot/actuator/src/main/resources/logback.xml[]
----
Running the server from your IDE:
.Run the server
[source,txt]
----
/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/bin/java -server -ea -Dspring.profiles.active=server
... example.app.temp.geode.server.BootGeodeServerApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.9.RELEASE)
TEMPERATURE READING [99 °F]
TEMPERATURE READING [-17 °F]
TEMPERATURE READING [67 °F]
TEMPERATURE READING [9 °F]
TEMPERATURE READING [102 °F]
TEMPERATURE READING [10 °F]
...
----
After the server starts and begins to log temperature readings (as shown above), then start the client.
The client can be ran in the same manner as the server, from your IDE or from the command-line using `gradlew`.
To run the client in your IDE (e.g. IntelliJ IDEA) then create a run profile configuration for the
`example.app.temp.geode.client.BootGeodeClientApplication` and set the JVM argument to activate the "_client_"
Spring Profile: `-Dspring.profiles.active=client".
Alternatively, you can run the client from the command-line using the following command:
`$ gradlew :spring-geode-samples-boot-actuator:bootRun`
NOTE: The client must be run in a separate terminal, unless you ran the server as a background process with the `&`
on Linux/UNIX based systems. See https://linuxize.com/post/how-to-run-linux-commands-in-background/[here]
for more details.
Running the client from your IDE:
.Run the client
[source,txt]
----
/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/bin/java -server -ea -Dspring.profiles.active=client
... example.app.temp.geode.client.BootGeodeClientApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.9.RELEASE)
COLD TEMPERATURE READING [-22 °F]
COLD TEMPERATURE READING [25 °F]
HOT TEMPERATURE READING [305 °F]
COLD TEMPERATURE READING [-82 °F]
HOT TEMPERATURE READING [295 °F]
HOT TEMPERATURE READING [326 °F]
HOT TEMPERATURE READING [378 °F]
COLD TEMPERATURE READING [-4 °F]
...
----
You should only see boiling and freezing temperatures logged to the client's console. This is because our client
only "monitors" and logs temperature readings above or below a certain threshold (boiling/freezing), as defined by
the Continuous Queries (CQ).
Both the server and client will continue to run, generating and logging temperatures, until you stop the JVM processes.
[[geode-samples-boot-actuator-example-monitor]]
== Monitoring our Example with Spring Boot Actuator, HealthIndicator Endpoints
After the application has been running for some time, we can inspect the Spring Boot Actuator, HealthIndicator Endpoints
provided by SBDG to monitor our application's health and runtime performance in addition to basic configuration
meta-data used to configure {geode-name} at runtime.
[[geode-samples-boot-actuator-example-monitor-client]]
=== Client Health Information
To navigate to the client applications Actuator Health endpoints, open a Web Browser (e.g. Google Chrome) to:
`http://localhost:9191/actuator/health`
This will output a JSON document similar to:
image::{images-dir}/client-actuator-health-endpoints.png[]
We can see details on our client "TemperatureReadings" Region:
image::{images-dir}/client-actuator-health-region-endpoint.png[]
As well as our CQs:
image::{images-dir}/client-actuator-health-cq-endpoint.png[]
When you hit refresh, the metrics will update.
[[geode-samples-boot-actuator-example-monitor-server]]
=== Server Health Information
To navigate to the server applications Actuator Health endpoint, open a Web Browser (e.g. Google Chrome) to:
`http://localhost:8181/actuator/health`
This will output a JSON document similar to:
image::{images-dir}/server-actuator-health-endpoints.png[]
We can see details of the server "TemperatureReadings" Region:
image::{images-dir}/server-actuator-health-region-endpoint.png[]
As well as our Indexes:
image::{images-dir}/server-actuator-health-indexes-endpoint.png[]
And our CacheServer serving clients:
image::{images-dir}/server-actuator-health-cacheserver-endpoint.png[]
When you hit refresh, the metrics will update.
[[geode-samples-boot-actuator-enabling]]
== Enabling Spring Boot Actuator
All of this was made possible by including the following dependency on our application classpath:
.Maven POM
[source,xml]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter-actuator</artifactId>
</dependency>
----
Additionally, in Spring Boot `application.properties`, you must enable the following:
.Spring Boot `application.properties`
[source,txt]
----
management.endpoint.health.show-details=always
----
[[geode-samples-boot-actuator-conclusion]]
== Conclusion
Hopefully this guide has shown you how to use the Spring Boot Actuator feature for {geode-name}.
You are encouraged to read more about {spring-boot-docs}/production-ready.html[Spring Boot's Actuator] functionality.
Additionally, you can find out more about {geode-name} Statistics
in the {geode-docs}/managing/statistics/chapter_overview.html[User Guide].
link:../index.html#geode-samples[Back to Samples]

View File

@@ -0,0 +1,352 @@
[[geode-samples-boot-security]]
= Spring Boot Security for Apache Geode
Patrick Johnson, John Blum
:gemfire-name: VMware Tanzu GemFire
:geode-name: Apache Geode
:pcc-docs: https://docs.pivotal.io/p-cloud-cache/1-11
:shiro-docs: https://shiro.apache.org/realm
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks you through building a simple Spring Boot application enabled with Security, specifically Auth
and TLS using SSL. You should already be familiar with Spring Boot and {geode-name}.
TIP: Refer to the link:../index.html#geode-security[Security] chapter in the reference documentation
for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geode-samples-boot-security-background]]
== Background
Security is critical to most applications. It is important to be able to control who or what can access your application
and what the subject is allowed to do. This is where Auth^2^ (Authentication & Authorization) comes in.
Authentication is used to verify a clients identity (human or application) in exchange for some sort of credentials.
Once authenticated, a client must be authorized before they can perform any actions. Authorization checks the
permissions required to perform an action (e.g. read data, modify data, change configuration, and so on) against the
permissions assigned to the clients identity
Of course, sending passwords and other sensitive information as plain text over the wire is not very secure, so we also
need to enable SSL/TLS to encrypt the information as it is transmitted. Now, our applications are secure.
WARNING: {geode-name} nor SBDG provide any support for _securing_ https://en.wikipedia.org/wiki/Data_at_rest[_data at rest_],
such as with _disk encryption_. This concern is typically left to hardware-based solutions.
TIP: See the Spring Boot for {geode-name} (SBDG) chapter on link:../index.html#geode-security[Security] for more
information.
[[geode-samples-boot-security-client]]
== Securing a Client Application
Enabling auth on the client is mostly taken care of by Spring Boots Auto-configuration.
TIP: For more details on Spring Boot's Auto-configuration applied to Security, and securing the client and server,
see link:boot-configuration.html#geode-samples-boot-configuration-clientserver-security[here].
In Spring Boot `application.properties`, set the `spring.data.gemfire.security.username` and
`spring.data.gemfire.security.password` properties to the username and password your application will use to
authenticate.
Enabling SSL on the client requires you to put a `trusted.keystore` file (a _Java KeyStore_) in a well-known place,
such as your applications working directory or your home directory, and Auto-configuration will do the rest.
If your `trusted.keystore` has a password (as it should), you will need to specify it using the
`spring.data.gemfire.security.ssl.keystore.password` property in your Spring Boot `application.properties` file. You can
generate a Keystore using https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html[Java Keytool].
TIP: See Spring Boot for {geode-name}'s (SBDG) chapter on link:../index.html#geode-security-auth-clients[Auth for Clients]
for more information.
[[geode-samples-boot-security-server]]
== Securing a Server Application
Auto-configuration does not do as much for you when configuring auth on the server as it does on the client. In order to
enable auth, you need to do two things.
First, annotate your configuration class with `@EnableSecurity`. Second, because {geode-name}s security is integrated
with Apache Shiro, define at least one Shiro Realm as a bean in your Spring `ApplicationContext`.
.Example Shiro Realm bean:
[source,java]
----
include::{samples-dir}/boot/security/src/main/java/example/app/security/server/BootGeodeSecurityServerApplication.java[tags=realm]
----
You can find more information on Apache Shiro and how to configure a Realm link:{shiro-docs}[here].
Enabling SSL on the server is essentially the same as for the client, just put your `trusted.keystore` file (a _Java
KeyStore_) in a well-known place, like your applications working directory or your home directory. If your
`trusted.keystore` has a password (as it should), you will need to specify it using the
`spring.data.gemfire.security.ssl.keystore.password` property in your Spring Boot `application.properties` file. You can
generate a Keystore using https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html[Java Keytool].
TIP: See Spring Boot for {geode-name}'s (SBDG) chapter on link:../index.html#geode-security-auth-servers[Auth for Servers]
for more information.
[[geode-samples-boot-security-example]]
== Example
To demonstrate the proper way to configure a Spring Boot application with security, we put together a simple example.
The example is made up of two main parts:
A client - `BootGeodeSecurityClientApplication`.
A server - `BootGeodeSecurityServerApplication`.
[[geode-samples-boot-security-example-behavior]]
=== What it Does
The example is very minimal and only performs some basic data access operations in a secure context. The server starts
up, and then the client connects to the server and tries to do two things:
1. Write a new value into Customers, which succeeds.
2. Read a value from Customers, which fails because the user that the client authenticates with is only authorized to
write data, not read it.
This behavior may change depending on the credentials used to authenticate. For example, running with
“_cluster_operator_” credentials on the platform will result in both read and write operations succeeding.
=== Classes
[[geode-samples-boot-security-example-classes-client]]
==== BootGeodeSecurityClientApplication
.Spring Boot, {geode-name} Client Application
[source,java]
----
include::{samples-dir}/boot/security/src/main/java/example/app/security/client/BootGeodeSecurityClientApplication.java[tag=class]
----
This class is a Spring Boot, {geode-name} client application (i.e. `ClientCache`) configured to authenticate when
connecting to a cluster of servers using connections secured with SSL.
The `@SpringBootApplication` annotation declares the application to be a Spring Boot application. With SBDG on the
application classpath, a `ClientCache` instance will be auto-configured automatically, making the application a cache
client capable of connecting to the cluster.
Finally, we declare a `ApplicationRunner` bean to perform some basic data access operations secured by the server
to observe the effects of security.
TIP: Because SDBG auto-configures a `ClientCache` instance by default, you do not need to explicitly annotate your
`@SpringBootApplication` class with SDG's `@ClientCacheApplication` annotation. In fact doing so disables some of the
auto-configuration, like security, applied by SBDG OOTB. The same is true when you declare one of the
[`@PeerCacheApplication`, `@CacheServerApplication`] annotations, which changes your `@SpringBootApplication` class
completely, from a client to a server-side {geode-name} process. Therefore, be careful! See the relevant
link:../index.html#geode-clientcache-applications[chapter] in the reference documentation for more details.
[[geode-samples-boot-security-example-classes-server]]
==== BootGeodeSecurityServerApplication
.Spring Boot, {geode-name} Server Application
[source,java]
----
include::{samples-dir}/boot/security/src/main/java/example/app/security/server/BootGeodeSecurityServerApplication.java[tag=class]
----
This class is a Spring Boot, {geode-name} server application (i.e. `CacheServer`) that requires clients
(i.e. `ClientCache`) to authenticate when connecting to the server and to communicate using SSL.
Unlike the client application class above, we annotate this `@SpringBootApplication` class with `@CacheServerApplication`
to override the default `ClientCache` auto-configured by SBDG OOTB. This makes the application a {geode-name} server on
startup, capable of serving clients.
We must additionally annotate the server application class with SBDG's `@EnableSecurity` annotation to enable
{geode-name} Security on the server-side. By explicitly declaring a `PropertiesRealm` bean, we are using Apache Shiro
as the auth provider, supplying the security credentials (users, roles and permissions) via a Java Properties file:
.Apache Shiro Properties file containing the security credentials configuration
[source,java]
----
include::{samples-dir}/boot/security/src/main/resources/shiro.properties[]
----
In addition to the auth (authentication/authorization) configuration, we must additionally supply a Java Keystore file
to encrypt the connection between the client and server using SSL, as discussed above. All you need to do is create a
Java Keystore file and put it in your application classpath root. SBDG will
link:../index.html#geode-security-ssl[take care of the rest].
Of course, if you have secured your Java Keystore file with a password (as you should) then you must additionally supply
the password in `application.properties`, like so:
.Application.properties containing Auth (username/password) and SSL configuration
[source,java]
----
include::{samples-dir}/boot/security/src/main/resources/application.properties[]
----
The SSL related configuration is used by both the client and server.
[[geode-samples-boot-security-example-classes-customer]]
==== Customer
.Customer class
[source,java]
----
include::{samples-dir}/boot/security/src/main/java/example/app/security/client/model/Customer.java[tag=class]
----
This is a simple application domain class to represent a customer. The `Customer` class is annotated with SDG's `@Region`
mapping annotation to declare that the "_Customers_" `Region` will contain `Customer` objects that will be accessed
securely from the client.
[[geode-samples-boot-security-example-classes-controller]]
==== SecurityController
.SecurityController class
[source,java]
----
include::{samples-dir}/boot/security/src/main/java/example/app/security/client/controller/SecurityController.java[tag=class]
----
This class is a Spring `RestController` exposing an REST service endpoint at “_/message_” to verify the clients
use of SSL.
[[geode-samples-boot-security-example-run]]
=== Running the Example
[[geode-samples-boot-security-example-run-local]]
==== Running Locally
To run the example, first start the `BootGeodeSecurityServerApplication`
and then run `BootGeodeSecurityClientApplication`.
In the terminal you should see the following output:
.Output when running locally
[source,text]
----
Successfully put [Customer(name=William Evans)] in Region [Customers]
Attempting to read from Region [Customers]...
Read failed because "jdoe not authorized for DATA:READ:Customers:2"
----
You can also hit the endpoint at http://localhost:8080/message[localhost:8080/message] to verify the application
is using SSL.
[[geode-samples-boot-security-example-run-platform]]
==== Running on {gemfire-name} [VMs]
In order for this sample to work, your {gemfire-name} [VMs] tile must be setup to work with TLS. Instructions to enable
TLS for the {gemfire-name} [VMs] tile can be found {pcc-docs}/prepare-TLS.html[here].
Once TLS has been enabled, create your service instance with the `-c '{"tls":true}'` flag.
For example:
.Create Service Instance enabled with TLS
[source,text]
----
cf create-service p-cloudcache [plan-name] [service-instance-name] -c '{"tls":true}'
----
Replace `[plan-name]` with the plan you are selecting and `[service-instance-name]` with the desired name
of your service.
.Update `manifest.yml` with the `[service-instance-name]`
[source,text]
----
services:
- [your-service-instance-name]
----
Before deploying the application to the platform, you must update the username and password in the
`application.properties` file with the correct credentials for your service instance.
Once your service instance is created youll need to create a service-key for the service.
.Create Service Key
[source,text]
----
cf create-service-key [service-instance-name] [service-key-name]
----
Replace `[service-instance-name]` with the name of your service instance (from above). `[service-key-name]` is what you
would like to call this service key.
Once the service key is created, access the credentials in the service with the following command:
.Review Service Key Details
[source,text]
----
cf service-key [service-instance-name] [service-key-name]
----
Replace `[service-instance-name]` with the name of your service instance and `[service-key-name]` with the name of your
service key (from the previous step above).
In the output, look for the “users” section. For this example, we used the “_cluster_operator_” user credentials.
.VCAP_SERVICES credentials block
[source,text]
----
{
...
"users": [
{
"password": "xxxxxxxxxxxxxxxxxxxxxxxx",
"roles": [
"cluster_operator"
],
"username": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"password": "xxxxxxxxxxxxxxxxxxxxxx",
"roles": [
"developer"
],
"username": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"password": "xxxxxxxxxxxxxxxxxxxxx",
"roles": [
"readonly"
],
"username": "xxxxxxxxxxxxxxxx"
}
],
"wan": {}
}
----
Now build the sample with Gradle:
.Build with Gradle
[source,text]
----
$ gradlew :spring-geode-samples-boot-security:build
----
Then push the application to the platform using `cf push`.
.Push to CF
[source,text]
[subs="verbatim,attributes"]
----
$ cf push <app-name> -u none -p ~/spring-boot-data-geode/spring-geode-samples/boot/security/build/libs/spring-geode-samples-boot-security-{spring-boot-data-geode-version}.jar
...
----
Once the app is running, check the logs with `cf logs security-app --recent` and you should see output like the
following:
.Log output from the platform
[source,text]
----
Successfully put [Customer(name=William Evans)] in Region [Customers]
Attempting to read from Region [Customers]...
Read failed because "jdoe not authorized for DATA:READ:Customers:2"
----
You can also hit the endpoint at
https://security-app.apps.<cf-instance>.cf-app.com/message[https://security-app.apps.<cf-instance>.cf-app.com/message].
Replace `<cf-instance>` with the name of your CloudFoundry instance to verify that the application is using SSL.
Congratulations! You have taken your first steps towards securing an {geode-name} application with Spring Boot.

View File

@@ -0,0 +1,311 @@
[[geode-samples-caching-http-session]]
= HTTP Session State Caching with Spring
:apache-geode-version: {apache-geode-doc-version}
:apache-geode-name: Apache Geode
:apache-geode-docs: https://geode.apache.org/docs/guide/{apache-geode-version}
:apache-geode-javadoc: https://geode.apache.org/releases/latest/javadoc
:apache-geode-website: https://geode.apache.org/
:images-dir: ./images
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/html
:spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api
:spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html
:spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api
:spring-session-docs: https://docs.spring.io/spring-session/docs/current/reference/html5
:spring-session-javadoc: https://docs.spring.io/spring-session/docs/current/api
:spring-session-website: https://spring.io/projects/spring-session
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks you through building a simple Spring Boot application using {spring-session-website}[Spring Session]
backed by {apache-geode-website}[{apache-geode-name}] to manage HTTP Session state.
It is assumed that the reader is familiar with the Spring _programming model_ as well as the _Java Servlet_ API.
No prior knowledge of Spring Session or {apache-geode-name} is required to utilize HTTP Session State Caching in your
Spring Boot applications.
Let's begin.
TIP: Refer to the link:../index.html#geode-session[Spring Session] chapter in the reference documentation
for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geode-samples-caching-http-session-background]]
== Background
HTTP Session Caching is one of the most used forms of caching in enterprise applications, especially given
the proliferation of Web applications in the enterprise.
HTTP Sessions are primarily used to manage conversational state with users of your Web applications between HTTP
requests given that HTTP is a stateless protocol. This is due to the fact that HTTP connections are not persistent.
When an HTTP client makes a request, the client opens a connection to the server, sends an HTTP request, waits for
the server to process the request and respond, and then closes the connection. Each time an HTTP request is sent,
the same procedure is followed.
Of course, there are alternatives to HTTP when making remote Web Service requests. For instance, if you are using
https://en.wikipedia.org/wiki/WebSocket[WebSockets] in your applications, then you would have persistent connections
and would most likely be using either the https://stomp.github.io/[STOMP] or https://wamp-proto.org/[WAMP] protocols.
TIP: The core Spring Framework has {spring-framework-docs}/web.html#websocket[first-class support] for _WebSockets_
over the STOMP protocol.
TIP: Spring Session additionally {spring-session-docs}/#websocket[supports] Session State Management for _WebSockets_.
As mentioned above, it is useful to use the HTTP Session to manage conversational state with users of your applications
so that they can experience continuity between separate interactions (i.e. HTTP requests). In order to maintain that
continuity and provide a consistent, uninterrupted experience, the HTTP Session must be preserved in a reliable manner.
One way to do this is to employ a data management solution in your application architecture that 1) makes the HTTP
Session highly available and 2) makes the HTTP Session resilient to failures in the system architecture.
{apache-geode-name} is ideal for managing HTTP Session state given that it can distribute data/state across a scaled-out,
highly-available architecture by replicating data in a redundant and organized (partitioned) manner, thereby making
the data resilient to network and hardware failures.
This is ideal in a cloud environment given that you will most likely be running multiple instances of your application
in order to serve the demand, especially during peak loads. In these cases, you will undoubtedly face failures and each
application instance will need to be prepared to take over in a moments notice to provide the consistent, uninterrupted
experience to which we alluded to above. These applications instances will need access to the same HTTP Session state.
An application architecture with HTTP Session State Caching appears as follows:
image::{images-dir}/HTTP-Session-Caching.png[]
Essentially, anytime an HTTP Session is requested by your Spring Boot, Web Application, the Servlet Container
(e.g. Apache Tomcat) delegates to Spring Session to provide the implementation of `javax.servlet.http.HttpSession`.
After all, `javax.servlet.http.HttpServlet` is an interface that can have many implementations.
Effectively, Spring Session provides it's own implementation of the `javax.servlet.http.HttpSession` interface through
a Servlet `Filter` that gets registered by Spring Session programmatically when Spring Session is on the application
classpath.
Spring Session's implementation of the `javax.servlet.http.HttpSession` interface can backed by many different providers
that implement the Spring Session framework's `SessionRepository` interface.
Spring Session's architecture can be depicted as follows:
image::{images-dir}/Spring-Session-Framework-Architecture.png[]
Again, the `SessionRepository` interface is the central component of the framework enabling any backend data store
to be adapted and serve as a provider for managing the HTTP Sessions.
This is effectively how https://github.com/spring-projects/spring-session-data-geode[Spring Session for {apache-geode-name}]
works.
[[geode-samples-caching-http-session-example]]
== Example
For our example, we are going to keep the Web application relatively simple. Essentially, we just want to show you
how easy it is to use Spring Session in your Spring Boot, Web applications, to manage the HTTP Session state.
So, we are going to switch from Servlet Container (e.g. Apache Tomcat) to Spring Session managed HTTP Sessions with a
single-line configuration change.
First, let's introduce the Spring Web MVC `Controller` in our Spring Boot, Web application.
[[geode-samples-caching-http-session-example-controller]]
=== Controller
Our Spring Web MVC `Controller` class is implemented as follows:
.Spring Boot, Web Application Controller
[source,java]
----
include::{samples-dir}/caching/http-session/src/main/java/example/app/caching/session/http/controller/CounterController.java[tags=class]
----
The main Web Service endpoint in our Spring Boot, Web application is the `/session` endpoint, which is accessible from
http//:localhost:8080/session[].
The `/session` endpoint outputs 3 bits of information:
1. The `javax.servlet.http.HttpSession` class type.
2. Current HTTP Session count.
3. Current HTTP Request count.
The `HttpSession` class type lets us know which implementation (e.g. Servlet Container vs. Spring Session) is being used
to manage the HTTP Session state.
The HTTP Request count is simply incremented every time a client HTTP Request is made to the HTTP server (e.g. Servlet
Container) before the HTTP Session expires. If the HTTP Session expires before another client HTTP Request is made,
then the HTTP Session count is incremented and the HTTP Request count resets to 1.
[[geode-samples-caching-http-session-example-configuration]]
=== Configuration
.Spring Boot, Web Application Configuration
[source,java]
----
include::{samples-dir}/caching/http-session/src/main/resources/application.properties[]
----
The configuration is quite simple. In this case, we have set the HTTP Session `timeout`, using the
`server.servlet.session.timeout` property, to *15 seconds*. This property is used to configure the HTTP Session timeout
regardless of whether the HTTP Session is being managed by the Servlet Container or by Spring Session.
Though not strictly necessary, we configured name for the Pool of connections used by Spring Session Data Geode in case
the application is switched to a client/server topology to the "_DEFAULT_" Pool. We also explicitly named the Region
used to store HTTP Session state as "_Sessions_" (default name is "_ClusteredSpringSessions_").
TIP: In most production deployments, you will likely be using a client/server topology, where the HTTP Session is
managed by a cluster of {apache-geode-name} servers so that the HTTP Session can be shared across multiple instances
of the Spring Boot, Web application. This would be especially true in a cloud environment when utilizing a Microservices
architecture. However, for example purposes, we tried to keep the sample as simple as possible.
NOTE: The default data management policy for the client cache (a.k.a. Region) used to manage HTTP Session state is a
`PROXY`, which is the basis for the client/server topology. Therefore, the default configuration assumes you will be
using the client/server topology in most of your applications.
The main Spring Boot application class is not particularly interesting other than we have annotated the class with the
`@EnableClusterAware` annotation to seamlessly switch between a client local context and a client/server topology:
.Spring Boot, Web Application Configuration
[source,java]
----
include::{samples-dir}/caching/http-session/src/main/java/example/app/caching/session/http/BootGeodeHttpSessionCachingApplication.java[]
----
Refer to SBDG's documentation to learn about the
link:../index.html#geode-configuration-declarative-annotations-productivity-enableclusteraware[`@EnableClusterAware`]
annotation.
[[geode-samples-caching-http-session-example-classpath]]
=== Classpath
The only essential components of the application classpath is a compile-time dependency on `spring-boot-starter-web`:
.`spring-boot-starter-web` compile-time dependency declaration
[source,xml]
----
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
----
Along with a runtime dependency on `spring-boot-starter-tomcat`. You may choose to use another Servlet Container,
such as Eclipse Jetty by declaring the `spring-boot-starter-jetty` runtime dependency.
.`spring-boot-starter-tomcat` runtime dependency declaration
[source,xml]
----
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
----
Spring Boot will detect Apache Tomcat on the application classpath and bootstrap an embedded, Apache Tomcat Servlet
Container using a derived `WebApplicationContext`.
[[geode-samples-caching-http-session-example-run]]
== Run the Example
Now its time to run the example.
You can run the `BootGeodeHttpSessionCachingApplication` class in your IDE (e.g. IntelliJ IDEA). Simply create a run
profile configuration and run it. No additional JVM arguments, System Properties or program arguments are required.
Alternatively, you can run the `BootGeodeHttpSessionCachingApplication` class from the command-line with the `gradlew`
command as follows:
.Run the example with `gradlew`
[source,text]
----
$ gradlew :spring-geode-samples-caching-httpsession:bootRun
----
[[geode-samples-caching-http-session-example-tomcat]]
== Run the Example with Apache Tomcat
After the application starts, open your Web browser to http://localhost:8080[]
image::{images-dir}/HttpSessionCachingApplication.png[]
When we navigate to the `/session` Web service endpoint:
image::{images-dir}/HttpSessionCachingApplication-ServletContainerSession.png[]
We see that the Servlet Container's implementing class for the `javax.servlet.http.HttpSession` interface is
`org.apache.catalina.session.StandardSession`.
If we continue to hit refresh in the Web browser, thereby causing additional client HTTP requests to be made to
the HTTP server, then our HTTP Request count increments. If we wait for 15 seconds, then the HTTP Session will expire,
and we will see the HTTP Session count increment along with the HTTP Request count reset to 1:
image::{images-dir}/HttpSessionCachingApplication-ServletContainerSessionExpiration.png[]
Now, we can repeat this same exercise, but this time, using Spring Session.
[[geode-samples-caching-http-session-example-run-spring-session]]
=== Run the Example with Spring Session
First, we must add Spring Session to the application's classpath. We do this simply by adding the
`spring-geode-starter-session` runtime dependency to the classpath of our example application:
.`spring-geode-starter-session` runtime dependency declaration
[source,xml]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter-session</artifactId>
<scope>runtime</scope>
</dependency>
----
The `spring-geode-starter-session` dependency adds Spring Session to the application's classpath at runtime
and positions {apache-geode-name} as the provider used to manage the HTTP Session state.
With {apache-geode-name}, we gain all the benefits of using a highly concurrent, highly distributed data management solution
that provides high availability (HA) and resiliency in a cloud environment.
That's it! This is all we have to do to replace the Servlet's Container's HTTP Session management facilities with a
robust, highly available, highly resilient, clustered solution provided by Spring Session.
When we run the example again, and access the `/session` Web service endpoint, we will see:
image::{images-dir}/HttpSessionCachingApplication-SpringSession.png[]
Now we see that the implementing class for the `javax.servlet.http.HttpSession` is
`org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper`.
Easy!
Of course, the ability to scale-out and optimize the data management policies for HTTP Session management is very
provider-specific (e.g. {apache-geode-name}) and highly dependent on the use case as well a application requirements,
therefore is beyond the scope of this guide.
[[geode-samples-caching-http-session-summary]]
== Summary
Spring Session is a powerful framework for managing your HTTP Session state. Not only does it allow you to plugin
different backend data management providers (as of this writing):
* https://github.com/spring-projects/spring-session-data-geode#spring-session-for-apache-geode--pivotal-gemfire[_{apache-geode-name}_]
* {spring-session-website}[_Hazelast_]
* {spring-session-website}[_JDBC_]
* https://spring.io/projects/spring-session-data-mongodb[_MongoDB_]
* {spring-session-website}[_Redis_]
Spring Session also allows you to manage different types of Sessions depending on the context:
* {spring-session-docs}/#httpsession[_HttpSession_]
* {spring-session-docs}/#websocket[_WebSocket_]
* {spring-session-docs}#websession[_WebSession_ (Reactive)]
Therefore, it makes it a simple matter to switch providers, or adopt additional Session management capabilities as your
application requirements change and/or your use cases grow.
HTTP Session state caching is one of the most effective and common ways to utilize caching in your Spring Boot,
Web applications, and ensure the users experience is first-class.
link:../index.html#geode-samples[Back to Samples]

View File

@@ -0,0 +1,479 @@
[[geode-samples-caching-inline]]
= Asynchronous Inline Caching with Spring
:apache-geode-name: Apache Geode
:apache-geode-version: {apache-geode-doc-version}
:apache-geode-docs: https://geode.apache.org/docs/guide/{apache-geode-version}
:apache-geode-javadoc: https://geode.apache.org/releases/latest/javadoc
:apache-geode-website: https://geode.apache.org/
:images-dir: ./images
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/html
:spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api
:spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html
:spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api
:spring-data-website: https://spring.io/projects/spring-data
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks you through building a simple Spring Boot application
using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction]
backed by {apache-geode-website}[{apache-geode-name}] as the caching provider for Asynchronous Inline Caching.
It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's
_Cache Abstraction_ or {apache-geode-name} is required to utilize caching in your Spring Boot applications.
Additionally, this Sample builds on the concepts from the link:caching-inline.html[Inline Caching with Spring]
and link:caching-look-aside.html[Look-Aside Caching with Spring] guides. Therefore, it would be helpful to have read
those guides before proceeding through this guide.
Let's begin.
TIP: Refer to the link:../index.html#geode-caching-provider-inline-caching[Inline Caching] section, and specifically,
link:../index.html#geode-caching-provider-inline-caching-asynchronous[Asynchronous Inline Caching],
in the link:../index.html#geode-caching-provider[Caching with {apache-geode-name}] chapter
of the reference documentation for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geode-samples-caching-inline-asynchronous-background]]
== 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 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 reflect the changes
sometime later.
Due to the asynchronous nature of _Asynchronous Inline Caching_, it is possible for the primary data source (i.e.
_System of Record_ (SOR)) and cache to be out-of-sync. Additionally, the primary data source may contain information
that the cache does not. That is, another application may be updating the primary data source and not using the cache.
Conversely, a 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 get
out-of-sync, or become inconsistent.
For this reason, throughput and latency are the primary application concerns and motivation, 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 the _Inline Caching_ logic sits between the cache
and the primary data source:
image::{images-dir}/Inline-Caching-Layer.png[]
In _Synchronous, Read/Write-Through, Inline Caching_, the system/application architecture appears as follows:
image::{images-dir}/Synchronous-Inline-Caching.png[]
With _Asynchronous, Write-Behind, Inline Caching_, the system/application architecture would instead appear as:
image::{images-dir}/Asynchronous-Inline-Caching.png[]
IMPLEMENTATION
As readers should know or will learn, the application cache is backed by an {apache-geode-name} Region.
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 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 some time 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_", 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.
TIP: It should also be intuitive that the listener registered on the AEQ attached to the (cache) Region does not have to
process the events by writing to a backend data store. It could write to a message queue, to the file system, or do
just about anything a user desires. However, OOTB SBDB provides support to inject a Spring Data Repository into an
AEQ listener to write to any backend data store supported by the 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 hole is calculated. At the end of the round, each golfer's final
score is calculated relative to par for the golf course (72).
The Golf Tournament application is a Spring Boot application using {apache-geode-name} to cache 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 (RDBMS), asynchronously using _Asynchronous_, _Write-Behind_, _Inline Caching_. It is assumed that
there is additional validation required (e.g. signing scorecards) 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 has been established, let's review a few of the application classes.
NOTE: Each of the application domain classes are code snippets or simply a preview of the actual class, and not actual
code. See the actual Sample {github-samples-url}/caching/inline-async[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<Golfer> {
@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<Integer> 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 `GolfCourse` 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<Pairing> {
private final String name;
private GolfCourse golfCourse;
private final List<Pairing> pairings = new ArrayList<>();
private final Set<Golfer> 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<Golfer, String> { }
----
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) with JPA using 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<Golfer> getAllGolfersFromCache() {
// Use SDG GemfireTemplate to access the "Golfers" Region
}
public List<Golfer> 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 iterates through the pairings and each `Golfer` plays all 18 holes. Their scores are calculated
and recorded for each hole until the round is completed, where the players score is then calculated relative to par
for the golf course and recorded to the cache, which eventually updates 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 Golf Tournament 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), 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 service 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 configurations
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:
.AEQ batch size configuration
[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.
.AEQ batch time interval configuration
[source,java]
----
include::{samples-dir}/caching/inline-async/src/main/java/example/app/caching/inline/async/config/AsyncInlineCachingConfiguration.java[tags=queue-batch-time-interval]
----
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 updates, a much longer delay was used. Likewise, the default AEQ
_batch size_ in {apache-geode-name} 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 configuration 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 (via the 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, in the conclusion.
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 AEQs can only be created and registered on Regions 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 _auto-configuration_ providing a `ClientCache` instance
by default simply by 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 and 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 run 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 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 Spring Boot application workflows
depending on the use case and requirements.
If throughput and latency are absolutely critical to your application design in order to achieve the necessary
responsiveness and quality of experience your users' expect, 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 the _Asynchronous Inline
Caching_ pattern, 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 for operations originating from the Region to which the AEQ
is attached.
You must decide on the *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 after all, 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 must also 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. The configured Queue Dispatcher Thread count plays into this as well.
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 informative when tackling difficult problems, the kind of application
problems where the _Asynchronous Inline Caching_ pattern can be applied with immediate benefits.
link:../index.html#geode-samples[Back to Samples]

View File

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

View File

@@ -0,0 +1,557 @@
[[geode-samples-caching-lookaside]]
= Look-Aside Caching with Spring
:apache-geode-name: Apache Geode
:apache-geode-version: {apache-geode-doc-version}
:apache-geode-docs: https://geode.apache.org/docs/guide/{apache-geode-version}
:apache-geode-javadoc: https://geode.apache.org/releases/latest/javadoc
:images-dir: ./images
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/html
:spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api
:spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html
:spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks you through building a simple Spring Boot application
using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction]
backed by {apache-geode-name} as the caching provider for Look-Aside Caching.
It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's
_Cache Abstraction_ or {apache-geode-name} is required to utilize caching in your Spring Boot applications.
Let's begin.
TIP: Refer to the link:../index.html#geode-caching-provider-look-aside-caching[Look-Aside Caching] section
in the link:../index.html#geode-caching-provider[Caching with {apache-geode-name}] chapter
in the reference documentation for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geode-samples-caching-lookaside-background]]
== Background
Caching is an effective software design pattern for reducing the resource consumption used by your application
as well as improving efficiency by increasing throughput and reducing latency.
The fundamental premise of caching is, when given the same arguments, if a service call yields the same results
every time, then it is a good candidate for caching.
Indeed, if I am searching for a customer record by account number and the search always yields the same customer for a
given account number, then adding caching to the search operation will improve the overall user experience. After all,
the account number may be a form of customer identity. We can save compute resources by caching the customer's
information, which is especially useful if the customer's information is used in multiple workflows of the application
during the interactions with the customer.
While there are different patterns of caching, the _**Look-Aside Caching**_ pattern is the most frequently used.
_Look-Aside Caching_ is a pattern of caching where the input of a cacheable operation is used as the key for looking up
any cached results from a prior invocation of the operation when given the same input. In _Look-Aside Caching_, the
cache is consulted first, before the operation is invoked, and if a computation for the given input has already been
computed and cached, then the value from the cache is returned. Otherwise, if no value has been cached for the given
input, or the previous cache result expired, or was evicted, then the operation will be invoked and the result of
the operation is cached using the input as the key and the result as a value.
It should be apparent that the data structure of a cache is a key/value store, or a `Map`. Indeed it is quite common
for most cache implementations to even implement the `java.util.Map` interface. However, many cache implementations
are quite a bit more sophisticated, providing distribution (to scale-out), replication (HA) and even persistence
along with other capabilities.
For example, I may have a `CustomerService` class that looks up a `Customer` by `AccountNumber`:
.Cacheable CustomerService class
[source,java]
----
@Service
class CustomerService {
@Cacheable("CustomersByAccountNumber")
Customer findBy(AccountNumber accountNumber) {
// ...
}
}
----
If I have already looked up a `Customer` (e.g. "Jon Doe") with a given `AccountNumber` (e.g. "abc123"), then when
the `findBy(..)` method is called with the same `AccountNumber` (i.e. "abc123") again, we would expect the same result
(i.e. `Customer` "Jon Doe") to be returned.
The _Look-Aside Caching_ pattern can be depicted in the following diagram:
image::{images-dir}/Look-Aside-Caching-Pattern.png[]
In the diagram above, we see that the caching provider (e.g. {apache-geode-name}) is consulted first, #2, after the
client initiated the request, #1. If the result of the cacheable operation for the given input has already been
computed and stored in the cache (a _cache hit_), then the result is simply returned, #3, and passed back to the caller,
#6.
However, if the cacheable operation has never been invoked with the given input, or the previous computation of
the operation for the given input expired, or was evicted, then the cacheable operation is invoked (_cache miss_).
This cacheable operation may access some external data source to perform its computation, #3 (red). After the operation
completes, it returns the result, but not before the caching infrastructure stores the result along with the input
in the cache, #4 & #5. After the result is cached, the value is returned to the caller, #3 (green). Any subsequent
invocation of the cacheable operation with the same input should yield the same result as stored in the cache,
providing the cache entry (input->result) has not expired or been evicted.
Spring's {spring-framework-docs}/integration.html#cache[Cache Abstraction] is just that, a very elegant implementation
of the _Look-Aside Caching_ pattern. Details of how Spring's _Cache Abstraction_ works under-the-hood is beyond the
scope of this document. In a nutshell, it relies on Spring AOP and is not unlike Spring's Transaction Management.
Different caching providers have different capabilities. You should choose the caching provider that gives you
what you require to handle your application needs and use cases correctly.
If used appropriately, caching can greatly improve your application's end-user experience.
TIP: Instead of using {spring-framework-javadoc}/org/springframework/cache/annotation/package-summary.html[Spring's Cache Annotations],
you may instead use JSR-107, JCache API Annotations, which is {spring-framework-docs}/integration.html#cache-jsr-107[supported]
by Spring's _Caching Abstraction_.
NOTE: See Spring Boot's documentation for a complete list of
{spring-boot-docs}/boot-features-caching.html#boot-features-caching-provider[supported caching providers].
[[geode-samples-caching-lookaside-example]]
== Example (with additional background)
To make the effects of Spring's _Cache Abstraction_ using {apache-geode-name} as the cache provider apparent in your
application, we show how to enable and use caching with your application in a very small, simple example.
The example Spring Boot application implements a Counter Service, which simply maintains a collection of named counters.
The application provides a REST-ful Web interface to increment a counter, get the current cached count for a named
counter, and the ability to reset a named counter to 0.
Typically, caching is used to offset the costs associated with expensive operations, such as disk or network I/O.
Indeed, both an operation's throughput and latency is bound by an I/O operation since compute is many orders
of magnitude faster than disk, network, etc.
While developers have been quick to throw more Threads at the problem, trying to do more work in parallel, this opens
the door to a whole new set of problems (concurrency), usually at the expense of using more resources, which does not
always yield the desired results.
Opportunities for caching are often overlooked, yet is very effective at minimizing the over utilization of resources
by leveraging reuse. In an ever increasing Microservices based world, caching will become even more important as it
serves a very important role in the applications architecture, not the least of which is, resiliency.
Of course, you still must tune your cache. Most caches keep information in memory, and since memory is finite, you must
utilize strategies to manage memory effectively, such as eviction, expiration, or even Off-Heap (i.e. native memory) for
JVM-based caches. For example, evicting/expiring entries based on use (_Least Recently Used_, or LRU) is 1 of many
effective strategies.
Each caching provider's capabilities are different in this regard. The choice should not only be based on what
capabilities you need now, but capabilities (e.g. distributed compute, streaming) you may need in the future.
So, choose wisely.
[[geode-samples-caching-lookaside-example-counterservice-application]]
=== Counter Service Application
Let's have a look at the Counter Service application.
We start with a simple, Spring Boot, Servlet-based, Web application:
.SpringBootApplication
[source,java]
----
include::{samples-dir}/caching/look-aside/src/main/java/example/app/caching/lookaside/BootGeodeLookAsideCachingApplication.java[tags=class]
----
With the `org.springframework.geode:spring-geode-starter` dependency on your application classpath:
.spring-goede-starter dependency
[source,xml]
----
<dependency>
<groupId>org.springframework.geode</groupId>
<artifactId>spring-geode-starter</artifactId>
</dependency>
----
And the `BootGeodeLookAsideCachingApplication` class annotated with `@SpringBootApplication`, you have everything you
need to begin using Spring's _Cache Abstraction_ in your application with {apache-geode-name} as the caching provider.
As an application developer, all you need do is focus on where in your application caching would be most beneficial.
Let's do that.
[[geode-samples-caching-lookaside-example-counterservice-cacheableservice]]
=== Caching-enabled CounterService
Next, we define the operations our `CounterService` and add caching:
.CounterService
[source,java]
----
include::{samples-dir}/caching/look-aside/src/main/java/example/app/caching/lookaside/service/CounterService.java[tags=class]
----
The primary function of the `CounterService` is to maintain a collection of named counters, incrementing the count
each time a named counter is accessed, and returning the current (cached) count. There is an additional operation
to reset a named counter to 0.
All `CounterService` operations perform a cache function.
The `@Cacheable` `getCachedCount(:String)` method is our _**look-aside cache**_ operation. That is, the "Counters" cache
is consulted for the named counter before the method is invoked. If a count has already been established for the named
counter, then the cached count is returned and the method will not be invoked. Otherwise the `getCachedCount(:String)`
method is invoked and proceeds to call the `getCount(:String)` method.
The `@CachePut` annotated `getCount(:String)` method is always invoked, but the result is cached. If a cache entry
already exists, then it is updated (or in this case, replaced). This method always has the effect of incrementing
the named counter.
Finally, we have a `@CacheEvict` annotated `resetCache(:String)` method, which will reset the named counter to 0
and evict the cache entry for the named counter.
TIP: Each of the Spring's Cache annotations can be replaced with the corresponding JSR-107 - JCache API annotations as
{spring-framework-docs}/integration.html#cache-jsr-107[documented here], and the application will work just the same.
[[geode-samples-caching-lookaside-example-counterservice-controller]]
=== CounterController
Then, we include a Spring Web MVC Controller to access our Counter Service application from a Web browser:
.CounterController
[source,java]
----
include::{samples-dir}/caching/look-aside/src/main/java/example/app/caching/lookaside/controller/CounterController.java[tags=class]
----
Essentially, we just inject our `CounterService` application class and wrap the service operations in Web service
endpoints, accessible by URL using HTTP:
.Counter Web service endpoints
|===
| URL | Description
| `/ping` | Heartbeat request to test that our application is alive and running.
| `/counter/\{name}` | Increments the "named" counter.
| `/counter/\{name}/cached` | Returns the current, cached count for the "named" counter.
| `/counter/\{name}/reset` | Resets the count for the "named" counter.
|===
The base URL is `http://localhost:8080`.
After running the `BootGeodeLookAsideCachingApplication` class, if you open a Web browser and navigate to
`http://localhost:8080/ping`, you should see the content "**PONG**".
[[geode-samples-caching-lookaside-example-counterservice-configuration]]
=== Counter Service Configuration
While Spring Boot for {apache-geode-name}, SBDG, takes care of enabling Spring's caching infrastructure for you,
configuring {apache-geode-name} as a caching provider, you still must define and declare your individual caches.
No Spring caching provider is fully configured by Spring or Spring Boot for that matter. Part of the reason for this
is that there are many different ways to configure the caches.
Remember earlier we mentioned tuning a cache with eviction or expiration policies, perhaps using Off-Heap memory,
overflowing entries to disk, making caches persistent, are few of the ways to tune or configure a cache. You might be
using a client/server or even a WAN topology and you might need to configure things like conflation, filters,
compression, security (e.g. SSL), and so on.
However, this is a lot to think about and you may just simply want to get up and running as quickly as possible. While
SBDG is not opinionated about this out-of-the-box, we do provide assistance to make this task easy:
.GeodeConfiguration
[source,java]
----
include::{samples-dir}/caching/look-aside/src/main/java/example/app/caching/lookaside/config/GeodeConfiguration.java[tags=class]
----
The only thing of real significance here is the `@EnableCachingDefinedRegions` annotation. This Spring Data
for {apache-geode-name} (SDG) annotation is responsible for introspecting our Spring Boot application on Spring
container startup, identifying all the caching annotations (both Spring Cache annotations as wells JSR-107, JCache
annotations) used in our application components, and creating the appropriate caches.
If you were not using SDG's `@EnablingCachingDefinedRegions` annotation, then you would need to define the Region using
the equivalent _JavaConfig_:
."Counters" Region definition using JavaConfig
[source,java]
----
@Configuration
class GeodeConfiguration {
@Bean("Counters")
public ClientRegionFactoryBean<Object, Object> countersRegion(GemFireCache gemfireCache) {
ClientRegionFactoryBean<Object, Object> countersRegion = new ClientRegionFactoryBean<>();
countersRegion.setCache(gemfireCache);
countersRegion.setShortcut(ClientRegionShortcut.LOCAL);
return countersRegion;
}
}
----
Or using XML:
."Counters" Region definiton using XML
[source,xml]
----
<gfe:client-region id="Counters" shortcut="LOCAL"/>
----
In {apache-geode-name} terminology, each cache identified in 1 of the caching annotations by name, will have an {apache-geode-name}
Region created for it.
In our case, SBDG provides us a `ClientCache` instance by default, so we will be creating client `LOCAL` Regions.
The client "Counters" Region is `LOCAL` since we do not (yet) have a cluster of servers running.
However, it would be very simple to convert this application into using a client/server topology by simply starting a
cluster of servers.
[[geode-samples-caching-lookaside-example-counterservice-configuration-clientserver]]
==== Client/Server Configuration
To use the client/server topology, you need to start a cluster with 1 or more servers using the default configuration.
You can start the cluster using the {apache-geode-name} Shell tool (_Gfsh_) and create the "Counters" Region
on the servers.
Of course, you technically do not even need to create the "Counters" Region on the server. The `@EnableClusterAware`
annotation is meta-annotated with SDG's `@EnableClusterConfiguration(..)` annotation, which will create the necessary
server-side, "Counters" Region for you.
After starting a cluster with a Locator & Server using _Gfsh_:
[source,txt]
----
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.2.1
Monitor and Manage Apache Geode
gfsh>start locator --name=LocatorOne --log-level=config
Starting a Geode Locator in /Users/jblum/pivdev/lab/LocatorOne...
....
gfsh>start server --name=ServerOne --log-level=config
Starting a Geode Server in /Users/jblum/pivdev/lab/ServerOne...
.....
gfsh>list members
Name | Id
---------- | ---------------------------------------------------
LocatorOne | 10.99.199.24(LocatorOne:40824:locator)<ec><v0>:1024
ServerOne | 10.99.199.24(ServerOne:40855)<v1>:1025
gfsh>list regions
No Regions Found
----
The application configuration (i.e. `GeodeConfiguration`) is already set to go:
.Using client/server
[source,java]
----
@Configuration
@EnableClusterAware
@EnableCachingDefinedRegions
public class GeodeConfiguration { }
----
After (re-)starting the application, we will see that the "Counters" Region has been created in the cluster,
and specifically on "_ServerOne_":
."Counters" Region
[source,txt]
----
gfsh>list regions
List of regions
---------------
Counters
gfsh>describe region --name=/Counters
..........................................................
Name : Counters
Data Policy : partition
Hosting Members : ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 0
| data-policy | PARTITION
----
We will refer to the client/server approach further below, when running the example.
Refer to {apache-geode-name}'s documentation to learn more about the
{apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[client/server topology].
Refer to SDG's documentation to learn more about
{spring-data-geode-docs}/#bootstrap-annotation-config-cluster[Cluster Configuration].
Refer to SBDG's documentation to learn about the
link:../index.html#geode-configuration-declarative-annotations-productivity-enableclusteraware[`@EnableClusterAware`]
annotation.
[[geode-samples-caching-lookaside-example-run]]
== Run the Example
Now it is time to run the example.
You can run the `BootGeodeLookAsideCachingApplication` class from your IDE (e.g. IntelliJ IDEA) by creating a simple
run profile configuration. No additional JVM arguments, System Properties or program argument are required to run
the example.
Alternatively, you can run the example using the `gradlew` command from the command-line as follows:
.Run the example with `gradlew`
[source,text]
----
$ gradlew :spring-geode-samples-caching-lookaside:bootRun
----
The program output will appear as follows:
.Run the `BootGeodeLookAsideCachingApplication` class
[source,txt]
----
/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/bin/java -server -ea ...
example.app.caching.lookaside.BootGeodeLookAsideCachingApplication
[info 2019/05/06 12:09:57.356 PDT <background-preinit> tid=0xd] HV000001: Hibernate Validator 6.0.16.Final
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.9.RELEASE)
[info 2019/05/06 12:09:57.531 PDT <main> tid=0x1] Starting BootGeodeLookAsideCachingApplication on jblum-mbpro-2.local with PID 40871...
[info 2019/05/06 12:09:57.532 PDT <main> tid=0x1] No active profile set, falling back to default profiles: default
[info 2019/05/06 12:09:57.582 PDT <main> tid=0x1] Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2eea88a1: startup date [Mon May 06 12:09:57 PDT 2019]; root of context hierarchy
...
[info 2019/05/06 12:09:59.234 PDT <main> tid=0x1] Tomcat initialized with port(s): 8080 (http)
2019-05-06 12:09:59.267 INFO 40871 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-05-06 12:09:59.269 INFO 40871 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.39
2019-05-06 12:09:59.280 INFO 40871 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/jblum/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2019-05-06 12:09:59.381 INFO 40871 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
[info 2019/05/06 12:09:59.381 PDT <localhost-startStop-1> tid=0x10] Root WebApplicationContext: initialization completed in 1800 ms
[info 2019/05/06 12:09:59.440 PDT <localhost-startStop-1> tid=0x10] Servlet dispatcherServlet mapped to [/]
...
2019-05-06 12:10:26.116 INFO 40871 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
----
Then open your Web browser and navigate to http://locahost:8080[] or `ping` Web service endpoint at
http://localhost:8080/ping[]:
image::{images-dir}/LookAsideCachingApplication-Ping.png[]
After that, we can create and increment counters, for example:
`http://localhost:8080/counter/A`
**1**
If you constantly hit the refresh button, you will see 2, 3, 4, 5, ... and so on. While the named counter's (i.e. "A")
new count is being cached, we are not returning the cached value.
If you navigate to:
`http://localhost:8080/counter/A/cached`
The count for the named counter (e.g. "A") will remain fixed on whatever the last count was (e.g. "5").
You can begin a new named counter (e.g. "B") without affecting the exiting named counter (i.e. "A"), by navigating to:
`http://localhost:8080/counter/B`
**1**
And again, after refreshing the page multiple times:
**3**
If you navigate to:
`http://localhost:8080/counter/B/reset`
**0**
This resets the count of counter "B" to 0. However, this does not affect the count of counter "A", which we can
reevaluate by navigating to:
`http://localhost:8080/counter/A/cached`
**5**
This is an extremely simple application, but shows the effects of caching.
[[geode-samples-caching-lookaside-example-run-clientserver]]
=== Running the Example using Client/Server
If you are using the client/server topology, the effects of caching are no different. However, after running the example
application you can evaluate the state of the "Counters" Region using _Gfsh_, like so:
.Describing and Querying the "Counters" Region on the Server
[source,txt]
----
gfsh>describe region --name=/Counters
..........................................................
Name : Counters
Data Policy : partition
Hosting Members : ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 2
| data-policy | PARTITION
gfsh>query --query="SELECT entries.key, entries.value FROM /Counters.entrySet entries"
Result : true
Limit : 100
Rows : 2
key | value
--- | -----
A | 5
B | 2
----
[[geode-samples-caching-lookaside-conclusion]]
== Conclusion
As you have learned, Spring makes enabling and using caching in your application really easy.
With SBDG, using {apache-geode-name} as your caching provider in Spring's _Cache Abstraction_ is as easy as making sure
`org.springframework.geode:spring-geode-starter` is on your application's classpath. You just need to focus on areas of
your application that would benefit from caching.
You have now successfully used the _**Look-Aside Caching**_ pattern in your Spring Boot application.
Later we will cover more advanced forms of the _Look-Aside Caching_ pattern (e.g. using Eviction/Expiration policies)
as well as take a look at other caching patterns, like _Inline Caching_, _Multi-Site Caching_ and _Near Caching_.
link:../index.html#geode-samples[Back to Samples]

View File

@@ -0,0 +1,594 @@
[[geode-samples-caching-near]]
= Near Caching with Spring
:apache-geode-version: {apache-geode-doc-version}
:apache-geode-name: Apache Geode
:apache-geode-docs: https://geode.apache.org/docs/guide/{apache-geode-version}
:apache-geode-javadoc: https://geode.apache.org/releases/latest/javadoc
:images-dir: ./images
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/current/reference/html
:spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api
:spring-data-geode-docs: https://docs.spring.io/spring-data/geode/docs/current/reference/html
:spring-data-geode-javadoc: https://docs.spring.io/spring-data/geode/docs/current/api
:spring-framework-docs: https://docs.spring.io/spring/docs/current/spring-framework-reference
:spring-framework-javadoc: https://docs.spring.io/spring/docs/current/javadoc-api
:toc: left
:toclevels: 2
:stylesdir: ../
:highlightjsdir: ../js/highlight
:docinfodir: guides
This guide walks you through building a simple Spring Boot application
using {spring-framework-docs}/integration.html#cache[Spring's Cache Abstraction]
backed by {apache-geode-name} as the caching provider for Near Caching.
It is assumed that the reader is familiar with the Spring _programming model_. No prior knowledge of Spring's
_Cache Abstraction_ nor {apache-geode-name} is required to utilize caching in your Spring Boot applications.
Additionally, this Sample builds on the concepts introduced in both link:caching-look-aside.html[Look-Aside Caching]
as well as link:caching-inline.html[Inline Caching] with Spring. It would be helpful to start by reading the guide on
_Look-Aside Caching_ followed by the guide on _Inline Caching_, first, before continuing with this guide.
Let's begin.
TIP: Refer to the link:../index.html#geode-caching-provider-near-caching[Near Caching] section
in the link:../index.html#geode-caching-provider[Caching with {apache-geode-name}] chapter
in the reference documentation for more information.
[#index-link]
link:../index.html[Index]
link:../index.html#geode-samples[Back to Samples]
[[geodes-samples-caching-near-background]]
== Background
In the first sample on link:caching-look-aside.html[_Look-Aside Caching_], we paved the foundation for using caching in
your Spring Boot applications. _Look-Aside Caching_ makes efficient use of resources (e.g. by reducing contention on
the database, or by reducing the number of network calls between Microservices in a distributed system) simply by
keeping frequently accessed data in-memory for quick retrieval (reads), which can improve throughput and reduce latency.
In the second sample, we expanded on _Look-Aside Caching_ with link:caching-inline.html[_Inline Caching_] and extended
the _Look-Aside Caching_ pattern to "_read/write-through_" to a backend data source (e.g. database). The backend data
source is likely the application's _System or Record_ (SOR), or "_source of truth_". The _write-through_ operation
to the backend data source is synchronous. If the write fails, the cache will not be modified. This ensures a
consistent view between the cache and the backend data source, which is an important characteristic of _Inline Caching_.
It is likely that you will be using a client/server topology when applying either the _Look-Aside_
or the _Inline Caching_ pattern to your Spring Boot application(s). This is especially true when scaling up
multiple instances of the same application in a Microservices architecture.
Multiple, Microservice, application instances will need a consistent view of the data, especially in a load-balanced,
cloud-native environment where separate client requests, that are part of the same session, could be routed to different
application instances. Therefore, application state needs to be maintained independent of the application instances.
NOTE: _Sticky Sessions_ can be used to keep conversational state associated with a user's Session tied to a single
application instance. However, use of _Sticky Sessions_ is not resilient to failures, and as such, essentially become
an anti-pattern in a cloud context. You should avoid using _Stick Sessions_ in a cloud environment whenever possible.
To keep up with demand and not overload backend systems, like a database, you would have to scale-up with more Memory,
more CPU, more Disk, more Network bandwidth, basically, more of everything, which can be a very costly endeavor as you
try to keep up with the every growing demand (which is a good problem to have, but...):
image::{images-dir}/Small-Database-To-Big-Database.png[]
Rather than scale-up, you could scale-out by using a sophisticated caching technology that uniformly partitions data
across a cluster of data nodes thereby enabling data access operations to be intelligently routed and evenly distributed
across the cluster. In addition, data can be replicated for redundancy and high-availability (HA) purposes making the
cluster more resilient to failure. Such a data management technology is ideal in a cloud environment. The cluster acts
as a single, logical unit of pooled resources (Memory, CPU, Disk, and Network) but uses a shared-nothing architecture.
That is, no node in the cluster can be a single point of failure.
image::{images-dir}/Cluster.png[]
From a Spring Boot application's point-of-view, it is the client in this application architecture, and multiple
application instances can access and share the same data. Indeed, in a Microservices architecture, another application
instance must be prepared to take over in a moments notice if any application instance goes down in order to avoid any
perceived disruption in the users' service.
However, even in a sophisticated, scale-out, client/server architecture such as the one we described above, it still
involves network access, even if only a "_single-hop_".
So, how might we use caching to further reduce resource consumption (e.g. Network) in our application architecture?
The key lies in keeping data closer to the point of access, i.e. on the client, in our Spring Boot application.
In essence, we put more responsibility on our Spring Boot application by increasing the participation of our application
in this slightly modified architecture, in a "_pro-active_" way.
Enter *_Near Caching_*.
Basically, in addition to our server-side, peer node, cache topology, the client additionally caches data, but only
the data it is "_interested_" in.
Additionally, rather than the client having to pull for data changes, the data can be pushed to the client when the data
changes, based on its "_registered interests_". Therefore, the client only receives the data it subscribed to,
in the first place.
Furthermore, the data change events can be conflated so our client application only sees the latest updates, not every
single change that may have occurred due to other application instances modifying the same data, possibly concurrently.
These 3 things in conjunction with each other should have a net effect of reducing noise and network saturation.
Effectively, an applied "_Near Caching_" software design pattern looks like the following in our application/system
architecture:
image::{images-dir}/Near-Caching-Pattern.png[]
It is now time to see the _Near Caching_ pattern in action.
[[geode-samples-caching-near-example]]
== Example
For our example, we develop a _Yellow Pages_ application with the ability to lookup a person by name and retrieve
the person's contact information, such as an email address and phone number.
[[geode-samples-caching-near-example-server-side]]
=== Server-side Configuration
First, we will configure and bootstrap an {apache-geode-name}, peer `CacheServer` node using Spring Boot:
.SpringBootApplication for an {apache-geode-name} `CacheServer`
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/server/BootGeodeNearCachingCacheServerApplication.java[tags=class]
----
This class consists of a Spring `@Configuration` class to configure the necessary server-side Region
(i.e "_YellowPages_") used to store a person's contact information:
.Server-side Configuration
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/server/BootGeodeNearCachingCacheServerApplication.java[tags=geode-configuration]
----
The pertinent bit of this configuration is the subscription conflation on the "_YellowPages_" Region. This setting
ensures that only the latest information is sent to the clients. It is possible that multiple clients maybe accessing
and updating a person's contact information. Therefore, we want to make sure all the clients have, and are only sent,
the latest information, which therefore reduces traffic across our network.
We include a Spring Boot `ApplicationRunner` with a few assertions to make sure the server is configured properly
on startup:
.Asserting Server-side Configuration
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/server/BootGeodeNearCachingCacheServerApplication.java[tags=application-runner]
----
And finally, we include a Spring `@Profile` to enable an embedded Locator and Manager, allowing us to connect to our
Spring Boot, {apache-geode-name} `CacheServer` application using _Gfsh_ (Geode Shell). Enabling the embedded Locator
and Manager are not necessary when starting the server or to run our application, but can be useful when debugging.
.Embedded Locator & Manager Configuration
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/server/BootGeodeNearCachingCacheServerApplication.java[tags=locator-manager]
----
TIP: For more information on configurating and bootstrapping a small cluster of {apache-geode-name} servers using Spring Boot,
see link:../index.html#geode-cluster-configuration-bootstrapping[Running an {apache-geode-name} cluster using Spring Boot].
[[geode-samples-caching-near-example-client-side]]
=== Client-side Configuration
Next, we will create and start 2 instances of our Spring Boot, {apache-geode-name} `ClientCache` application, which will use
the _Look-Aside Caching_ pattern enhanced with_Near Caching_.
We start with the `@SpringBootApplication` main class:
.SpringBootApplication for Geode `ClientCache`
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/BootGeodeNearCachingClientCacheApplication.java[tags=class]
----
Essentially, the main class just serves to bootstrap our application configuration and components. Additionally, we
include some assertions in a Spring Boot `ApplicationRunner` bean to ensure our client configuration is correct.
Our configuration appears as follows:
.Application Geode Configuration
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/config/GeodeConfiguration.java[tags=class]
----
First, we note the "_YellowPages_" client Region, which must match the server-side Region by name:
.The "YellowPages" client `CACHING_PROXY` Region
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/config/GeodeConfiguration.java[tags=region]
----
Most importantly, the client Region's data policy is set to `ClientRegionShortcut.CACHING_PROXY`:
.Enabling Near Caching
[source,java]
----
clientRegion.setShortcut(ClientRegionShortcut.CACHING_PROXY);
----
This enables a _local cache_ (a.k.a. "*_Near Cache_*") on the client in our Spring Boot application.
NOTE: The default `ClientRegionShortcut` is `PROXY`, which means there is no local cache. With a client `PROXY` Region,
all cache operations are forwarded to the server.
Equally important is the "_interest registration_" for all KEYS:
.Register Interest
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/config/GeodeConfiguration.java[tags=interest-registration]
----
The first parameter is a _Regular Expression_ (i.e. `.*`) matching the KEYS this client is interested in
receiving updates for, which in this case, is all KEYS.
The other parameters to the `RegexInterest` constructor includes the `InterestResultPolicy`, which determines whether
the client should get an initial push of the data (KEYS/VALUES) matching the regex when the client registers interest.
The `durable` boolean parameter sets whether the client subscription queue on the server should be "durable",
i.e. maintained when the client is not present. If the client goes down, for whatever reason, the server will continue
to maintain the client's subscription queue with events matching the regex up to a specified timeout (configurable on
the server). If the client comes back online before the configured timeout, the events in the queue will be replayed
back to the client. If the client does not reconnect before the configured timeout, the queue is discarded.
Durability can be useful for clients that need to receive events for data it missed while the client was offline in the
order the events occurred. Of course, keep in mind that durable clients use up system resources on the server
(e.g. memory).
TIP: To learn more about durable subscriptions, see the {apache-geode-name}
{apache-geode-docs}/developing/events/implementing_durable_client_server_messaging.html[documentation]
The `receiveValues` boolean parameter determines whether the client will receive both KEYS and VALUES when an event
matching the regex occurs, or whether the client will only receive the KEYS for the VALUES that changed.
Configuring the client to only receive KEYS minimizes the amount of data sent over the network when the client only
wants to (perhaps) "invalidate" the keyed entriess, e.g. by using
{apache-geode-javadoc}/org/apache/geode/cache/Region.html#localInvalidate-java.lang.Object-[`Region.localInvalidate(key:Object)`].
In that way, the memory footprint of the clients can also be maintained and the client will only lazily fetch the value
when needed again.
TIP: The `RegexInterest` constructor corresponds to
{apache-geode-javadoc}/org/apache/geode/cache/Region.html#registerInterestRegex-java.lang.String-org.apache.geode.cache.InterestResultPolicy-boolean-boolean-[Region.registerInterestRegex(:String, :InterestResultPolicy, :boolean, :boolean)]
There is one final bit of configuration on the client-side that we need, and that is to enable subscriptions. We do so
by setting the appropriate Spring Data for {apache-geode-name} (SDG) property
(e.g. `spring.data.gemfire.pool.subscriptions-enabled`) in `application.properties`, like so:
.Common Client `application.properties`
[source,java]
----
include::{samples-dir}/caching/near/src/main/resources/application-client.properties[]
----
Additionally, each client (i.e. "one", "two" and so on, for however many clients we want to start) each have their own
client specific `application.properties`, for example:
.Common Client `application.properties`
[source,java]
----
include::{samples-dir}/caching/near/src/main/resources/application-client-one.properties[]
----
We set the `spring.application.name` property to help identify the client and additionally set the `server.port`
property to a unique value since our Spring Boot application is a Web application.
Now we can discuss the components of the application.
[[geode-samples-caching-near-example-app-model]]
=== Application Model
We start by modeling our `Person` and a person's contact information, an email address and phone number:
.`Person` class
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/model/Person.java[tags=class]
----
The class uses https://projectlombok.org/[Project Lombok] to simplify the implementation. Otherwise, the `Person` class
is pretty self-explanatory and there is nothing else special about the class.
[[geode-samples-caching-near-example-app-service]]
=== Application Service
Next, we have our `YellowPagesService` class that implements our _Yellow Pages_ application logic. This class also uses
Spring's Caching annotations to demarcate service methods that will apply "_Look-Aside_" with "_Near Caching_"
semantics:
.`YellowPagesService` class
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/service/YellowPagesService.java[tags=class]
----
Essentially, we have a `@Cacheable`, `find(:String)` service method that tries to lookup a `Person` by name
from the cache. If a `Person` by name is found, then the `Person` is simply returned, otherwise, the `find(:String)`
service method is invoked and a `Person` with the given name and generated contact information is created and cached.
Technically, our `find(:String)` service method should "_idempotent_", but for example purposes,
we combine READ with CREATE.
Our service class additionally contains operations to update (i.e. `@CachePut`) a `Person's` contact information
as well as evict (i.e. `@CacheEvict`) the `Person's` contact information from the cache.
[[geode-samples-caching-near-example-app-controller]]
=== Application Controller
To make the operations of our _Yellow Pages_ application accessible, we expose REST-ful Web service endpoints using a
Spring Web MVC `@RestController` class:
.`YellowPagesController` class
[source,java]
----
include::{samples-dir}/caching/near/src/main/java/example/app/caching/near/client/controller/YellowPagesController.java[tags=class]
----
Basically, we have REST-based Web service endpoints matching our `YellowPagesService` class service methods.
NOTE: Unless you install a Web browser plugin, a Web browser will only allow HTTP GET requests. Therefore, for
convenience purposes only, our application provides REST API endpoints (e.g. `/yellow-pages/JonDoe/update?email=jondoe@home.org`)
that allows the user to modify the data. No properly constructed REST-ful application should do this.
Now we are ready to run our example application and observe the effects of *_Near Caching_*.
[[geode-samples-caching-near-example-run]]
== Run the Example
[[geode-samples-caching-near-example-run-server]]
=== Run the Server
First, we must start our Spring Boot application that configures and bootstraps the {apache-geode-name} `CacheServer`.
TIP: If you want to connect to the server with _Gfsh_, you must have a distribution of {apache-geode-name} installed on your
system and you must enable the "_locator-manager_" profile. The "_locator-manager_" profile can be enabled using the
`-Dspring.profiles.active=server,locator-manager` Java System property. Additionally, the `server` profile has been
enabled as well.
When running the `BootGeodeNearCachingCacheServerApplication` class, you should see output similar to the following:
.Server output
[source,txt]
----
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.7.BUILD-SNAPSHOT)
[info 2019/08/12 13:02:17.328 PDT <main> tid=0x1] Starting BootGeodeNearCachingCacheServerApplication on jblum-mbpro-2.local with PID 13725...
[info 2019/08/12 13:02:17.329 PDT <main> tid=0x1] The following profiles are active: locator-manager,server
...
// Then you should see a bunch of {apache-geode-name} log output, ending with something like...
...
[info 2019/08/12 13:02:20.975 PDT <main> tid=0x1] CacheServer Configuration: port=40404 max-connections=800 max-threads=0 notify-by-subscription=true socket-buffer-size=32768 maximum-time-between-pings=60000 maximum-message-count=230000 message-time-to-live=180 eviction-policy=none capacity=1 overflow directory=. groups=[] loadProbe=ConnectionCountProbe loadPollInterval=5000 tcpNoDelay=true
[info 2019/08/12 13:02:20.996 PDT <main> tid=0x1] Started BootGeodeNearCachingCacheServerApplication in 4.216 seconds (JVM running for 5.49)
----
NOTE: The Spring Boot Gradle plugin has been configured to run the `BootGeodeNearCachingClientCacheApplication` class,
not the server.
Now that the server is running, if you installed {apache-geode-name} on your system and set the `$PATH` to include `$GEODE/bin`,
then you can run _Gfsh_ and connect to the server:
.Connect to the Server with Gfsh
[source,txt]
----
$ echo $GEODE
/Users/jblum/pivdev/apache-geode-1.6.0
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.6.0
Monitor and Manage Apache Geode
gfsh>connect
Connecting to Locator at [host=localhost, port=10334] ..
Connecting to Manager at [host=10.99.199.24, port=1099] ..
Successfully connected to: [host=10.99.199.24, port=1099]
gfsh>list members
Name | Id
----------------- | ----------------------------------------------------------------
YellowPagesServer | 10.99.199.24(YellowPagesServer:13725)<ec><v0>:1024 [Coordinator]
gfsh>describe member --name=YellowPagesServer
Name : YellowPagesServer
Id : 10.99.199.24(YellowPagesServer:13725)<ec><v0>:1024
Host : 10.99.199.24
Regions : YellowPages
PID : 13725
Groups :
Used Heap : 75M
Max Heap : 3641M
Working Dir : /Users/jblum/pivdev/spring-boot-data-geode
Log file : /Users/jblum/pivdev/spring-boot-data-geode
Locators : localhost[10334]
Cache Server Information
Server Bind :
Server Port : 40404
Running : true
Client Connections : 0
gfsh>list regions
List of regions
---------------
YellowPages
gfsh>describe region --name=/YellowPages
..........................................................
Name : YellowPages
Data Policy : replicate
Hosting Members : YellowPagesServer
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ------------------------------ | ---------
Region | data-policy | REPLICATE
| enable-subscription-conflation | true
| size | 0
gfsh>
----
[[geode-samples-caching-near-example-run-client-app]]
=== Run the Client Application
Now it is time to start 2 instances of the Spring Boot, {apache-geode-name} `ClientCache` application hosting
our _Yellow Pages_ service.
NOTE: Make sure to enable the `client` generic profile in addition to 1 of the client-specific profiles,
e.g. "_client-one_", like so: `-Dspring.profiles.active=client,client-one`. To run a second application instance,
change the profile from `client-one` to `client-two`.
TIP: Alternatively, rather than using client instance specific `application.properties`, you could set the
`spring.application.name` and `server.port` properties using JVM System properties on the command-line, or in your IDE
run profile as so: `-Dspring.application.name=ClientApplicationTwo -Dserver.port=8282`. Furthermore, you could set the
`server.port` property to the ephemeral port and let the system determine an available port for the embedded Web Server
(i.e. Jetty). You must make note of the port number when the application starts up so that you can access the Webapp
from your Web browser. Look for a line containing: `[info 2019/08/12 13:14:19.755 PDT <main> tid=0x1] Tomcat initialized with port(s): 8181 (http)`.
Once both application instances are running, you can access the Webapp from your Web browser at the following URL:
`http::/localhost:8181/`.
image::{images-dir}/Near-Caching-Example-Webapp.png[]
TIP: To switch between the 2 client app instances, it is useful to have 2 Web browser tabs or windows open
accessing each Web Server port (e.g. `8181` and `8282`).
Next, let's create some data using client app instance one.
image::{images-dir}/Near-Caching-Example-Webapp-Create-JonDoe.png[]
This operation takes a bit of (simulated) time (`2167 milliseconds (ms)`, or `~2 seconds (s)`) since "_Jon Doe_"
did not previously exist in the cache, which can be noted by the `cacheMiss` value of *true*. "_JonDoe's_"
email address and phone number were randomly generated.
If you hit the refresh button in your Web browser, the latency significantly drops (~`0-1|2 ms`) because the value
is being pulled from the "_local_" cache (i.e. "_Near Cache_) on the client.
Now, in our second client app instance, if we access the same person, "_JonDoe_", then we see the following:
image::{images-dir}/Near-Caching-Example-Webapp-Read-JonDoe.png[]
Notice that `cacheMiss` is *false* and the `latency` is only `1 ms`. That is because the 2nd client app instance
was already pushed the data from the server based on the client's interest registration. This is also apparent
in the log output for the client application instances:
.Client Application Instance 2 Log Output On Create
[source,txt]
----
[CREATE] EntryEvent for [JonDoe] with value [Person(name=JonDoe, email=jondoe@microsoft.com, phoneNumber=319-468-4802)]
----
To see the effects of updating a cache entry from a client app instance, let's update "_JonDoe_" from the 2nd client app
instance by changing his email address and phone number:
image::{images-dir}/Near-Caching-Example-Webapp-Update-JonDoe.png[]
Before we refresh the Web browser tab or window pointing to our 1st client app instance, if you look at the log output
for the 1st client app instance, you will see:
.Client Application Instance 1 Log Output After Update
[source,txt]
----
[UPDATE] EntryEvent for [JonDoe] with value [Person(name=JonDoe, email=jondoe@google.com, phoneNumber=206-555-1234)]
----
Then, switch back to the 1st client app instance Web browser tab or window and hit the refresh button, or navigate
to the URL, `http://localhost:8181/yellow-pages/JonDoe`, and you should see the updated contact information:
image::{images-dir}/Near-Caching-Example-Webapp-Reload-JonDoe.png[]
You can repeat this exercise as often as you like.
Now, if you describe the "_YellowPages_" Region in _Gfsh_, you will see that there are cache entries:
.Gfsh `describe region`
[source,txt]
----
gfsh>describe region --name=/YellowPages
..........................................................
Name : YellowPages
Data Policy : replicate
Hosting Members : YellowPagesServer
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ------------------------------ | ---------
Region | data-policy | REPLICATE
| enable-subscription-conflation | true
| size | 3
----
You can even query the data using OQL:
.OQL Query to query the YellowPages
[source,txt]
----
gfsh>query --query="SELECT person.name, person.email, person.phoneNumber FROM /YellowPages person"
Result : true
Limit : 100
Rows : 3
name | email | phoneNumber
------- | ------------------ | ------------
JaneDoe | janedoe@pivotal.io | 608-826-7621
JonDoe | jondoe@google.com | 206-555-1234
PieDoe | piedoe@comcast.net | 406-413-6170
----
Presto! You have now just created a Spring Boot application using the _Look-Aside Caching_ pattern enhanced with
_Near Caching_.
[[geode-samples-caching-near-summary]]
== Summary
In this guide, we learned how to create a Spring Boot application using Spring's Cache Abstraction backed by
{apache-geode-name} using the _Look-Aside Caching_ pattern in our application service methods. We further enhanced
the caching ability of our application with *_Near Caching_*.
With *_Near Caching_*, we have the added ability to further improve on the throughput and latency of our application
as well as make even more efficient use of system resources. _Near Caching_ gives us:
* A local, client-side cache for quick lookup, transforming our client into an efficient, light-weight data container
for data that is relevant to the client, thereby reducing the load on our servers.
* By _enabling subscriptions_ and _registering interests_, we can have data intelligently pushed to us (rather than
simply pulled when needed) based on the data the client is specifically interested in, or "subscribed" to.
* Then, we saw that we can conflate subscription events on the server-side so clients only receive the latest updates
to the data that will be sent to the client based on the registered interests.
* With 1 more step, it is simple to make the subscription queues maintained on the servers for each client "durable"
so if the client is offline, it does not miss any events, if necessary.
* Furthermore, the client subscriptions queues on the servers can be made both redundant and persistent for
high-availability (HA) and resiliency purposes.
There is a much more that can be achieved with a _Near Cache_, so we leave it as an exercise for the reader
to explore and experiment more. Hopefully this has peaked your curiosity and shown you a few of the benefits of
applying the _Near Caching_ pattern to your Spring Boot applications.
link:../index.html#geode-samples[Back to Samples]

View File

@@ -0,0 +1,2 @@
<script type="text/javascript" src="../js/tocbot/tocbot.min.js"></script>
<script type="text/javascript" src="../js/toc.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Some files were not shown because too many files have changed in this diff Show More