1. Overview

In this tutorial, we’ll introduce the Spring Cloud Open Service Broker project and learn how to implement the Open Service Broker API.

First, we’ll dive into the specification of the Open Service Broker API. Then, we’ll learn how to use Spring Cloud Open Service Broker to build applications that implement the API specs.

Finally, we’ll explore what security mechanisms we can use to protect our service broker endpoints.

2. Open Service Broker API

The Open Service Broker API project allows us to quickly provide backing services to our applications running on cloud-native platforms such as Cloud Foundry and Kubernetes. In essence, the API specification describes a set of REST endpoints through which we can provision and connect to these services.

In particular, we can use service brokers within a cloud-native platform to:

  • Advertise a catalog of backing services
  • Provision service instances
  • Create and delete bindings between a backing service and a client application
  • Deprovision service instances

Spring Cloud Open Service Broker creates the base for an Open Service Broker API compliant implementation by providing the required web controllers, domain objects, and configuration. Additionally, we’ll need to come up with our business logic by implementing the appropriate service broker interfaces.

3. Auto Configuration

In order to use Spring Cloud Open Service Broker in our application, we need to add the associated starter artifact. We can use Maven Central to search for the latest version of the open-service-broker starter.

Besides the cloud starter, we’ll also need to include a Spring Boot web starter, and either Spring WebFlux or Spring MVC, to activate the auto-configuration:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-open-service-broker</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

The auto-configuration mechanism configures default implementations for most of the components that we need for a service broker. If we want, we can override the default behavior by providing our own implementation of the open-service-broker Spring-related beans.

3.1. Service Broker Endpoints Path Configuration

By default, the context path under which the service broker endpoints are registered is “/”.

If that’s not ideal and we want to change it, the most straightforward way is to set the property spring.cloud.openservicebroker.base-path in our application properties or YAML file:

spring:
  cloud:
    openservicebroker:
      base-path: /broker

In this case, to query the service broker endpoints, we’ll first need to prefix our requests with the /broker/ base-path.

4. A Service Broker Example

Let’s create a service broker application using the Spring Cloud Open Service Broker library and explore how the API works.

Through our example, we’ll use the service broker to provision and connect to a backing mail system. For simplicity, we’ll use a dummy mail API provided within our code examples.

4.1. Service Catalog

First, to control which services our service broker offers, we’ll need to define a service catalog. To quickly initialize the service catalog, in our example we’ll provide a Spring bean of type Catalog:

@Bean
public Catalog catalog() {
    Plan mailFreePlan = Plan.builder()
        .id("fd81196c-a414-43e5-bd81-1dbb082a3c55")
        .name("mail-free-plan")
        .description("Mail Service Free Plan")
        .free(true)
        .build();

    ServiceDefinition serviceDefinition = ServiceDefinition.builder()
        .id("b92c0ca7-c162-4029-b567-0d92978c0a97")
        .name("mail-service")
        .description("Mail Service")
        .bindable(true)
        .tags("mail", "service")
        .plans(mailFreePlan)
        .build();

    return Catalog.builder()
        .serviceDefinitions(serviceDefinition)
        .build();
}

As shown above, the service catalog contains metadata describing all available services that our service broker can offer. Moreover, the definition of a service is intentionally broad as it could refer to a database, a messaging queue, or, in our case, a mail service.

Another key point is that each service is built up from plans, which is another general term. In essence, each plan can offer different features and cost different amounts.

In the end, the service catalog is made available to the cloud-native platforms through the service broker /v2/catalog endpoint:

curl http://localhost:8080/broker/v2/catalog

{
    "services": [
        {
            "bindable": true,
            "description": "Mail Service",
            "id": "b92c0ca7-c162-4029-b567-0d92978c0a97",
            "name": "mail-service",
            "plans": [
                {
                    "description": "Mail Service Free Plan",
                    "free": true,
                    "id": "fd81196c-a414-43e5-bd81-1dbb082a3c55",
                    "name": "mail-free-plan"
                }
            ],
            "tags": [
                "mail",
                "service"
            ]
        }
    ]
}

Consequently, cloud-native platforms will query the service broker catalog endpoint from all service brokers to present an aggregated view of the service catalogs.

4.2. Service Provisioning

Once we start advertising services, we also need to provide the mechanisms in our broker to provision and manage the lifecycle of them within the cloud platform.

Furthermore, what provisioning represents varies from broker to broker. In some cases, provisioning may involve spinning up empty databases, creating a message broker, or simply providing an account to access external APIs.

In terms of terminology, the services created by a service broker will be referred to as service instances.

With Spring Cloud Open Service Broker, we can manage the service lifecycle by implementing the ServiceInstanceService interface. For example, to manage the service provisioning requests in our service broker we must provide an implementation for the createServiceInstance method:

