diff --git a/spring-geode-docs/spring-geode-docs.gradle b/spring-geode-docs/spring-geode-docs.gradle index 19a17ac7..b1a5402a 100644 --- a/spring-geode-docs/spring-geode-docs.gradle +++ b/spring-geode-docs/spring-geode-docs.gradle @@ -26,8 +26,9 @@ asciidoctor { 'version-snapshot': snapshotBuild, 'version-milestone': milestoneBuild, 'version-release': releaseBuild, - 'download-url' : "${githubBaseUrl}/archive/${githubTag}.zip", 'github-url': githubUrl, + 'github-samples-url': "${githubUrl}/spring-geode-samples", + 'download-url' : "${githubBaseUrl}/archive/${githubTag}.zip", 'spring-version' : versions['org.springframework:spring-core'], 'spring-boot-version' : "${springBootVersion}", 'spring-boot-data-geode-version' : project.version, @@ -37,7 +38,8 @@ asciidoctor { 'spring-session-data-gemfire-version' : "${springSessionDataGeodeVersion}", 'spring-session-data-geode-version' : "${springSessionDataGeodeVersion}", 'docs-src-dir' : rootProject.projectDir.path + '/spring-geode-docs/src/main/java', - 'examples-dir' : rootProject.projectDir.path + '/spring-geode-examples/' + 'examples-dir' : rootProject.projectDir.path + '/spring-geode-examples/', + 'samples-dir' : rootProject.projectDir.path + '/spring-geode-samples/' } javadoc { diff --git a/spring-geode-docs/src/docs/asciidoc/guides/boot-configuration.adoc b/spring-geode-docs/src/docs/asciidoc/guides/boot-configuration.adoc new file mode 100644 index 00000000..71b04935 --- /dev/null +++ b/spring-geode-docs/src/docs/asciidoc/guides/boot-configuration.adoc @@ -0,0 +1,767 @@ +[[geode-samples-boot-configuration]] += Spring Boot Auto-configuration for Apache Geode & Pivotal GemFire + +This guide walks you through building a simple Customer Service, Spring Boot application using Apache Geode to manage +Customer interactions. It is assumed that you are already familiar with Spring Boot and Apache Geode. By the end of +this lesson, users should have a better understanding of the _auto-configuration_ support provided by +Spring Boot Data Geode (SBDG). + +Let's begin. + +NOTE: This guide builds on the https://www.youtube.com/watch?v=OvY5wzCtOV0[_Simplifying Apache Geode with Spring Data_] +talk presented by John Blum during the 2017 SpringOne Platform conference in San Francisco, CA. While this example +as well as the example presented during the talk both use Spring Boot, only this example is using Spring Boot +for Apache Geode (SBDG). Therefore, this guide and its example is an improvement over the example in the presentation. + +[[geode-samples-boot-configuration-app-domain-classes]] +== Application Domain Classes + +==== `Customer` class + +Like any sensible application development project, we begin by modeling the data our application needs to manage, +i.e. a `Customer`. For this example, the `Customer` class is implemented as follows: + +link:{samples-dir}/boot/configuration/src/main/java/example/app/crm/model/Customer.java[] + +The `Customer` class uses https://projectlombok.org/[Project Lombok] to simplify the implementation so that we can focus +on the important details. Lombok is useful for testing or prototyping purposes. However, using Lombok is not required +and in most production applications, I would not recommend using it. + +Additionally, the `Customer` class is annotated with Spring Data Geode's (SDG) `@Region` annotation. `@Region` +is a mapping annotation declaring the Apache Geode cache `Region` in which Customer data will be persisted. + +Finally, the `org.springframework.data.annotation.Id` annotation was used to designate the `Customer.id` field as +the identifier for `Customer` objects. The identifier is the Key of the Entry in the Apache Geode cache `Region`. +A `Region` is a distributed version of `java.util.Map`. + +NOTE: If the `@Region` annotation is not explicitly declared, then SDG uses the simple name of the class, which in this +case would just be "Customer", to identify the `Region`. However, there is another reason we explicitly annotated the +`Customer` class with `@Region`, which we will cover below. + +==== `CustomerRepository` interface + +Next, we create a _Data Access Object_ (DAO), or Spring Data _Repository_ to persist `Customers` to Apache Geode: + +link:{samples-dir}/boot/configuration/src/main/java/example/app/crm/repo/CustomerRepository.java[] + +`CustomerRepository` is a Spring Data `CrudRepository`. `CrudRepository` provides basic CRUD (Create, Read, Update, +Delete) data access operations along with simple queries for `Customer` objects stored in Apache Geode. + +Spring Data Geode is responsible for creating a proxy for your application-defined _Repository_ interfaces in order to +implement any query methods you may have explicitly defined on the interface in addition to the data access operations +provided by the `CrudRepository` interface extension. + +In addition to the base `CrudRepository` operations, `CustomerRepository` has additionally defined a +`findByNameLike(:String):Customer` query method. + +NOTE: Though it is beyond the scope of this document, Spring Data's _Repository_ infrastructure is capable of generating +data store specific queries (e.g. Apache Geode OQL) for _Repository_ interface query method declarations just by +introspecting the method signature. The query methods must conform to specific conventions. Alternatively, users +may use `@Query` to annotate query methods and specify the raw query instead (i.e. OQL for Apache Geode, +SQL for JDBC, and so on). + +==== Customer Service Application (main class) + +Now that we have created the basic domain classes of our Customer Service application, we need a main application class +to drive the interactions with `Customers`. + +The end result looks like this: + +link:{samples-dir}/boot/configuration/src/main/java/example/app/crm/CustomerServiceApplication.java[] + +The `CustomerServiceApplication` class is annotated with `@SpringBootApplication`. Therefore, this main class is +a proper Spring Boot application equipped with all the features of Spring Boot (e.g. _auto-configuration_). + +Additionally, we use Spring Boot's `SpringApplicationBuilder` in the `main` method to configure and bootstrap +the Customer Service application. + +Then, we declare a Spring Boot `ApplicationRunner` bean, which is invoked by Spring Boot after the Spring container +(i.e. `ApplicationContext`) has been properly initialized and started. Our `ApplicationRunner` defines the Customer +interactions performed by our Customer Service application. + +Specifically, we create a new `Customer` object ("Jon Doe"), save him to the Apache Geode "Customers" cache Region, +and then query for "Jon Doe" using an OQL query with the predicate: `name LIKE '%Doe'`. + +NOTE: `%` is the wildcard for OQL text searches. + +[[geode-samples-boot-configuration-autoconfig]] +== Auto-configuration for Apache Geode, Take One + +"_With great power comes great responsibility._" - Uncle Ben + +While it is not apparent (yet), there is a lot of intrinsic power provided by Spring Boot Data Geode (SBDG) +in this example. + +==== Cache instance + +First, in order to put anything into Apache Geode, you need a cache instance. A cache instance is also required to +create the Regions which ultimately will store the application's data (state). Again, a `Region` is just a Key/Value +data structure, like a `java.util.Map`, mapping a Key to an Object. A `Region` is actually much more than a simple +`Map` since it is distributed. However, since `Region` implements `java.util.Map`, it can be treated as such. + +NOTE: A complete discussion of a `Region` and it concepts are beyond the scope of this document. You may learn more +by reading the Apache Geode User Guide on {apache-geode-docs}/developing/region_options/chapter_overview.html[Regions]. + +SBDG is opinionated and assumes most developer applications will be client applications in Apache Geode's +{apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[client/server topology]. As a result, +SBDG will auto-configure a `ClientCache` instance by default. + +We can make this more apparent by disabling the _auto-configuration_ of the `ClientCache` instance provided by SBDG: + +.Disabling ClientCache auto-configuration +[source,java] +---- +@SpringBootApplication(exclude = ClientCacheAutoConfiguration.class) +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +public class CustomerServiceApplication { + ... +} +---- + +Note the `exclude` on the `ClientCacheAutoConfiguration` class. + +With the correct log level set, you will see an error message similar to: + +.Error resulting from no ClientCache instance +[source,txt] +---- +16:20:47.543 [main] DEBUG o.s.b.d.LoggingFailureAnalysisReporter - Application failed to start due to an exception +org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'example.app.crm.repo.CustomerRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} + at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1509) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:819) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + ... +16:20:47.548 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of method runner in example.app.crm.CustomerServiceApplication required a bean of type 'example.app.crm.repo.CustomerRepository' that could not be found. +---- + +Essentially, the `CustomerRepository` could not be injected into our `CustomerServiceApplication` class, +`ApplicationRunner` bean method because the `CustomerRepository`, which depends on the "Customers" Region, +could not be created. The `CustomerRepository` could not be created because the "Customers" Region +could not be created. The "Customers" Region could not be created because there was no cache instance available +(e.g. `ClientCache`). + +The `ClientCache` auto-configuration is equivalent to the following: + +.Equivalent ClientCache configuration +[source,java] +---- +@SpringBootApplication +@ClientCacheApplication +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +public class CustomerServiceApplication { + ... +} +---- + +That is, we would need to explicitly declare the `@ClientCacheApplication` annotation if we were not using SBDG. + +==== Repository instance + +We are also using the Spring Data (Geode) _Repository_ infrastructure in the Customer Service application. +This should be evident from our definition of the application-specific `CustomerRepository` interface. + +If we disable the auto-configuration of the Spring Data Repository infrastructure: + +. Disabling Spring Data Repositories +[source,java] +---- +@SpringBootApplication(exclude = RepositoriesAutoConfiguration.class) +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +public class CustomerServiceApplication { + ... +} +---- + +We would run into a similar error: + +.Error resulting from no proxied CustomerRepository instance +[source,txt] +---- +17:31:21.231 [main] DEBUG o.s.b.d.LoggingFailureAnalysisReporter - Application failed to start due to an exception +org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'example.app.crm.repo.CustomerRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} + at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1509) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:819) ~[spring-beans-5.0.13.RELEASE.jar:5.0.13.RELEASE] + ... +17:31:21.235 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of method runner in example.app.crm.CustomerServiceApplication required a bean of type 'example.app.crm.repo.CustomerRepository' that could not be found. +---- + +The Spring Data _Repository auto-configuration_ even takes care of locating our application Repository interface +definitions for us. + +Without auto-configuration, you would need to: + +.Equivalent Spring Data Repositories configuration +[source,java] +---- +@SpringBootApplication +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +@EnableGemfireRepositories(basePackageClasses = CustomerRepository.class) +public class CustomerServiceApplication { + ... +} +---- + +That is, you would need to explicitly declare the `@EnableGemfireRepositories` annotation and set the `basePackages` +attribute, or the equivalent, type-safe `basePackageClasses` attribute, if you were not using SBDG. + +==== Entity-defined Regions + +The only explicit declaration of configuration in our Customer Service application is with the +`@EnableEntityDefinedRegions` annotation. As was alluded to above, there was another reason to explicitly declare +the `@Region` annotation on our `Customer` class. + +We could, for all intents and purposes, explicitly define the client-local, "Customers" Regions as so: + +.JavaConfig Bean Defintion for the Customers Region +[source,java] +---- +@Bean("Customers") +public ClientRegionFactoryBean customersRegion(GemFireCache gemfireCache) { + + ClientRegionFactoryBean customersRegion = new ClientRegionFactoryBean<>(); + + customersRegion.setCache(gemfireCache); + customersRegion.setClose(false); + customersRegion.setShortcut(ClientRegionShortcut.LOCAL); + + return customersRegion; +} +---- + +Or, even define the "Customers" Region using XML: + +.XML Bean Definition for the Customers Region +[source,xml] +---- + +---- + +But, it is very convenient to scan and then define Regions (whether client or server/peer Regions) based on +your application entity classes themselves (e.g. `Customer`): + +.Annotation-based config for the Customers Region +[source,java] +---- +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +---- + +The `basePackageClasses` attribute is an alternative to `basePackages`, and is a more ideal, type-safe way to target +the packages (and subpackages) containing the entity classes that your application will persist to Apache Geode. You +need only choose 1 class in the top-level package where you want the scan to begin. Spring Data Geode uses this class +to determine the package to start the scan. 'basePackageClasses` accepts an array of `Class` types so that you can +specify multiple independent top-level packages. The annotation also includes the ability to filter types. + +However, the `@EnableEntityDefinedRegions` annotation only works when the entity class (e.g. `Customer`) is explicitly +annotated with the `@Region` annotation (e.g. `@Region("Customers")`), otherwise it ignores the class. + +You will also notice that the data policy type (i.e. `clientRegionShort`, or simply `shortcut`) is set to `LOCAL` +in our example. Why? + +Well, initially we wanted to get up and running as quickly as possible, without a lot of ceremony and fuss. By using a +`LOCAL` client Region initially, we are not required to start a server for the client to be able to store data. + +However, while `LOCAL` client Regions can be useful for some purposes (e.g. local processing/querying), it is +more common for a client to persist data in a cluster of servers, and for that data to be share by multiple clients, +especially as the application is scaled out to meet demand. + +[[geode-samples-boot-configuration-clientserver]] +== Switching to Client/Server + +We continue with our example by switching from local to a client/server architecture. + +If you are rapidly prototyping your application and want to lift off the ground quickly, then it is useful to start +locally and gradually migrate to a client/server topology. + +To switch to the client/server architecture, all you need to do is remove the `clientRegionShortcut` attribute: + +.Client/Server Topology Region Configuration +[source,java] +---- +@EnableEntityDefinedRegions(basePackageClasses = Customer.class) +---- + +The default value for the `clientRegionShortcut` attribute is `ClientRegionShortcut.PROXY`. This means no data +is kept locally. All data is sent from the client to 1 or more servers in a cluster. + +However, if we try to run the application, it will fail: + +.NoAvailableServersException +[source,txt] +---- +Caused by: org.apache.geode.cache.client.NoAvailableServersException + at org.apache.geode.cache.client.internal.pooling.ConnectionManagerImpl.borrowConnection(ConnectionManagerImpl.java:234) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.OpExecutorImpl.execute(OpExecutorImpl.java:136) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.OpExecutorImpl.execute(OpExecutorImpl.java:115) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.PoolImpl.execute(PoolImpl.java:763) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.QueryOp.execute(QueryOp.java:58) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.ServerProxy.query(ServerProxy.java:70) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.executeOnServer(DefaultQuery.java:456) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.execute(DefaultQuery.java:338) ~[geode-core-1.2.1.jar:?] + at org.springframework.data.gemfire.GemfireTemplate.find(GemfireTemplate.java:311) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.repository.support.SimpleGemfireRepository.count(SimpleGemfireRepository.java:129) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + ... + at example.app.crm.CustomerServiceApplication.lambda$runner$0(CustomerServiceApplication.java:59) ~[classes/:?] + at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:783) ~[spring-boot-2.0.9.RELEASE.jar:2.0.9.RELEASE] +---- + +The client is expecting there to be a cluster of servers to communicate with and to store/access data. + +There are several ways in which to to start a cluster of GemFire/Geode servers. For example, you may use Spring +to configure and bootstrap the cluster, which is demonstrated <>. + +For this sample, we are going to use the tools provided with Apache Geode or Pivotal GemFire, e.g. _Gfsh_ +(GemFire/Geode Shell) for reasons that will become apparent later. + +NOTE: You need to https://geode.apache.org/releases/[download] and {apache-geode-docs}/prereq_and_install.html[install] +a full distribution of Apache Geode to make use of the provided tools. After installation, you will need to set +the GEODE (or GEMFIRE) environment variable to the location of your installation. Additionally, add $GEODE/bin +to your system $PATH. + +Once Apache Geode is successfully installed, you can open a command prompt (terminal) and do: + +.Running Gfsh +[source,txt] +---- +$ echo $GEMFIRE +/Users/jblum/pivdev/apache-geode-1.2.1 + +jblum-mbpro-2:lab jblum$ gfsh + _________________________ __ + / _____/ ______/ ______/ /____/ / + / / __/ /___ /_____ / _____ / + / /__/ / ____/ _____/ / / / / +/______/_/ /______/_/ /_/ 1.2.1 + +Monitor and Manage Apache Geode +gfsh> +---- + +You are set to go. + +For convenience, this sample provides a _Gfsh_ shell script to start the cluster: + +link:{samples-dir}/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh[] + +Specifically, we are starting 1 Locator and 1 Server, all running with the default ports. + +Then you can execute the Gfsh shell script using: + +.Run the start-simple-cluster.gfsh +[source,txt] +---- +gfsh>run --file=/Users/jblum/pivdev/spring-boot-data-geode/samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh +1. Executing - start locator --name=LocatorOne --log-level=config + +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: 68425 +Uptime: 2 seconds +Geode Version: 1.2.1 +Java Version: 1.8.0_192 +Log File: /Users/jblum/pivdev/lab/LocatorOne/LocatorOne.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.99.199.24, port=1099] + +Cluster configuration service is up and running. + +2. Executing - start server --name=ServerOne --log-level=config + +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: 68434 +Uptime: 2 seconds +Geode Version: 1.2.1 +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.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 +---- + +NOTE: You will need to change the path to spring-boot-data-geode/samples/boot/configuration directory in the +`run --file=...` _Gfsh_ command above based on where you cloned the `spring-boot-data-geode` project on your computer. + +Now, our simple cluster with an Apache Geode Locator and (Cache) Server is running. We can verify by +listing and describing members: + +.List and Describe Members +[source,txt] +---- +gfsh>list members + Name | Id +---------- | --------------------------------------------------- +LocatorOne | 10.99.199.24(LocatorOne:68425:locator):1024 +ServerOne | 10.99.199.24(ServerOne:68434):1025 + + +gfsh>describe member --name=ServerOne +Name : ServerOne +Id : 10.99.199.24(ServerOne:68434):1025 +Host : 10.99.199.24 +Regions : +PID : 68434 +Groups : +Used Heap : 27M +Max Heap : 3641M +Working Dir : /Users/jblum/pivdev/lab/ServerOne +Log file : /Users/jblum/pivdev/lab/ServerOne/ServerOne.log +Locators : 10.99.199.24[10334] + +Cache Server Information +Server Bind : null +Server Port : 40404 +Running : true +Client Connections : 0 +---- + +What happens if we try to run our application now? + +It will fail: + +.RegionNotFoundException +[source,txt] +---- +17:42:16.873 [main] ERROR o.s.b.SpringApplication - Application run failed +java.lang.IllegalStateException: Failed to execute ApplicationRunner + ... + at example.app.crm.CustomerServiceApplication.main(CustomerServiceApplication.java:51) [classes/:?] +Caused by: org.springframework.dao.DataAccessResourceFailureException: remote server on 10.99.199.24(SpringBasedCacheClientApplication:68473:loner):51142:f9f4573d:SpringBasedCacheClientApplication: While performing a remote query; nested exception is org.apache.geode.cache.client.ServerOperationException: remote server on 10.99.199.24(SpringBasedCacheClientApplication:68473:loner):51142:f9f4573d:SpringBasedCacheClientApplication: While performing a remote query + at org.springframework.data.gemfire.GemfireCacheUtils.convertGemfireAccessException(GemfireCacheUtils.java:230) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.GemfireAccessor.convertGemFireAccessException(GemfireAccessor.java:91) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.GemfireTemplate.find(GemfireTemplate.java:329) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.repository.support.SimpleGemfireRepository.count(SimpleGemfireRepository.java:129) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + ... + at example.app.crm.CustomerServiceApplication.lambda$runner$0(CustomerServiceApplication.java:59) ~[classes/:?] + at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:783) ~[spring-boot-2.0.9.RELEASE.jar:2.0.9.RELEASE] + ... 3 more +Caused by: org.apache.geode.cache.client.ServerOperationException: remote server on 10.99.199.24(SpringBasedCacheClientApplication:68473:loner):51142:f9f4573d:SpringBasedCacheClientApplication: While performing a remote query + at org.apache.geode.cache.client.internal.AbstractOp.processChunkedResponse(AbstractOp.java:352) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.QueryOp$QueryOpImpl.processResponse(QueryOp.java:170) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.AbstractOp.processResponse(AbstractOp.java:230) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.AbstractOp.attempt(AbstractOp.java:394) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.AbstractOp.attemptReadResponse(AbstractOp.java:203) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.ConnectionImpl.execute(ConnectionImpl.java:275) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.pooling.PooledConnection.execute(PooledConnection.java:332) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.OpExecutorImpl.executeWithPossibleReAuthentication(OpExecutorImpl.java:900) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.OpExecutorImpl.execute(OpExecutorImpl.java:158) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.OpExecutorImpl.execute(OpExecutorImpl.java:115) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.PoolImpl.execute(PoolImpl.java:763) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.QueryOp.execute(QueryOp.java:58) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.ServerProxy.query(ServerProxy.java:70) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.executeOnServer(DefaultQuery.java:456) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.execute(DefaultQuery.java:338) ~[geode-core-1.2.1.jar:?] + at org.springframework.data.gemfire.GemfireTemplate.find(GemfireTemplate.java:311) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.repository.support.SimpleGemfireRepository.count(SimpleGemfireRepository.java:129) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + ... + at example.app.crm.CustomerServiceApplication.lambda$runner$0(CustomerServiceApplication.java:59) ~[classes/:?] + at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:783) ~[spring-boot-2.0.9.RELEASE.jar:2.0.9.RELEASE] + ... 3 more +Caused by: org.apache.geode.cache.query.RegionNotFoundException: Region not found: /Customers + at org.apache.geode.cache.query.internal.DefaultQuery.checkQueryOnPR(DefaultQuery.java:599) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.execute(DefaultQuery.java:348) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.query.internal.DefaultQuery.execute(DefaultQuery.java:319) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.BaseCommandQuery.processQueryUsingParams(BaseCommandQuery.java:121) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.BaseCommandQuery.processQuery(BaseCommandQuery.java:65) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.command.Query.cmdExecute(Query.java:91) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.BaseCommand.execute(BaseCommand.java:165) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.ServerConnection.doNormalMsg(ServerConnection.java:791) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.ServerConnection.doOneMessage(ServerConnection.java:922) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.ServerConnection.run(ServerConnection.java:1180) ~[geode-core-1.2.1.jar:?] + ... +---- + +The application fails to run because we (deliberately) did not create a corresponding, server-side, "Customers" Region. +In order for a client to send data via a client `PROXY` Region (a Region with no local state) to a server in a cluster, +at least one server in the cluster must have a matching Region by name (i.e. "Customers"). + +Indeed, we have no Regions in the cluster: + +.List Regions +[source,txt] +---- +gfsh>list regions +No Regions Found +---- + +Of course, you could have created the matching server-side, "Customers" Region using _Gfsh_: + +[source,txt] +---- +gfsh>create region --name=Customers --type=PARTITION +---- + +But, what if you have hundreds of domain objects, which is not unreasonable in a practical enterprise application? + +While it is not a "convention" in Spring Boot for Apache Geode, Spring Data for Apache Geode (SDG) comes to our rescue. +We simply only need to enable cluster configuration from the client: + +.Enable Cluster Configuration from the Client +[source,java] +---- +@SpringBootApplication +@EnableEntityDefinedRegions(basePackageClasses = Customer.class) +@EnableClusterConfiguration(useHttp = true) +public class CustomerServiceApplication { + ... +} +---- + +That is, we annotate our Customer Service application class with SDG's `@EnableClusterConfiguration` annotation. +Additionally, we have set the `useHttp` attribute to `true`. This sends the configuration meta-data from the client +to the cluster via GemFire/Geode's Management REST API. + +This is useful when your GemFire/Geode cluster may be running behind a firewall, such as on public cloud infrastructure. +However, there are other benefits to using HTTP as well. As stated, the client send configuation meta-data to +GemFire/Geode's Management REST interface, which is a facade for the server-side Cluster Configuration Service. If +another member (e.g. server) is added to the cluster as a peer, then this member will get the same configuration. If +the entire cluster goes down, it will have the same configuration when it restarts. + +SDG is careful not to stomp on existing Regions since those Regions might have data in them. Declaring the +`@EnableClusterConfiguration` annotation is a useful development-time utility, but it is recommended to explicitly +define and declare your Regions in production environments, either using _Gfsh_ or Spring confg. + +Now, we can run our application again, and this time, it works! + +.Client/Server Run Successful +[source,java] +---- + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v2.0.9.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 +---- + +In the cluster (server-side), we will also see that the "Customers" Region was created successfully: + +.List & Describe Regions +[source,txt] +---- +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 +---- + +We see that the "Customers" Region has a size of 1. We can even query the "Customers" Region: + +.Query for all Customers +[source,java] +---- +gfsh>query --query="SELECT customer.name FROM /Customers customer" +Result : true +Limit : 100 +Rows : 1 + +Result +------- +Jon Doe +---- + +[[geode-samples-boot-configuration-clientserver-autoconfig]] +== Auto-configuration for Apache Geode, Take Two + +What may not be apparent in this example up to this point is how the data got from the client to the server. Certainly, +our client did send `Jon Doe` to the server, but our `Customer` class is not `java.io.Serializable`. + +Any object that is sent over a network, between two Java processes, or streamed to/from disk, must be serializable. + +Additionally, when we started the cluster, we also did not include any application domain classes on the classpath +of any member in the cluster. As further evidence, we an adjust our query slightly: + +.Invalid Query +[source,txt] +---- +gfsh>query --query="SELECT * FROM /Customers" +Message : Could not create an instance of a class example.app.crm.model.Customer +Result : false +---- + +If we tried to perform a `get`, we would hit a similar error: + +.Region.get(key) +[source,txt] +---- +gfsh>get --region=/Customers --key=1 --key-class=java.lang.Long +Message : Could not create an instance of a class example.app.crm.model.Customer +Result : false +---- + +So, how was the data sent, then? + +Well, Apache Geode and Pivotal GemFire provide 2 proprietary serialization formats in addition to _Java Serialization_: +{apache-geode-docs}/developing/data_serialization/gemfire_data_serialization.html[Data Serialization] +and {apache-geode-docs}/developing/data_serialization/gemfire_pdx_serialization.html[PDX], or _Portable Data Exchange_. + +While _Data Serialization_ is more efficient, PDX is more flexible (i.e. "portable"). PDX enables data to be queried +in serialized form and is the format used to support both Java and native clients (C++, C#). Therefore, PDX is +auto-configured by Spring Boot Data Geode (SBDG), by default. + +This is convenient since you may not want to implement `java.io.Serializable` for all your application domain model +types that you store in Apache Geode. In other cases, you may not have control over the types referred to by your +application domain model types, such as when using a 3rd party library. + +So, SBDG auto-configures PDX and uses Spring Data Geode's `MappingPdxSerializer` as the `PdxSerializer` to de/serialize +all application domain types. + +If we disable PDX auto-configuration, we can see the effects of trying to serialize a non-serializable type, `Customer`. +First, let's destroy the server-side "Customers" Region: + +.Destroy "Customers" Region +[source,txt] +---- +gfsh>destroy region --name=/Customers +"/Customers" destroyed successfully. + + +gfsh>list regions +No Regions Found +---- + +Then, we disable PDX _auto-configuration_: + +.Disable PDX Auto-configuration +[source,java] +---- +@SpringBootApplication(exclude = PdxSerializationAutoConfiguration.class) +@EnableEntityDefinedRegions(basePackageClasses = Customer.class) +@EnableClusterConfiguration(useHttp = true) +public class CustomerServiceApplication { + ... +} +---- + +When we re-run the application, we get the error we would expect: + +.NotSerializableException +[source,txt] +---- +Caused by: java.io.NotSerializableException: example.app.crm.model.Customer + at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) ~[?:1.8.0_192] + at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) ~[?:1.8.0_192] + at org.apache.geode.internal.InternalDataSerializer.writeSerializableObject(InternalDataSerializer.java:2248) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.InternalDataSerializer.basicWriteObject(InternalDataSerializer.java:2123) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.DataSerializer.writeObject(DataSerializer.java:2936) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.util.BlobHelper.serializeTo(BlobHelper.java:66) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.Message.serializeAndAddPart(Message.java:396) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.Message.addObjPart(Message.java:340) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.tier.sockets.Message.addObjPart(Message.java:319) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.PutOp$PutOpImpl.(PutOp.java:281) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.PutOp.execute(PutOp.java:66) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.cache.client.internal.ServerRegionProxy.put(ServerRegionProxy.java:162) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.serverPut(LocalRegion.java:3006) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.cacheWriteBeforePut(LocalRegion.java:3115) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.ProxyRegionMap.basicPut(ProxyRegionMap.java:222) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.virtualPut(LocalRegion.java:5628) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegionDataView.putEntry(LocalRegionDataView.java:151) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.basicPut(LocalRegion.java:5057) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.validatedPut(LocalRegion.java:1595) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.LocalRegion.put(LocalRegion.java:1582) ~[geode-core-1.2.1.jar:?] + at org.apache.geode.internal.cache.AbstractRegion.put(AbstractRegion.java:325) ~[geode-core-1.2.1.jar:?] + at org.springframework.data.gemfire.GemfireTemplate.put(GemfireTemplate.java:193) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + at org.springframework.data.gemfire.repository.support.SimpleGemfireRepository.save(SimpleGemfireRepository.java:86) ~[spring-data-geode-2.0.14.RELEASE.jar:2.0.14.RELEASE] + ... + at example.app.crm.CustomerServiceApplication.lambda$runner$0(CustomerServiceApplication.java:70) ~[spring-samples-boot-configuration-1.0.0.RELEASE.jar] + at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:783) ~[spring-boot-2.0.9.RELEASE.jar:2.0.9.RELEASE] + ... +---- + +Our "Customers" Region is recreated, but is empty: + +.Empty "Customers" Region +[source,txt] +---- +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 | 0 + | data-policy | PARTITION +---- + +So, SBDG can take care of all your serialization needs without you having to configure serialization or implement +`java.io.Serializable` on all your application domain types, including types your application domain types refer to. + +The PDX _auto-configuration_ provided by SBDG is equivalent to: + +.Equivalent PDX Configuration +[source,java] +---- +@SpringBootApplication +@ClientCacheApplication +@EnableEntityDefinedRegions(basePackageClasses = Customer.class) +@EnableClusterConfiguration(useHttp = true) +@EnablePdx +public class CustomerServiceApplication { + ... +} +---- + +`@EnablePdx` is responsible for configuring PDX serialization and registering SDG's `MappingPdxSerializer`. + +[[geode-samples-boot-configuration-clientserver-secure]] +== Securing the Client & Server + + +[[geode-samples-boot-configuration-conclusion]] +== Conclusion + +Hopefully this guide has now given you a better understanding of what the _auto-configuration_ support provided by +Spring Boot for Apache Geode/Pivotal GemFire is giving you when developing Apache Geode or Pivotal GemFire applications +with Spring. diff --git a/spring-geode-docs/src/docs/asciidoc/index.adoc b/spring-geode-docs/src/docs/asciidoc/index.adoc index 45685838..2ba12eb8 100644 --- a/spring-geode-docs/src/docs/asciidoc/index.adoc +++ b/spring-geode-docs/src/docs/asciidoc/index.adoc @@ -217,4 +217,5 @@ include::data-serialization.adoc[] include::session.adoc[] include::security.adoc[] include::actuator.adoc[] +include::samples.adoc[] include::appendix.adoc[] diff --git a/spring-geode-docs/src/docs/asciidoc/samples.adoc b/spring-geode-docs/src/docs/asciidoc/samples.adoc new file mode 100644 index 00000000..d2150411 --- /dev/null +++ b/spring-geode-docs/src/docs/asciidoc/samples.adoc @@ -0,0 +1,19 @@ +[[geode-samples]] +== Samples + +This section contains working examples demonstrating how to use Spring Boot for Apache Geode and Pivotal GemFire (SBDG) +effectively. + +Some examples focus on specific Use Cases (e.g. [(HTTP) Session state] caching) while other examples demonstrate how +SBDG works under-the-hood to give users a better understanding of what is actually happening and how to debug problems +with their Apache Geode / Pivotal GemFire, Spring Boot applications. + +.Example Apache Geode Applications using Spring Boot +|=== +| Guide | Description | Source + +| link:guides/boot-configuration.html[Spring Boot Auto-configuration for Apache Geode/Pivotal GemFire] +| Explains what auto-configuration is provided by SBDG out-of-the-box and what the auto-configuration is doing. +| {github-samples-url}/boot/configuration[Boot Auto-configuration] + +|=== diff --git a/spring-geode-samples/boot/configuration/spring-geode-samples-boot-configuration.gradle b/spring-geode-samples/boot/configuration/spring-geode-samples-boot-configuration.gradle new file mode 100644 index 00000000..22a796f3 --- /dev/null +++ b/spring-geode-samples/boot/configuration/spring-geode-samples-boot-configuration.gradle @@ -0,0 +1,15 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' + +description = "Spring Geode Guides demonstrating the use of Spring Boot Auto-configuration for Apache Geode." + +dependencies { + + compile project(":spring-geode-starter") + + compile "org.springframework.data:spring-data-geode-test" + + compile "org.assertj:assertj-core" + + compile "org.projectlombok:lombok" + +} diff --git a/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/CustomerServiceApplication.java b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/CustomerServiceApplication.java new file mode 100644 index 00000000..7dbc55c4 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/CustomerServiceApplication.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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. + */ +package example.app.crm; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions; + +import example.app.crm.model.Customer; +import example.app.crm.repo.CustomerRepository; + +/** + * Spring Boot application implementing a Customer Service. + * + * @author John Blum + * @see org.springframework.boot.ApplicationRunner + * @see org.springframework.boot.autoconfigure.SpringBootApplication + * @see org.springframework.boot.builder.SpringApplicationBuilder + * @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions + * @since 1.0.0 + */ +// tag::class[] +@SpringBootApplication +@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) +public class CustomerServiceApplication { + + public static void main(String[] args) { + + new SpringApplicationBuilder(CustomerServiceApplication.class) + .web(WebApplicationType.NONE) + .build() + .run(args); + } + + @Bean + ApplicationRunner runner(CustomerRepository customerRepository) { + + return args -> { + + assertThat(customerRepository.count()).isEqualTo(0); + + Customer jonDoe = Customer.newCustomer(1L, "Jon Doe"); + + System.err.printf("Saving Customer [%s]%n", jonDoe); + + jonDoe = customerRepository.save(jonDoe); + + assertThat(jonDoe).isNotNull(); + assertThat(jonDoe.getId()).isEqualTo(1L); + assertThat(jonDoe.getName()).isEqualTo("Jon Doe"); + assertThat(customerRepository.count()).isEqualTo(1); + + System.err.println("Querying for Customer [SELECT * FROM /Customers WHERE name LIKE '%Doe']"); + + Customer queriedJonDoe = customerRepository.findByNameLike("%Doe"); + + assertThat(queriedJonDoe).isEqualTo(jonDoe); + + System.err.printf("Customer was [%s]%n", queriedJonDoe); + }; + } +} +// end::class[] diff --git a/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/model/Customer.java b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/model/Customer.java new file mode 100644 index 00000000..81bfdb5c --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/model/Customer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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. + */ +package example.app.crm.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.annotation.Region; + +/** + * An Abstract Data Type (ADT) modeling a {@literal Customer}. + * + * @author John Blum + * @see org.springframework.data.annotation.Id + * @see org.springframework.data.gemfire.mapping.annotation.Region + * @since 1.0.0 + */ +// tag::class[] +@Region("Customers") +@EqualsAndHashCode +@ToString(of = "name") +@RequiredArgsConstructor(staticName = "newCustomer") +public class Customer { + + @Id @NonNull @Getter + private Long id; + + @NonNull @Getter + private String name; + +} +// end::class[] diff --git a/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/repo/CustomerRepository.java b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/repo/CustomerRepository.java new file mode 100644 index 00000000..2a3772ef --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/java/example/app/crm/repo/CustomerRepository.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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. + */ +package example.app.crm.repo; + +import org.springframework.data.repository.CrudRepository; + +import example.app.crm.model.Customer; + +/** + * Data Access Object (DAO) used to perform basic CRUD and simple query operations on {@link Customer} objects + * store in Apache Geode + * + * @author John Blum + * @see java.lang.Long + * @see org.springframework.data.repository.CrudRepository + * @see example.app.crm.model.Customer + * @since 1.0.0 + */ +// tag::class[] +public interface CustomerRepository extends CrudRepository { + + Customer findByNameLike(String name); + +} +// end::class[] diff --git a/spring-geode-samples/boot/configuration/src/main/resources/application.properties b/spring-geode-samples/boot/configuration/src/main/resources/application.properties new file mode 100644 index 00000000..ad56c2bb --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.data.gemfire.cache.log-level=error diff --git a/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh new file mode 100644 index 00000000..e2e90560 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh @@ -0,0 +1,4 @@ +# Gfsh shell script to start a simple GemFire/Geode cluster + +start locator --name=LocatorOne --log-level=config +start server --name=ServerOne --log-level=config diff --git a/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.sh b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.sh new file mode 100644 index 00000000..901540e0 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +gfsh -e "run --file=/Users/jblum/pivdev/spring-boot-data-geode/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/start-simple-cluster.gfsh" diff --git a/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.gfsh b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.gfsh new file mode 100644 index 00000000..ca299780 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.gfsh @@ -0,0 +1,4 @@ +# Gfsh shell script to stop the cluster + +stop server --name=ServerOne +stop locator --name=LocatorOne diff --git a/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.sh b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.sh new file mode 100644 index 00000000..1611dafe --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +gfsh -e "run --file=/Users/jblum/pivdev/spring-boot-data-geode/spring-geode-samples/boot/configuration/src/main/resources/geode/bin/stop-cluster.gfsh" diff --git a/spring-geode-samples/boot/configuration/src/main/resources/java-util-logging.properties b/spring-geode-samples/boot/configuration/src/main/resources/java-util-logging.properties new file mode 100644 index 00000000..97056764 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/java-util-logging.properties @@ -0,0 +1,4 @@ +# java.util.logging (JUL) configuration + +org.apache=ERROR +org.springframework=ERROR diff --git a/spring-geode-samples/boot/configuration/src/main/resources/log4j2-test.xml b/spring-geode-samples/boot/configuration/src/main/resources/log4j2-test.xml new file mode 100644 index 00000000..a4dc8556 --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/log4j2-test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-geode-samples/boot/configuration/src/main/resources/logback.xml b/spring-geode-samples/boot/configuration/src/main/resources/logback.xml new file mode 100644 index 00000000..de99c52b --- /dev/null +++ b/spring-geode-samples/boot/configuration/src/main/resources/logback.xml @@ -0,0 +1,22 @@ + + + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + + + + +