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.
View the source for this page in GitHub