One of the first tasks when using GemFire and Spring is to configure the data grid using dependency injection via the Spring container. While this is possible out of the box, the configuration tends to be verbose and only address basic cases. To address this problem, the Spring GemFire project provides several classes that enable the configuration of distributed caches or regions to support a variety of scenarios with minimal effort.
To simplify configuration, SGF provides a dedicated namespace for most of its components. However, one can opt to configure the objects directly through the usual <object> definition. For more information about XML Schema-based configuration in Spring.NET, see this appendix in the Spring.NET Framework reference documentation.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.net" xmlns:gfe="http://www.springframework.net/gemfire"> <gfe:cache ...> </objects>
Spring GemFire namespace prefix. Any name can do but through out
the reference documentation, the | |
The namespace URI. | |
Declaration example for the GemFire namespace. Notice the prefix usage. |
For the remainder of this doc, to improve readability, the XML
examples will simply refer to the <gfe>
namespace
without the namespace declaration, where possible. To enable Spring.NET to
recognize this namespace you need to register it in the applications
App.config section.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Data.GemFire.Config.GemfireNamespaceParser, Spring.Data.GemFire" /> </parsers> </spring> </configuration>
In order to use the GemFire Fabric, one needs to either create a new Cache or connect to an existing one. As of the current version of GemFire (6.0.x) there can be only one opened cache per application. In most cases the cache is created once and then all other consumers connect to it.
In its simplest form, a cache can be defined in one line:
<gfe:cache />
The declaration above declares an
object(CacheFactoryObject
) for the GemFire Cache, named
gemfire-cache
. All the other SGF components use this
naming convention if no name is specified, allowing for very concise
configurations. The definition above will try to connect to an existing
cache and, in case one does not exist, create it. Since no additional
properties were specified the created cache uses the default cache
configuration.Especially in environments with opened caches, this basic
configuration can go a long way.
For scenarios where the cache needs to be configured, the user can pass in a reference the GemFire configuration file:
<gfe:cache id="cache-with-xml" cache-xml-location="cache-config.xml"/>
In this example, if the cache needs to be created, it will use the
file named cache.xml
located in the root of the runtime
directory. Only if the cache is created will the configuration file be
used.
In addition to referencing an external configuration file one can specify GemFire settings directly through NameValueCollection properties. This can be quite handy when just a few settings need to be changed.
<gfe:cache id="cache-with-xml" cache-xml-location="cache-config.xml" properties-ref="props"/> <object name="props" type="System.Collections.Specialized.NameValueCollection"> <property name="['log-level']" value="warning"/> <!-- set properties such as durable-client-id, durable-timeout for a durable cache --> </object>
Other properties that can be set in the namespace are
disconnect-on-close
,
keepalive-on-close
, and
distributed-system-name
as shown below.
<gfe:cache disconnect-on-close="false" keepalive-on-close="true" distributed-system-name="MySystemName"/>
The disconnect-on-close property is set to true by default. It should be changed to false in case you encounter a race condition in the 3.0.0.5 client library that results in the disconnect call hanging the application.
Or can use fallback to a raw
<objects>
declaration:
<object name="cache-with-props" type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"> <property name="Properties"> <name-values> <add key="log-level" value="warning"/> </name-values> </property> </object>
In this last example, the SGF classes are declared and configured directly without relying on the namespace. As one can tell, this approach is a generic one, exposing more of the backing infrastructure.
<object name="default-cache" type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"/>
Here, the default-cache will try to connect to an existing cache and, in case one does not exist, create a local client cache. Since no additional properties were specified the created cache uses the default cache configuration.
The name of the cache will be the name of the Spring object
definition unless you specify a different name using the
NameValueCollection property described next. Note, you can specify the
name of the underlying DistributedSystem that will be created with the
property DistributedSystemName
Especially in environments with opened caches, this basic configuration can go a long way. For scenarios where the cache needs to be configured, the user can pass in a reference the GemFire configuration file:
<object name="cache-with-xml" type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"> <property name="CacheXml" value="cache.xml"/> </object>
In this example, if the cache needs to be created, it will use the
file named cache.xml
located in the runtime
directory.
In addition to referencing an external configuration file one can specify GemFire settings directly through .NET name value properties. This can be quite handy when just a few settings need to be changed:
<object name="cache-with-props" type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"> <property name="Properties"> <name-values> <add key="log-level" value="warning"/> </name-values> </property> </object>
The 'name' property is used to set the name of the Cache object. For a complete list of properties refer to the GemFire reference documentation.
Spring object definitions support property replacement through the use of variable replacement. The following configuration allows you to externalize the properties from the Spring configuration file which is a best practice. The Spring configuration file is usually an embedded assembly resource so as to prevent accidental changes in production. There are seven locations supported out of the box in Spring.NET where you can place your externalized configuration data and can be extended to support your own locations. In the following example the configuration flues would come from a name-value configuration section in App/Web.config
<gfe:cache id="cache-with-props" cache-xml-location="cache-config.xml" properties-ref="props"/> <object name="props" type="System.Collections.Specialized.NameValueCollection"> <property name="['log-level']" value="${cache.log-level}"/> </object> <object type="Spring.Objects.Factory.Config.VariablePlaceholderConfigurer, Spring.Core"> <property name="VariableSources"> <list> <object type="Spring.Objects.Factory.Config.ConfigSectionVariableSource, Spring.Core"> <property name="SectionNames" value="CacheConfiguration" /> </object> </list> </property> </object>
The CacheConfiguration section in App.config would then look like the following
<configuration> <configSections> <section name="CacheConfiguration" type="System.Configuration.NameValueSectionHandler"/> </configSections> <CacheConfiguration> <add key="cache.log-level" value="warning"/> </CacheConfiguration> </configuration>
It is worth pointing out again, that the cache settings apply only if the cache needs to be created, there is no opened cache in existence otherwise the existing cache will be used and the configuration will simply be discarded.
Once the Cache is configured, one needs to configure one or more Regions to interact with the data fabric. SGF allows various region types to be configured and created directly from Spring or in case they are created directly in GemFire, retrieved as such.
For more information about the various region types and their capabilities as well as configuration options, please refer to the GemFire Developer's Guide and community site.
For consuming but not creating
Regions (for example in case, the regions
are already configured through GemFire native configuration, the
cache.xml
), one can use the
lookup-region
element. Simply declare the target
region name the name
attribute; for example to
declare a object definition, named region-object
for
an existing region named orders
one can use the
following definition:
<gfe:lookup-region id="region-object" name="orders"/>
If the name
is not specified, the object name
will be used automatically. The example above becomes:
<!-- lookup for a region called 'orders' --> <gfe:lookup-region id="orders"/>
Note | |
---|---|
If the region does not exist, an initialization exception will be thrown. For configuring new GemFire regions proceed to the sections below for client region and advanced client region configuration. |
Note that in the previous examples, since no cache name was
defined, the default SGF naming convention
(gemfire-cache
) was used. If that is not an option,
one can point to the cache object through the
cache-ref
attribute:
<gfe:cache id="cache"/> <gfe:lookup-region id="region-object" name="orders" cache-ref="cache"/>
The lookup-region
provides a simple way of
retrieving existing, pre-configured regions without exposing the region
semantics or setup infrastructure.
GemFire supports various deployment topologies for managing and
distributing data. For .NET the currenlty supported topoloy is
client-server. Additional topologies such as peer-to-peer may be
supported in future releases of the .NET client as they are supported in
the Java client already. To declare client regions
which connect to a backing cache server, SGF offers dedicated support
for such configuration through the client-region
and
pool
elements. As the name imply, the former defines
a client region while the latter connection pools to be used/shared by
the various client regions.
Below is a usual configuration for a client region:
<gfe:cache/> <!-- client region declaration --> <gfe:client-region id="complex" pool-name="gemfire-pool"> <gfe:cache-listener ref="c-listener"/> </gfe:client-region> <object id="c-listener" type="Spring.Data.GemFire.Tests.SimpleCacheListener, Spring.Data.GemFire.Tests"/> <!-- pool declaration --> <gfe:pool id="gemfire-pool" subscription-enabled="false"> <gfe:server host="localhost" port="40404"/> </gfe:pool>
Just as the other region types, client-region
allows defining CacheListeners. It also
relies on the same naming conventions in case the region name or the
cache are not set explicitely. However, it also requires a connection
pool
to be specified for connecting to the server.
Each client can have its own pool or they can share the same one.
The client-region can also reference multiple cache-listeners:
<gfe:cache/> <gfe:client-region id="complex" pool-name="gemfire-pool"> <gfe:cache-listener> <ref object="c-listener"/> <object type="Spring.Data.GemFire.Tests.SimpleCacheListener, Spring.Data.GemFire.Tests"/> </gfe:cache-listener> </gfe:client-region> <object id="c-listener" type="Spring.Data.GemFire.Tests.SimpleCacheListener, Spring.Data.GemFire.Tests"/> <!-- pool declaration --> <gfe:pool id="gemfire-pool" subscription-enabled="false"> <gfe:server host="localhost" port="40404"/> </gfe:pool>
Other options to configure the client-region are to specify a specific cache by name, set the and persistence of the reigon via the persistent attribute, and also to set the various RegionAttributes. RegionAttributes are set using the attributes-ref element:
<gfe:client-region id="simple" pool-name="gemfire-pool" attributes-ref="myCustomRegionAttributes" /> <!-- some common base cache configuration that can be used later in parent object referencts --> <object id="cachingProxy" type="Spring.Data.GemFire.RegionAttributesFactoryObject, Spring.Data.GemFire"> <property name="ClientNotification" value="true"/> </object> <object id="myCustomRegionAttributes" parent="cachingProxy" type="Spring.Data.GemFire.RegionAttributesFactoryObject, Spring.Data.GemFire"> <property name="LruEntriesLimit" value="1234"/> </object>
The use of Spring's parent referent allows you set setup commonly used configurations and then extend them by overriding or adding additional properties.
For a full list of options to set on the client and especially on the pool, please refer to the SGF schema (Appendix A, Spring.NET for Gemfire's spring-gemfire-1.0.xsd) and the GemFire documentation.
To minimize network traffic, each client can define its own 'interest', pointing out to GemFire, the data it actually needs. In SGF, interests can be defined for each client, both key-based and regular-expression-based types being supported; for example:
<gfe:client-region id="complex" pool-name="gemfire-pool"> <gfe:all-keys-interest durable="true" result-policy="Keys"/> <gfe:key-interest result-policy="KeysAndValues" key-ref="cacheableString"/> <gfe:regex-interest pattern=".*"/> </gfe:client-region> <object id="cacheableString" type="GemStone.GemFire.Cache.CacheableString, GemStone.GemFire.Cache"> <constructor-arg index="0" value="hello"/> </object>
SGF namespaces allow short and easy configuration of the major GemFire regions and associated entities. However, there might be corner cases where the namespaces are not enough, where a certain combination or set of attributes needs to be used. For such situations, using directly the SGF FactoryObjects is a possible alternative as it gives access to the full set of options at the expense of conciseness.
As a warm up, below are some common configurations, declared through
raw objects
definitions.
<object name="basic" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire"> <property Name="Cache"> <object type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"/> </property> </object>
By default the region name is the name of the object definition
unless explicitly specified using the Name
property.
Notice how the GemFire cache definition has been nested into the declaring
region definition. Let's add more regions and make the cache a top level
object.
Since the region object definition name is usually the same with
that of the cache, the name
property can be omitted
(the object name will be used automatically).
<!-- shared cache across regions --> <object id="cache" type="Spring.Data.GemFire.CacheFactoryObject, Spring.Data.GemFire"/> <!-- region named 'basic' --> <object name="basic" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire"> <property name="Endpoints" value="localhost:40404"/> <property name="Cache" ref="Cache"/> </object> <!-- region with a name different then the object definition --> <object name="basic" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire"> <property name="Endpoints" value="localhost:40404"/> <property name="Cache" ref="Cache"/> <property name="Name" value="default-region"/> </object>
It is worth pointing out, that for the vast majority of cases configuring the cache loader, listener and writer through the Spring container is preferred since the same instances can be reused across multiple regions and additionally, the instances themselves can benefit from the container's rich feature set:
<object name="baseRegion" abstract="true"> <property name="Endpoints" value="localhost:40404"/> <property name="Cache" ref="Cache"/> <!-- definition not shown here --> </object> <object name="listeners" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire" parent="baseRegion"> <property name="CacheListener"> <object type="Spring.Data.GemFire.Tests.SimpleCacheListener, Spring.Data.GemFire.Tests"> <!-- set properties or constructor arguments --> </object> </property> <property name="CacheLoader"> <object type="Spring.Data.GemFire.Tests.SimpleCacheLoader, Spring.Data.GemFire.Tests"/> </property> <property name="CacheWriter"> <object type="Spring.Data.GemFire.Tests.SimpleCacheWriter, Spring.Data.GemFire.Tests"/> </property> </object>
For scenarios where a CacheServer is used and
clients need to be configured and the namespace is
not an option, SGF offers a dedicated configuration class named:
ClientRegionFactoryObject. This allows client
interests to be registered in both key and regex
form through AllKeysInterest,
KeyInterest, and
RegexInterest classes in the
Spring.Data.GemFire
namespace. Here is an example of
how to configure the AllKeys interest in the region.
<object name="baseRegion" abstract="true"> <property name="Endpoints" value="localhost:40404"/> <property name="Cache" ref="Cache"/> <!-- Cache definition not shown here --> <property name="Attributes"> <object type="Spring.Data.GemFire.RegionAttributesFactoryObject, Spring.Data.GemFire"> <property name="ClientNotification" value="true"/> </object> </property> </object> <object name="region-all-keys-interest" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire" parent="baseRegion"> <property name="interests"> <list> <object type="Spring.Data.GemFire.AllKeysInterest"/> </list> </property> </object>
This example makes use of Spring's object configuration
inheritance allowing you to place common configuration in the baseRegion
object definition and then refer to it later via the
parent
attribute.
Users that need fine control over a region, can configure it in
Spring by using the Attributes
property. To ease
declarative configuration in Spring, SGF provides an
RegionAttributesFactoryObject. The previous
examples shows configuring the baseRegion by embedding the use of the
RegionAttributesFactoryObject:
To register interest for a set of key, use the KeyInterest class, as shown below from the sample application
<object name="region" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire" parent="baseRegion"> <!-- baseRegion definition not shown here --> <property name="Interests"> <list> <object type="Spring.Data.GemFire.KeyInterest"> <property name="Keys"> <list> <object type="GemStone.GemFire.Cache.CacheableString" factory-method="Create"> <constructor-arg value="Key-123"/> </object> </list> </property> </object> </list> </property> </object>
To register interest based on a regular expression, use the following configuration
<object name="Region" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire" parent="baseRegion"> <!-- baseRegion definition not shown here --> <property name="Interests"> <list> <object type="Spring.Data.GemFire.RegexInterest"> <property name="Regex" value="Key-.*"/> </object> </list> </property> </object>
This would only register interest in keys of the type 'Key-123' or 'Key-4abc'.
See below an example of configuring a region through Spring XML embedding the use of the RegionAttributesFactoryObject, no parent object definition is used.
<object name="Region" type="Spring.Data.GemFire.ClientRegionFactoryObject, Spring.Data.GemFire" <property name="Endpoints" value="localhost:40404"/> <property name="Cache" ref="Cache"/> <property name="Name" value="exampleregion"/> <property name="Attributes"> <object type="Spring.Data.GemFire.RegionAttributesFactoryObject, Spring.Data.GemFire"> <property name="ClientNotification" value="true"/> </object> </property> <property name="Interests"> <list> <object type="Spring.Data.GemFire.RegexInterest"> <property name="Regex" value="Key-.*"/> </object> </list> </property> </object>
Please refer to the API documentation for more information on the various IInterest subclasses.
With SGF, GemFire regions, pools and cache can be configured either
through Spring or directly inside GemFire, native,
cache.xml
file. While both are valid approaches, it's
worth pointing out that Spring's powerful DI container and AOP
functionality makes it very easy to wire GemFire into an application. This
lalso lets you leverage features such as replacement of varaiable
placeholders, e..g. ${port}. Also configuring a region cache loader,
listener and writer through the Spring container is preferred since the
same instances can be reused across multiple regions. Spring object
definition inheritance also allows you to create base region and region
attribute object definitions that can later be referred to using Spring's
'parent' attribute.
Whatever route one chooses to go, SGF supports both approaches allowing for easy migrate between them without forcing an upfront decision.