Bind a Service to Your App

Page last updated:

Page last updated:

The service marketplace has a large number of data stores, from Redis and MongoDB, to MariaDB (fork of MySQL) and RabbitMQ. You can run cf marketplace to get an overview. In this step you will add a small Redis datastore to your app.

Create the Redis datastore:

$ cf create-service redis small my-redis
Creating service instance my-redis in org MyOrg / space MySpace as user@mydomain.com...
OK

Create in progress. Use 'cf services' or 'cf service my-redis' to check operation status.

Attention: The plan `small` of service `redis` is not free.  The instance `my-redis` will incur a cost.  Contact your administrator if you think this is in error.

This creates a small Redis datastore for you which you now have to bind to your application. Binding means that the credentials and URL of the service will be written dynamically into the environment variables of the app as VCAP_SERVICES and can hence be used directly from there.

Now bind the new service to your existing application:

$ cf bind-service my-java-app my-redis
Binding service my-redis to app my-java-app in org MyOrg / space MySpace as user@mydomain.com...
OK
TIP: Use 'cf restage my-java-app' to ensure your env variable changes take effect

Note: If you are getting Server error, status code: 409, please try again after a couple of minutes. It takes a while to spin up that Redis datastore for you.

The credentials and URL of the bound service are available in the environment variables of your app. You can list the environment variables of your app as follows:

$ cf env my-java-app

System-Provided:
{
 "VCAP_SERVICES": {
  "redis": [
   {
    "credentials": {
     "host": "cbkc81454gjrxxxx.service.consul",
     "password": "RxxkJ75jKTDqxxxx",
     "port": 59282
    },
    "label": "redis",
    "name": "my-redis",
    "plan": "small",
    "provider": null,
    "syslog_drain_url": null,
    "tags": []
   }
  ]
 }
}

...

After that you have to restage the application as suggested so that it includes the new credentials in its environment variables:

$ cf restage my-java-app
Restaging app my-java-app in MyOrg monitor / space MySpace as user@mydomain.com...
-----> Downloaded app buildpack cache (3M)
-----> Java Buildpack Version: v3.6 (offline) | https://github.com/cloudfoundry/java-buildpack.git#5194155
-----> Downloading Open Jdk JRE 1.8.0_71 from https://download.run.pivotal.io/openjdk/trusty/x86_64/openjdk-1.8.0_71.tar.gz (found in cache)
...

Now you want to consume your new Redis datastore from within your application. In order to do that, you have to adjust the code to store and retrieve data via Redis. But first you have to add the Jedis dependency to the dependencies section of your build.gradle file. Jedis is a Java Redis client that lets your app interact with the Redis server.

compile 'redis.clients:jedis:2.6.2'

Now adjust the existing ProductService class in src/main/.../ProductService.java to setup a Redis-based ProductRepository implementation.

Add Jedis as a dependency import to the top of the file:

import redis.clients.jedis.Jedis;

Adjust the existing getProductRepository method in the ProductService class as listed below. This method ensures that a Redis-based repository is created when the Redis service binding can be found.

private static ProductRepository getProductRepository() {
        ProcessBuilder processBuilder = new ProcessBuilder();
    String servicesJson = processBuilder.environment().get("VCAP_SERVICES");
    if (servicesJson != null) {
        Map<String,Object> redisCredentials = getRedisCredentials(servicesJson);
        return new RedisProductRepository(redisCredentials);
    } else {
        return new SimpleProductRepository();
    }
}

Add the getRedisCredentials method below as a new method to the ProductService class. This method reads and parses the Redis credentials from the environment variables.

@SuppressWarnings("unchecked")
private static Map<String,Object> getRedisCredentials(String servicesJson) {
  ObjectMapper mapper = new ObjectMapper();
    if (servicesJson != null) {
      try {
        Map<String,Object> services = mapper.readValue(servicesJson, new TypeReference<Map<String,Object>>() { });
        List<Map<String,Object>> redisServices = (List<Map<String, Object>>) services.get("redis");
        for (Map<String,Object> redisService : redisServices) {
          // It is assumed that only one Redis service is bound to this app. Evaluate the name property
          // of the credentials in case multiple Redis services are bound to an app
          return (Map<String, Object>) redisService.get("credentials");
        }
      } catch (Exception exception) {
        throw new RuntimeException("Redis service declaration not found", exception);
      }
   }
   throw new RuntimeException("Redis service declaration not found");
}

Now add the new RedisProductRepository below as static inner class of ProductService class to store and retrieve products via Redis datastore:

public static class RedisProductRepository implements ProductRepository {

  private Jedis jedis;
  private ObjectMapper mapper;

  public RedisProductRepository(Map<String,Object> redisCredentials) {
    jedis = new Jedis((String) redisCredentials.get("host"), (int) redisCredentials.get("port"));
    jedis.auth((String) redisCredentials.get("password"));
    mapper = new ObjectMapper();
  }

  public long add(Product product) {
    product.setId(nextId());
    jedis.rpush("products", mapToJson(product));
    return product.getId();
  }

  public Collection<Product> findAll() {
    return jedis.lrange("products", 0, jedis.llen("products")).stream()
      .map(productJson -> mapToProduct(productJson))
      .collect(Collectors.toList());
  }

  private Long nextId() {
    return jedis.incr("productid");
  }

  private String mapToJson(Product product) {
    try {
      return mapper.writeValueAsString(product);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Product cannot be serialized as JSON");
    }
  }

  private Product mapToProduct(String productJson) {
    try {
      return mapper.readValue(productJson, Product.class);
    } catch (IOException exception) {
      throw new RuntimeException("Product cannot be deserialized from JSON");
    }
  }

}

With this latest code you can still experiment locally, since your app uses the SimpleProductRepository instead of the RedisProductRepository in case your Redis service binding cannot be found. This allows you to run your app locally as well as in the cloud without having to configure anything differently. However, you must install Redis on your local machine, if you want to test your app with a local Redis server.

Now compile/build your changes and push them to the cloud:

$ gradle build
$ cf push my-java-app -p build/libs/cf-sample-app-java-1.0.0.jar

You can access other services like MongoDB or MariaDB in a similar matter, simply by binding them to your app and accessing them through the environment variables.

I’ve bound a service to my App
View the source for this page in GitHub