diff --git a/spring-geode-docs/src/docs/asciidoc/_includes/geode-api-ext.adoc b/spring-geode-docs/src/docs/asciidoc/_includes/geode-api-ext.adoc new file mode 100644 index 00000000..cc486d50 --- /dev/null +++ b/spring-geode-docs/src/docs/asciidoc/_includes/geode-api-ext.adoc @@ -0,0 +1,296 @@ +[[geode-api-extensions]] +== Apache Geode API Extensions +:gemfire-name: Pivotal GemFire +:geode-name: Apache Geode +:images-dir: ./images + + +The Spring Boot for {geode-name} (SBDG) project includes the `org.springframework.geode:apache-geode-extensions` module +to make working with the Apache Geode {apache-geode-javadoc}[APIs] tolerable and useful. While this module is relatively +new, it contains several useful API extensions already. We will continue to add extensions to this module as both SBDG +and users' needs dictate. + +{geode-name}'s {apache-geode-javadoc}[API] is quite convoluted with many design problems: + +1. Non-intuitive, complex interfaces that often contradict industry standard terms. +(e.g. `Cache` vs. {apache-geode-javadoc}/org/apache/geode/cache/Region.html[`Region`]). +2. APIs with an excessive footprint: no sensible https://en.wikipedia.org/wiki/Abstract_data_type[ADTs] resulting in +too many overloaded methods with loaded method signatures (e.g. {apache-geode-javadoc}/org/apache/geode/cache/Region.html[`Region`]). +3. Useful functionality hidden behind so called "internal" APIs that ought to be public. +4. APIs that are closed for modification yet offer no option for extension violating the +https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle[_Open/Closed Principle_]. +5. Utility/Helper classes containing functionality that ought to be part of the behavior exhibited by the types on which +the Utility/Helper classes operate (e.g. {apache-geode-javadoc}/org/apache/geode/cache/partition/PartitionRegionHelper.html[`PartitionRegionHelper`]). +6. Incorrect use of _Checked_ `Exceptions` +(e.g. {apache-geode-javadoc}/org/apache/geode/cache/IncompatibleVersionException.html[`IncompatibleVersionException`]). +7. Lingering deprecations. +8. ... + +All of this plus much more makes using the {geode-name} API correctly difficult and confusing at times, especially +without prior knowledge or experience. Users very often get this wrong and it is the main reason why Spring's APIs for +{geode-name} are so invaluable; they can help you do the right thing! + +Consider this, the one and only cache implementation (`GemFireCacheImpl`) implements both the `ClientCache` and `Cache` +interfaces. A `ClientCache` instance is created by client applications to access/persist data in a {geode-name} cluster. +On the contrary, a "peer" `Cache` instance is created by server-side applications serving as peer members of the +{geode-name} cluster (a.k.a. distributed system) to manage data. Both incarnations result in an instance of +`GemFireCacheImpl`, yet a cache cannot be both a client and a peer. But, you would never know this by introspecting +the cache instance. + +The {apache-geode-javadoc}/org/apache/geode/Delta.html[`Delta`] interface, {apache-geode-javadoc}/org/apache/geode/Delta.html#hasDelta--[`hasDelta()`] +method is another point of confusion. If there is no delta, why send the object in its entirety? Presumably there are no +changes. Of course, there is a reason but it is not immediately apparent why! + +Spring in general, and SBDG in particular, shield users from design problems as well as changes in {geode-name}'s APIs +that could adversely affect your applications when integrating with {geode-name}. Spring's APIs provide a layer of +indirection along with enhanced capabilities (e.g. Exception translation). + +NOTE: Spring Data for {geode-name} (SDG) also {spring-data-geode-docs-html}/#apis[offers] some relief when working with +{geode-name}'s APIs. + +[[geode-api-extensions-cacheresolver]] +=== `SimpleCacheResolver` + +In some cases, it is necessary to acquire a reference to the cache instance in your application components at runtime. +For instance, you might want to create a temporary `Region` on the fly in order to aggregate data for analysis. + +Typically, you already know the type of cache your application is using since you must declare your application to be +either a client (i.e. `ClientCache`) in the {apache-geode-docs}/topologies_and_comm/cs_configuration/chapter_overview.html[client/server topology], +or a {apache-geode-docs}/topologies_and_comm/p2p_configuration/chapter_overview.html[peer member/node] in the cluster +(i.e. `Cache`) on startup. This is expressed in configuration when creating the cache instance required to interact with +the {geode-name} data management system. In most cases, your application will be a client. SBDG makes the decision easy +since it _auto-configures_ a `ClientCache` instance, <>. + +In a Spring context, the cache instance created by the framework is a managed bean in the Spring container. As such, +it is a simple matter to inject a reference to the _Singleton_ cache bean into any other managed application component. + +Auto-wiring a Cache Reference using Dependency Injection (DI) +[source,java] +---- +@Service +class CacheMonitoringService { + + @Autowired + ClientCache clientCache; + + // use the clientCache object reference to monitor the cache as necessary + +} +---- + +However, in cases where the application component or class is not managed by Spring, and you need a reference to the +cache instance at runtime, then SBDG provides the abstract `org.springframework.geode.cache.SimpleCacheResolver` class +(see {spring-boot-data-geode-javadoc}/org/springframework/geode/cache/SimpleCacheResolver.html[Javadoc]). + +.`SimpleCacheResolver` API +[source, java ] +---- +package org.springframework.geode.cache; + +abstract class SimpleCacheResolver { + + T require() { } + + Optional resolve() { } + + Optional resolveClientCache() { } + + Optional resolvePeerCache() { } + +} +---- + +`SimpleCacheResolver` adheres to https://en.wikipedia.org/wiki/SOLID[SOLID OO Principles]. This class is abstract and +extensible so that users could change the algorithm used to resolve client or peer cache instances as well as mock the +methods for use in unit tests. + +Additionally, each method is precise. For example, `resolveClientCache()` will only resolve a reference to a cache if +the cache instance is a "client"! If a cache exists but is a "peer" instance, then `resolveClientCache()` returns +`Optional.EMPTY`. + +`require()` returns a non-`Optional` reference to a cache instance throwing an `IllegalStateException` if a cache +is not present. + +[[geode-api-extensions-cacheutils]] +=== `CacheUtils` + +Under-the-hood, the `SimpleCacheResolver` delegates some of its functions to the +{spring-boot-data-geode-javadoc}/org/springframework/geode/util/CacheUtils.html[`org.springframework.geode.util.CacheUtils`] +abstract utility class, which provides additional, convenient capabilities when working with a cache. + +While there are utility methods to determine whether a cache instance (i.e. `GemFireCache`) or _Region_ is a client +or a peer, 1 of the more useful functions is to extract all the values from a _Region_. + +To extract all the values stored in a _Region_ call `CacheUtils.collectValues(:Region)`. This method returns a +`Collection` containing all the values stored in the given _Region_. + +The `collectValues(:Region)` is smart and knows how to handle either client or peer _Regions_. This distinction is +important since client `PROXY` _Regions_ store no values. + +WARNING: Caution is advised when getting all values from a _Region_. While getting filtered reference values from a +non-transactional, reference-only data [`REPLICATE`] _Region_ is quite useful, getting values from a transactional, +[`PARTITION`] _Region_ can prove quite detrimental, especially in production. Getting all values from a _Region_ is +also quite useful in testing. + +[[geode-api-extensions-membership]] +=== `MembershipListenerAdapter` & `MembershipEvent` + +Another useful API hidden by {geode-name} is the membership events and listener interface. This API is particularly +useful on the server-side when your Spring Boot application is serving as a peer member/node of the {geode-name} +distributed system. + +When a peer member gets disconnected from the cluster, perhaps due to a network failure, the member is forcibly removed +from the distributed system. This node immediately enters a reconnecting state, trying to establish a connection back to +the cluster. Once reconnected, the peer member must rebuild all cache objects (i.e. `Cache`, `Regions`, `Indexes`, +`DiskStores`, etc). All old/previous cache objects are invalid and their references stale. + +As you can imagine, in a Spring context this is particularly problematic since most {geode-name} objects are _Singleton_ +beans declared in and managed by the Spring container. Those beans maybe, and in many cases are, injected into framework +and application components. For instance, `Regions` are injected into SDG's `GemfireTemplate`, Spring Data _Repositories_ +and possibly application-specific _Data Access Objects_ (https://en.wikipedia.org/wiki/Data_access_object[DAO]). + +If references to those cache objects become stale on a forced disconnect event, then there is no way to auto-wire fresh +object references into the dependent application or framework components when the peer member is reconnected, not unless +the Spring `ApplicationContext` is "refreshed". In fact, there is no way to even know that this event has occurred since +the {geode-name} `MembershipListener` API and corresponding events are "internal". + +NOTE: We have explored the idea of creating proxies for all types of cache objects (i.e. `Cache`, `Regions`, `Indexes`, +`DiskStores`, AEQs, `GatewayReceivers`, `GatewaySenders`, etc) used by Spring. The proxies would know how to obtain a +"fresh" reference on a reconnect event. However, this turns out to be more problematic than it is worth. It is simply +easier to "refresh" the Spring `ApplicationContext`, although no less cheap. Neither way is ideal. See +https://jira.spring.io/browse/SGF-921[SGF-921] and https://jira.spring.io/browse/SGF-227[SGF-227] for further details. + +In the situation where membership events are useful to the Spring Boot application, SBDG provides the following +{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/package-frame.html[API]: + +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipListenerAdapter.html[`MembershipListenerAdapter`] +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.html[`MembershipEvent`] + +The abstract `MembershipListenerAdapter` class implements {geode-name}'s clumsy +`org.apache.geode.distributed.internal.MembershipListener` interface, simplifying the event handler method signatures +by using an appropriate `MembershipEvent` type to encapsulate the actors in the event. + +The abstract `MembershipEvent` class is further subclassed to represent the specific membership event types that occur +within the {geode-name} system: + +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberDepartedEvent.html[`MemberDepartedEvent`] +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberJoinedEvent.html[`MemberJoinedEvent`] +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/MemberSuspectEvent.html[`MemberSuspectEvent`] +* {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/support/QuorumLostEvent.html[`QuorumLostEvent`] + +The API can be depicted in this UML diagram: + +image::{images-dir}/membership-api-uml.png[] + +The membership event type is further categorized with an appropriate enumerated value, +{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.Type.html[`MembershipEvent.Type`], +as a property of the `MembershipEvent` itself (see {spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/MembershipEvent.html#getType--[`getType()`]). + +The type hierarchy is useful in `instanceof` expressions while the `Enum` is useful in `switch` statements. + +You can see 1 particular implementation of the `MembershipListenerAdapter` with the +{spring-boot-data-geode-javadoc}/org/springframework/geode/distributed/event/ApplicationContextMembershipListener.html[`ApplicationContextMembershipListener`] class, +which does exactly as we described above, handling forced-disconnect/auto-reconnect membership events inside a +Spring context. + +[[geode-api-extensions-pdx]] +=== PDX + +{geode-name}'s PDX serialization framework is yet another API that falls short. + +For instance, there is no easy or direct way to serialize an object as PDX bytes. It is also not possible to modify an +existing `PdxInstance` by adding or removing a field. In this case, you must create a new `PdxInstance`, but +unfortunately, the {geode-name} API offers no assistance when copying from an existing `PdxInstance`. + +In such cases, SBDG provides the {spring-boot-data-geode-javadoc}/org/springframework/geode/pdx/PdxInstanceBuilder.html[`PdxInstanceBuilder`] +class, appropriately named after the https://en.wikipedia.org/wiki/Builder_pattern[_Builder Software Design Pattern_]. +The `PdxInstanceBuilder` also offers a fluent API style for constructing `PdxInstances`. + +.`PdxInstanceBuilder` API +[source,java] +---- +class PdxInstanceBuilder { + + PdxInstanceFactory copy(PdxInstance pdx); + + Factory from(Object target); + +} +---- + +For example, you could serialize an application domain object as PDX bytes with the following code: + +.Object to PDX +[source,java] +---- +@Component +class CustomerSerializer { + + PdxInstance serialize(Customer customer) { + + return PdxInstanceBuilder.create() + .from(customer) + .create(); + } +} +---- + +You could then modify the `PdxInstance` by copying from the original: + +.Copy `PdxInstance` +[source,java] +---- +@Component +class CustomerDecorator { + + @Autowired + CustomerSerializer serializer; + + PdxIntance decorate(Customer customer) { + + PdxInstance pdxCustomer = serializer.serialize(customer); + + return PdxInstanceBuilder.create() + .copy(pdxCustomer) + .writeBoolean("vip", isImportant(cutomer)) + .create(); + } +} +---- + +SBDG also provides the {spring-boot-data-geode-javadoc}/org/springframework/geode/pdx/PdxInstanceWrapper.html[`PdxInstanceWrapper`] +class, which wraps an existing `PdxInstance` in order to provide more control during the conversion from PDX to JSON +and back into a POJO. Specifically, the wrapper gives users more control of the configuration of Jackson's `ObjectMapper`. + +The `ObjectMapper` constructed by {geode-name}'s own `PdxInstance` implementation (`PdxInstanceImpl`) is not +configurable nor was it configured correctly. And unfortunately, since `PdxInstance` is not extensible, the `getObject()` +method fails miserably when converting the JSON generated from PDX back into a POJO for any practical application domain +model type. + +.Wrapping an existing `PdxInstance` +[source,java] +---- +PdxInstanceWrapper wrapper = PdxInstanceWrapper.from(pdxInstance); +---- + +For all operations on `PdxInstance` except `getObject()`, the wrapper delegates to the underlying `PdxInstance` +implementation of the called method. + +In addition to the decorated `getObject()` method, the `PdxInstanceWrapper` provides a thorough implementation of the +`toString()` method. The state of the `PdxInstance` is output in a JSON-like String. + +WARNING: It is not currently possible to implement the `PdxInstance` interface and store instances of this type as a +value in a _Region_. {geode-name} naively assumes that all `PdxInstance` objects are an implementation created by +{geode-name} itself (i.e. `PdxInstanceImpl`), which has a tight coupling to the PDX type registry. + +[[geode-api-extensions-security]] +=== Security + +For testing purposes, SBDG provides a test implementation of {geode-name}'s {apache-geode-javadoc}/org/apache/geode/security/SecurityManager.html[`SecurityManager`] +interface that simply expects the password to match the username (case-sensitive) when authenticating. + +By default, all operations are authorized. + +To match the expectations of SBDG's `TestSecurityManager`, SBDG additionally provides a test implementation of +{geode-name}'s {apache-geode-javadoc}/org/apache/geode/security/AuthInitialize.html[`AuthInitialize`] interface that +supplies matching credentials for both the username and password. diff --git a/spring-geode-docs/src/docs/asciidoc/images/membership-api-uml.png b/spring-geode-docs/src/docs/asciidoc/images/membership-api-uml.png new file mode 100644 index 00000000..70e52029 Binary files /dev/null and b/spring-geode-docs/src/docs/asciidoc/images/membership-api-uml.png differ diff --git a/spring-geode-docs/src/docs/asciidoc/index.adoc b/spring-geode-docs/src/docs/asciidoc/index.adoc index 235d1b59..e54f2c71 100644 --- a/spring-geode-docs/src/docs/asciidoc/index.adoc +++ b/spring-geode-docs/src/docs/asciidoc/index.adoc @@ -29,7 +29,7 @@ John Blum :spring-boot-docs-html: {spring-boot-docs}/html :spring-boot-javadoc: https://docs.spring.io/spring-boot/docs/current/api :spring-boot-website: https://spring.io/projects/spring-boot -:spring-boot-data-geode-javadoc: https://docs.spring.io/autorepo/docs/spring-boot-data-geode-build/current/api/ +:spring-boot-data-geode-javadoc: https://docs.spring.io/spring-boot-data-geode-build/current/api/ :spring-data-commons-docs: https://docs.spring.io/spring-data/commons/docs/current/reference :spring-data-commons-docs-html: {spring-data-commons-docs}/html :spring-data-commons-javadoc: https://docs.spring.io/spring-data/commons/docs/current/api @@ -237,8 +237,9 @@ include::{include-dir}/functions.adoc[] include::{include-dir}/continuous-query.adoc[] include::{include-dir}/data.adoc[] include::{include-dir}/data-serialization.adoc[] -include::{include-dir}/security.adoc[] include::{include-dir}/logging.adoc[] +include::{include-dir}/security.adoc[] +include::{include-dir}/geode-api-ext.adoc[] include::{include-dir}/actuator.adoc[] include::{include-dir}/session.adoc[] include::{include-dir}/cloudfoundry.adoc[]