From e5802e6602be76974bdb392eecb09b86aedbd4c4 Mon Sep 17 00:00:00 2001 From: Roy Clarkson Date: Thu, 30 Jan 2020 20:23:52 -0500 Subject: [PATCH] Document example implementation of ServiceInstanceStateRepository - Add example implementation in reference docs - Include warning in javadoc and reference about using in mem implementation in production Resolves #257. Resolves #314 --- ...ServiceInstanceBindingStateRepository.java | 6 ++ ...nMemoryServiceInstanceStateRepository.java | 5 ++ spring-cloud-app-broker-docs/build.gradle | 7 +- .../src/docs/asciidoc/index.adoc | 1 + .../src/docs/asciidoc/service-bindings.adoc | 30 ++++++++ .../src/docs/asciidoc/service-instances.adoc | 30 ++++++++ ...ServiceInstanceBindingStateRepository.java | 55 ++++++++++++++ ...ExampleServiceInstanceStateRepository.java | 48 +++++++++++++ .../example/appbroker/ServiceInstance.java | 59 +++++++++++++++ .../appbroker/ServiceInstanceBinding.java | 71 +++++++++++++++++++ ...iceInstanceBindingStateCrudRepository.java | 26 +++++++ .../ServiceInstanceStateCrudRepository.java | 17 +++++ 12 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceBindingStateRepository.java create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceStateRepository.java create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstance.java create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBinding.java create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBindingStateCrudRepository.java create mode 100644 spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceStateCrudRepository.java diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceBindingStateRepository.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceBindingStateRepository.java index 67f7eaf..38e806d 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceBindingStateRepository.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceBindingStateRepository.java @@ -26,6 +26,12 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.servicebroker.model.instance.OperationState; +/** + * Default implementation of {@link ServiceInstanceBindingStateRepository} meant for demonstration and testing purposes + * only. + *

+ * WARNING: This implementation is not intended for production applications! + */ public class InMemoryServiceInstanceBindingStateRepository implements ServiceInstanceBindingStateRepository { private final Map states = new ConcurrentSkipListMap<>(); diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceStateRepository.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceStateRepository.java index 3bdf0f1..f43504e 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceStateRepository.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceStateRepository.java @@ -25,6 +25,11 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.servicebroker.model.instance.OperationState; +/** + * Default implementation of {@link ServiceInstanceStateRepository} meant for demonstration and testing purposes only. + *

+ * WARNING: This implementation is not intended for production applications! + */ public class InMemoryServiceInstanceStateRepository implements ServiceInstanceStateRepository { private final Map states = new ConcurrentSkipListMap<>(); diff --git a/spring-cloud-app-broker-docs/build.gradle b/spring-cloud-app-broker-docs/build.gradle index ef8ce97..d2efd67 100644 --- a/spring-cloud-app-broker-docs/build.gradle +++ b/spring-cloud-app-broker-docs/build.gradle @@ -20,10 +20,6 @@ plugins { description = "Spring Cloud App Broker Documentation" -repositories { - mavenCentral() -} - configurations { docs } @@ -42,6 +38,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-tomcat") implementation("io.projectreactor:reactor-core") + implementation("org.springframework.boot.experimental:spring-boot-starter-r2dbc:0.1.0.M3") + implementation("org.springframework.boot.experimental:spring-boot-starter-data-r2dbc:0.1.0.M3") + implementation("io.r2dbc:r2dbc-h2:0.8.0.RELEASE") docs("io.spring.docresources:spring-doc-resources:0.1.0.RELEASE@zip") } diff --git a/spring-cloud-app-broker-docs/src/docs/asciidoc/index.adoc b/spring-cloud-app-broker-docs/src/docs/asciidoc/index.adoc index 15879d6..f0fda99 100644 --- a/spring-cloud-app-broker-docs/src/docs/asciidoc/index.adoc +++ b/spring-cloud-app-broker-docs/src/docs/asciidoc/index.adoc @@ -6,6 +6,7 @@ :toclevels: 3 :sectlinks: +:examples-dir: ../../src/test/java/com/example/appbroker/ :sapbr: https://cloud.spring.io/spring-cloud-app-broker/ :sapbr-href: {sapbr}[Spring Cloud App Broker] :sapbr-api: https://docs.spring.io/spring-cloud-app-broker/docs/{project-version}/api/ diff --git a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-bindings.adoc b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-bindings.adoc index b8ddfb4..233d0cb 100644 --- a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-bindings.adoc +++ b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-bindings.adoc @@ -1,3 +1,4 @@ +:examples-dir: ../../src/test/java/com/example/appbroker/ [[service-bindings]] == Service Bindings @@ -12,3 +13,32 @@ The service broker application can implement the {sapbr-api}/org/springframework === Deleting a Service Binding The service broker application can implement the {sapbr-api}/org/springframework/cloud/appbroker/service/DeleteServiceInstanceBindingWorkflow.html[`DeleteServiceInstanceBindingWorkflow`] interface. Alternatively, the service broker application can implement the `ServiceInstanceBindingService` interface provided by Spring Cloud Open Service Broker. See {scosb-docs}/#service-bindings[Service Bindings] in the {scosb-docs}/[Spring Cloud Open Service Broker documentation]. + +=== Persisting Service Instance Binding State + +Spring Cloud App Broker provides the {sapbr-api}/org/springframework/cloud/appbroker/state/ServiceInstanceBindingStateRepository.html[`ServiceInstanceBindingStateRepository`] interface for persisting service instance binding state. The default implementation is {sapbr-api}/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceBindingStateRepository.html[`InMemoryServiceInstanceBindingStateRepository`], which uses an in memory `Map` to save state and offers an easy getting started experience. In order to use a proper database for persisting state, implement `ServiceInstanceBindingStateRepository` in your application. + +WARNING: The `InMemoryServiceInstanceBindingStateRepository` is provided for demonstration and testing purposes only. It is not suitable for production applications! + +==== Example Implementation + +The following example shows a service instance binding state repository implementation: + +[source,java,%autofit] +---- +include::{examples-dir}/ExampleServiceInstanceBindingStateRepository.java[] +---- + +One option for persisting service instance binding state is to use a Spring Data `CrudRepository`. The following example shows a `ReactiveCrudRepository` implementation: + +[source,java,%autofit] +---- +include::{examples-dir}/ServiceInstanceBindingStateCrudRepository.java[] +---- + +A model object is necessary for persisting data with a `CrudRepository`. The following example shows a `ServiceInstanceBinding` model: + +[source,java,%autofit] +---- +include::{examples-dir}/ServiceInstanceBinding.java[] +---- diff --git a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc index 2376985..0734ef0 100644 --- a/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc +++ b/spring-cloud-app-broker-docs/src/docs/asciidoc/service-instances.adoc @@ -1,3 +1,4 @@ +:examples-dir: ../../src/test/java/com/example/appbroker/ [[service-instances]] == Service Instances @@ -340,3 +341,32 @@ CAUTION: Modifying certain properties, such as disk and memory, when updating an === Deleting a Service Instance Spring Cloud App Broker provides the {sapbr-api}/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.html[`AppDeploymentDeleteServiceInstanceWorkflow`] workflow, which handles deleting the configured backing applications and services as illustrated in the previous sections. The service broker application can implement the {sapbr-api}/org/springframework/cloud/appbroker/service/DeleteServiceInstanceWorkflow.html[`DeleteServiceInstanceWorkflow`] interface to further modify the deployment. Multiple workflows may be annotated with `@Order` so as to process the workflows in a specific order. Alternatively, the service broker application can implement the `ServiceInstanceService` interface provided by Spring Cloud Open Service Broker. See {scosb-docs}/#service-instances[Service Instances] in the {scosb-docs}/[Spring Cloud Open Service Broker documentation]. + +=== Persisting Service Instance State + +Spring Cloud App Broker provides the {sapbr-api}/org/springframework/cloud/appbroker/state/ServiceInstanceStateRepository.html[`ServiceInstanceStateRepository`] interface for persisting service instance state. The default implementation is {sapbr-api}/org/springframework/cloud/appbroker/state/InMemoryServiceInstanceStateRepository.html[`InMemoryServiceInstanceStateRepository`], which uses an in memory `Map` to save state and offers an easy getting started experience. In order to use a proper database for persisting state, implement `ServiceInstanceStateRepository` in your application. + +WARNING: The `InMemoryServiceInstanceStateRepository` is provided for demonstration and testing purposes only. It is not suitable for production applications! + +==== Example Implementation + +The following example shows a service instance state repository implementation: + +[source,java,%autofit] +---- +include::{examples-dir}/ExampleServiceInstanceStateRepository.java[] +---- + +One option for persisting service instance state is to use a Spring Data `CrudRepository`. The following example shows a `ReactiveCrudRepository` implementation: + +[source,java,%autofit] +---- +include::{examples-dir}/ServiceInstanceStateCrudRepository.java[] +---- + +A model object is necessary for persisting data with a `CrudRepository`. The following example shows a `ServiceInstance` model: + +[source,java,%autofit] +---- +include::{examples-dir}/ServiceInstance.java[] +---- diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceBindingStateRepository.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceBindingStateRepository.java new file mode 100644 index 0000000..46495c2 --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceBindingStateRepository.java @@ -0,0 +1,55 @@ +package com.example.appbroker; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.appbroker.state.ServiceInstanceBindingStateRepository; +import org.springframework.cloud.appbroker.state.ServiceInstanceState; +import org.springframework.cloud.servicebroker.model.instance.OperationState; + +class ExampleServiceInstanceBindingStateRepository implements ServiceInstanceBindingStateRepository { + + private final ServiceInstanceBindingStateCrudRepository serviceInstanceBindingStateCrudRepository; + + ExampleServiceInstanceBindingStateRepository( + ServiceInstanceBindingStateCrudRepository serviceInstanceBindingStateCrudRepository) { + this.serviceInstanceBindingStateCrudRepository = serviceInstanceBindingStateCrudRepository; + } + + @Override + public Mono saveState(String serviceInstanceId, String bindingId, OperationState state, + String description) { + return serviceInstanceBindingStateCrudRepository + .findByServiceInstanceIdAndBindingId(serviceInstanceId, bindingId) + .switchIfEmpty(Mono.just(new ServiceInstanceBinding())) + .flatMap(binding -> { + binding.setServiceInstanceId(serviceInstanceId); + binding.setBindingId(bindingId); + binding.setOperationState(state); + binding.setDescription(description); + return Mono.just(binding); + }) + .flatMap(serviceInstanceBindingStateCrudRepository::save) + .map(ExampleServiceInstanceBindingStateRepository::toServiceInstanceState); + } + + @Override + public Mono getState(String serviceInstanceId, String bindingId) { + return serviceInstanceBindingStateCrudRepository + .findByServiceInstanceIdAndBindingId(serviceInstanceId, bindingId) + .switchIfEmpty(Mono.error(new IllegalArgumentException( + "Unknown binding: serviceInstanceId=" + serviceInstanceId + ", bindingId=" + bindingId))) + .map(ExampleServiceInstanceBindingStateRepository::toServiceInstanceState); + } + + @Override + public Mono removeState(String serviceInstanceId, String bindingId) { + return getState(serviceInstanceId, bindingId) + .doOnNext(serviceInstanceState -> serviceInstanceBindingStateCrudRepository + .deleteByServiceInstanceIdAndBindingId(serviceInstanceId, bindingId)); + } + + private static ServiceInstanceState toServiceInstanceState(ServiceInstanceBinding binding) { + return new ServiceInstanceState(binding.getOperationState(), binding.getDescription(), null); + } + +} diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceStateRepository.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceStateRepository.java new file mode 100644 index 0000000..6f45577 --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ExampleServiceInstanceStateRepository.java @@ -0,0 +1,48 @@ +package com.example.appbroker; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.appbroker.state.ServiceInstanceState; +import org.springframework.cloud.appbroker.state.ServiceInstanceStateRepository; +import org.springframework.cloud.servicebroker.model.instance.OperationState; + +class ExampleServiceInstanceStateRepository implements ServiceInstanceStateRepository { + + private final ServiceInstanceStateCrudRepository serviceInstanceStateCrudRepository; + + ExampleServiceInstanceStateRepository(ServiceInstanceStateCrudRepository serviceInstanceStateCrudRepository) { + this.serviceInstanceStateCrudRepository = serviceInstanceStateCrudRepository; + } + + @Override + public Mono saveState(String serviceInstanceId, OperationState state, String description) { + return serviceInstanceStateCrudRepository.findByServiceInstanceId(serviceInstanceId) + .switchIfEmpty(Mono.just(new ServiceInstance())) + .flatMap(serviceInstance -> { + serviceInstance.setServiceInstanceId(serviceInstanceId); + serviceInstance.setOperationState(state); + serviceInstance.setDescription(description); + return Mono.just(serviceInstance); + }) + .flatMap(serviceInstanceStateCrudRepository::save) + .map(ExampleServiceInstanceStateRepository::toServiceInstanceState); + } + + @Override + public Mono getState(String serviceInstanceId) { + return serviceInstanceStateCrudRepository.findByServiceInstanceId(serviceInstanceId) + .switchIfEmpty(Mono.error(new IllegalArgumentException("Unknown service instance ID " + serviceInstanceId))) + .map(ExampleServiceInstanceStateRepository::toServiceInstanceState); + } + + @Override + public Mono removeState(String serviceInstanceId) { + return getState(serviceInstanceId) + .doOnNext(serviceInstanceState -> serviceInstanceStateCrudRepository.deleteByServiceInstanceId(serviceInstanceId)); + } + + private static ServiceInstanceState toServiceInstanceState(ServiceInstance serviceInstance) { + return new ServiceInstanceState(serviceInstance.getOperationState(), serviceInstance.getDescription(), null); + } + +} diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstance.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstance.java new file mode 100644 index 0000000..46fa381 --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstance.java @@ -0,0 +1,59 @@ +package com.example.appbroker; + +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.data.annotation.Id; + +class ServiceInstance { + + @Id + private Long id; + + private String serviceInstanceId; + + private String description; + + private OperationState operationState; + + public ServiceInstance() { + + } + + public ServiceInstance(String serviceInstanceId, String description, OperationState operationState) { + this.serviceInstanceId = serviceInstanceId; + this.description = description; + this.operationState = operationState; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getServiceInstanceId() { + return serviceInstanceId; + } + + public void setServiceInstanceId(String serviceInstanceId) { + this.serviceInstanceId = serviceInstanceId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OperationState getOperationState() { + return operationState; + } + + public void setOperationState(OperationState operationState) { + this.operationState = operationState; + } + +} diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBinding.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBinding.java new file mode 100644 index 0000000..1dc0aeb --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBinding.java @@ -0,0 +1,71 @@ +package com.example.appbroker; + +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.data.annotation.Id; + +class ServiceInstanceBinding { + + @Id + private Long id; + + private String bindingId; + + private String serviceInstanceId; + + private String description; + + private OperationState operationState; + + public ServiceInstanceBinding() { + + } + + public ServiceInstanceBinding(String bindingId, String serviceInstanceId, String description, + OperationState operationState) { + this.bindingId = bindingId; + this.serviceInstanceId = serviceInstanceId; + this.description = description; + this.operationState = operationState; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getBindingId() { + return bindingId; + } + + public void setBindingId(String bindingId) { + this.bindingId = bindingId; + } + + public String getServiceInstanceId() { + return serviceInstanceId; + } + + public void setServiceInstanceId(String serviceInstanceId) { + this.serviceInstanceId = serviceInstanceId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OperationState getOperationState() { + return operationState; + } + + public void setOperationState(OperationState operationState) { + this.operationState = operationState; + } + +} diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBindingStateCrudRepository.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBindingStateCrudRepository.java new file mode 100644 index 0000000..36e02e2 --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceBindingStateCrudRepository.java @@ -0,0 +1,26 @@ +package com.example.appbroker; + +import reactor.core.publisher.Mono; + +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +interface ServiceInstanceBindingStateCrudRepository extends ReactiveCrudRepository { + + @Query("select * from service_instance_binding " + + "where service_instance_id = :service_instance_id " + + "and binding_id = :binding_id") + Mono findByServiceInstanceIdAndBindingId( + @Param("service_instance_id") String serviceInstanceId, + @Param("binding_id") String bindingId); + + + @Query("delete from service_instance_binding " + + "where service_instance_id = :service_instance_id " + + "and binding_id = :binding_id") + Mono deleteByServiceInstanceIdAndBindingId( + @Param("service_instance_id") String serviceInstanceId, + @Param("binding_id") String bindingId); + +} diff --git a/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceStateCrudRepository.java b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceStateCrudRepository.java new file mode 100644 index 0000000..c1cc4a5 --- /dev/null +++ b/spring-cloud-app-broker-docs/src/test/java/com/example/appbroker/ServiceInstanceStateCrudRepository.java @@ -0,0 +1,17 @@ +package com.example.appbroker; + +import reactor.core.publisher.Mono; + +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +interface ServiceInstanceStateCrudRepository extends ReactiveCrudRepository { + + @Query("select * from service_instance where service_instance_id = :service_instance_id") + Mono findByServiceInstanceId(@Param("service_instance_id") String serviceInstanceId); + + @Query("delete from service_instance where service_instance_id = :service_instance_id") + Mono deleteByServiceInstanceId(@Param("service_instance_id") String serviceInstanceId); + +}