This topic discusses declarative configuration in Spring Boot for VMware Tanzu GemFire.
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 Spring Data for VMware GemFire annotations covered by Spring Boot for Tanzu GemFire’s auto-configuration.
-
Lists all Spring Data for VMware GemFire annotations not covered by Spring Boot for Tanzu GemFire’s auto-configuration.
-
Covers the Spring Boot for Tanzu GemFire, Spring Session for VMware GemFire and Spring Data for VMware GemFire annotations that you must explicitly declare and that provide the most value and productivity when getting started with VMware GemFire in Spring [Boot] applications.
The list of Spring Data for VMware GemFire annotations covered by Spring Boot for Tanzu GemFire’s auto-configuration is discussed in detail in the Appendix: Auto-configuration versus Annotation-based configuration.
To be absolutely clear about which Spring Data for VMware GemFire annotations we are referring to, we mean the Spring Data for VMware GemFire annotations in the org.springframework.data.gemfire.config.annotation
package.
In subsequent sections, we also cover which annotations are added by Spring Boot for Tanzu GemFire.
Auto-configuration
We explained auto-configuration in detail in the Auto-configuration chapter.
Annotations Not Covered by Auto-configuration
The following Spring Data for VMware GemFire annotations are not implicitly applied by Spring Boot for Tanzu GemFire’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)
-
@EnableStatistics
-
@UseGemFireProperties
One reason that Spring Boot for Tanzu GemFire 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
As stated earlier in this topic, Spring Boot for Tanzu GemFire 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 VMware GemFirecache.xml
. This is usually the case only if you have legacycache.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 VMware GemFire) 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 and 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.
Productivity Annotations
This section calls out the annotations we believe to be most beneficial for your application development purposes when using VMware GemFire in Spring [Boot] applications.
@EnableClusterAware
The @EnableClusterAware
annotation is arguably the most powerful and valuable annotation.
Example 1. Declaring @EnableClusterAware
@SpringBootApplication
@EnableClusterAware
class SpringBootGemFireClientCacheApplication { }
When you annotate your main @SpringBootApplication
class with @EnableClusterAware
, your Spring Boot, VMware GemFire 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 VMware GemFire servers is detected, the client application sends and receives data to and from the VMware GemFire 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 Spring Data for VMware GemFire’s @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.
@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, VMware GemFire ClientCache
application requires an VMware GemFire 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 start up in a local-only capacity.
When strictMatch
is set to true
and an VMware GemFire cluster is not available, your Spring Boot, VMware GemFire 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:
Example 2. Set @EnableClusterAware.strictMatch
@SpringBootApplication
@EnableClusterAware(strictMatch = true)
class SpringBootGemFireClientCacheApplication { }
Alternatively, you can set strictMatch
attribute by using the corresponding property in Spring Boot application.properties
:
Example 3. Set strictMatch
using a property
# 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 VMware GemFire cluster, such as which explicitly or implicitly configured connections were successful.
The following example shows typical output:
Example 4. @EnableClusterAware
INFO log output
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 VMware GemFire-based cluster
Note:
An attempt is always made to connect to
localhost
on the default Locator
port,
10334
, and the default CacheServer
port
40404
.
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.
@EnableCachingDefinedRegions
, @EnableClusterDefinedRegions
and @EnableEntityDefinedRegions
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:
Example 5. Creating a Region with Spring JavaConfig
@Configuration
class GemFireConfiguration {
@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:
Example 6. Creating a client Region using Spring 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.
@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 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:
Example 7. Defining Regions based on Spring or JSR-107 JCache Annotations
@Service
class CustomerService {
@Cacheable(cacheNames = "CustomersByAccountNumber", key = "#account.number")
public Customer findBy(Account account) {
// ...
}
}
Further consider the following example, in which the main @SpringBootApplication
class is annotated with @EnableCachingDefinedRegions
:
Example 8. Using @EnableCachingDefinedRegions
@SpringBootApplication
@EnableCachingDefinedRegions
class SpringBootGemFireClientCacheApplication { }
With this setup, Spring Boot for Tanzu GemFire would create a client PROXY
Region (or PARTITION_REGION
if your application were a peer member of the VMware GemFire 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.
@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 Spring Data for VMware GemFire’s @Region
mapping annotation:
Example 9. Customer entity class annotated with @Region
@Region("Customers")
class Customer {
@Id
private Long id;
@Indexed
private String name;
}
For this class, Spring Boot for Tanzu GemFire 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
:
Example 10. Using @EnableEntityDefinedRegions
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class,
clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY)
class SpringBootGemFireClientCacheApplication { }
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:
Example 11. Entity Scan
- 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:
Example 12. Targeting with @EnableEntityDefinedRegions
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = { NonEntity.class, Account.class } )
class SpringBootGemFireClientCacheApplication { }
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.
@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
:
Example 13. Using @EnableClusterDefinedRegions
@SpringBootApplication
@EnableClusterDefinedRegions
class SpringBootGemFireClientCacheApplication { }
Every Region that exists on the servers in the VMware GemFire 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:
Example 14. Using a server-side Region on the client
@Component
class SomeApplicationComponent {
@Resource(name = "ServerRegion")
private Region<Integer, EntityType> serverRegion;
public void someMethod() {
EntityType entity = new EntityTypeImpl(...);
this.serverRegion.put(1, entity);
// ...
}
}
Spring Boot for Tanzu GemFire auto-configures a GemfireTemplate
for the “ServerRegion” Region (see RegionTemplateAutoConfiguration, 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:
Example 15. Using a server-side Region on the client with a template
@Component
class SomeApplicationComponent {
@Autowired
@Qualifier("serverRegionTemplate")
private GemfireTemplate serverRegionTemplate;
public void someMethod() {
EntityType entity = new EntityTypeImpl(...);
this.serverRegionTemplate.put(1, entity);
//...
}
}
@EnableIndexing
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 Spring Data for VMware GemFire, such as @Indexed
.
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.
You may have noticed that the Customer
entity class’s name
field was annotated with @Indexed
.
Consider the following listing:
Example 16. Customer entity class with @Indexed
annotated name
field
@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 VMware GemFire OQL Index for the Customer.name
field is created, allowing OQL queries on customers by name to use this Index:
Example 17. Using @EnableIndexing
@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = Customer.class)
@EnableIndexing
class SpringBootGemFireClientCacheApplication { }
Note: OQL Indexes are not persistent between restarts, and VMware GemFire 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.
@EnableExpiration
It is often useful to define both eviction and expiration policies, particularly with a system like VMware GemFire, 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.
You can enable off-heap (or main memory usage) capabilities by declaring Spring Data for VMware GemFire’s @EnableOffHeap
annotation.
Defining eviction and expiration policies lets you limit what is kept in memory and for how long.
With Spring Data for VMware GemFire, you can define the expiration policies associated with a particular application class type on the class type itself, by using the @Expiration
, @IdleTimeoutExpiration
and @TimeToLiveExpiration
annotations.
See the VMware GemFire 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:
Example 18. Customer entity class with Idle Timeout Expiration (TTI)
@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
:
Example 19. Enabling Expiration
@SpringBootApplication
@EnableExpiration
class SpringBootGemFireApplication { }
Note:
Technically, this entity-class-specific
annotation-based expiration policy is implemented by using
VMware GemFire's CustomExpiry
interface. For more information, see the VMware GemFire Java API Reference.
Content feedback and comments