@Override
public Mono<CreateServiceInstanceResponse> createServiceInstance(
    CreateServiceInstanceRequest request) {
    return Mono.just(request.getServiceInstanceId())
        .flatMap(instanceId -> Mono.just(CreateServiceInstanceResponse.builder())
            .flatMap(responseBuilder -> mailService.serviceInstanceExists(instanceId)
                .flatMap(exists -> {
                    if (exists) {
                        return mailService.getServiceInstance(instanceId)
                            .flatMap(mailServiceInstance -> Mono.just(responseBuilder
                                .instanceExisted(true)
                                .dashboardUrl(mailServiceInstance.getDashboardUrl())
                                .build()));
                    } else {
                        return mailService.createServiceInstance(
                            instanceId, request.getServiceDefinitionId(), request.getPlanId())
                            .flatMap(mailServiceInstance -> Mono.just(responseBuilder
                                .instanceExisted(false)
                                .dashboardUrl(mailServiceInstance.getDashboardUrl())
                                .build()));
                    }
                })));
}

Here, we allocate a new mail service in our internal mappings, if one with the same service instance id doesn’t exist, and provide a dashboard URL. We can consider the dashboard as a web management interface for our service instance.

Service provisioning is made available to the cloud-native platforms through the /v2/service_instances/{instance_id} endpoint:

curl -X PUT http://localhost:8080/broker/v2/service_instances/[email protected] 
  -H 'Content-Type: application/json' 
  -d '{
    "service_id": "b92c0ca7-c162-4029-b567-0d92978c0a97", 
    "plan_id": "fd81196c-a414-43e5-bd81-1dbb082a3c55"
  }' 

{"dashboard_url":"http://localhost:8080/mail-dashboard/[email protected]"}

In short, when we provision a new service, we need to pass the service_id and the plan_id advertised in the service catalog. Additionally, we need to provide a unique instance_id, which our service broker will use in future binding and de-provisioning requests.

4.3. Service Binding

After we provision a service, we’ll want our client application to start communicating with it. From a service broker’s perspective, this is called service binding.

Similar to service instances and plans, we should consider a binding as another flexible abstraction that we can use within our service broker. In general, we’ll provide service bindings to expose credentials used to access a service instance.

In our example, if the advertised service has the bindable field set to true, our service broker must provide an implementation of the ServiceInstanceBindingService interface. Otherwise, the cloud platforms won’t call the service binding methods from our service broker.

Let’s handle the service binding creation requests by providing an implementation to the createServiceInstanceBinding method:

@Override
public Mono<CreateServiceInstanceBindingResponse> createServiceInstanceBinding(
    CreateServiceInstanceBindingRequest request) {
    return Mono.just(CreateServiceInstanceAppBindingResponse.builder())
        .flatMap(responseBuilder -> mailService.serviceBindingExists(
            request.getServiceInstanceId(), request.getBindingId())
            .flatMap(exists -> {
                if (exists) {
                    return mailService.getServiceBinding(
                        request.getServiceInstanceId(), request.getBindingId())
                        .flatMap(serviceBinding -> Mono.just(responseBuilder
                            .bindingExisted(true)
                            .credentials(serviceBinding.getCredentials())
                            .build()));
                } else {
                    return mailService.createServiceBinding(
                        request.getServiceInstanceId(), request.getBindingId())
                        .switchIfEmpty(Mono.error(
                            new ServiceInstanceDoesNotExistException(
                                request.getServiceInstanceId())))
                        .flatMap(mailServiceBinding -> Mono.just(responseBuilder
                            .bindingExisted(false)
                            .credentials(mailServiceBinding.getCredentials())
                            .build()));
                }
            }));
}

The above code generates a unique set of credentials – username, password, and a URI – through which we can connect and authenticate to our new mail service instance.

Spring Cloud Open Service Broker framework exposes service binding operations through the /v2/service_instances/{instance_id}/service_bindings/{binding_id} endpoint:

curl -X PUT 
  http://localhost:8080/broker/v2/service_instances/[email protected]/service_bindings/admin 
  -H 'Content-Type: application/json' 
  -d '{ 
    "service_id": "b92c0ca7-c162-4029-b567-0d92978c0a97", 
    "plan_id": "fd81196c-a414-43e5-bd81-1dbb082a3c55" 
  }'

{
    "credentials": {
        "password": "bea65996-3871-4319-a6bb-a75df06c2a4d",
        "uri": "http://localhost:8080/mail-system/[email protected]",
        "username": "admin"
    }
}

Just like service instance provisioning, we are using the service_id and the plan_id advertised in the service catalog within our binding request. Furthermore, we also pass a unique binding_id, which the broker uses as a username for our credentials set.

5. Service Broker API Security

Usually, when service brokers and cloud-native platforms communicate with each other, an authentication mechanism is required.

Unfortunately, the Open Service Broker API specification doesn’t currently cover the authentication part for the service broker endpoints. Because of this, also the Spring Cloud Open Service Broker library doesn’t implement any security configuration.

Luckily, if we need to protect our service broker endpoints, we could quickly use Spring Security to put in place Basic authentication or an OAuth 2.0 mechanism. In this case, we should authenticate all service broker requests using our chosen authentication mechanism and return a 401 Unauthorized response when the authentication fails.

6. Conclusion

In this article, we explored the Spring Cloud Open Service Broker project.

First, we learned what the Open Service Broker API is, and how it allows us to provision and connect to backing services. Subsequently, we saw how to quickly build a Service Broker API compliant project using the Spring Cloud Open Service Broker library.

Finally, we discussed how we can secure our service broker endpoints with Spring Security.

As always, the source code for this tutorial is available over on GitHub.