-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbind-service.html.md.erb
192 lines (151 loc) · 7.54 KB
/
bind-service.html.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
---
title: Bind a Service to Your App
owner: Daniel Freitag
---
<strong><%= modified_date %></strong>
The <a href="../service-offerings/index.html" target="_blank">service marketplace</a> 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:
<pre class="terminal">
$ 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.
</pre>
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:
<pre class="terminal">
$ 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
</pre>
<p class="note">
<strong>Note</strong>: If you are getting <code>Server error, status code: 409</code>, please try again after a couple of minutes. It takes a while to spin up that Redis datastore for you.
</p>
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:
<pre class="terminal">
$ 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": []
}
]
}
}
...
</pre>
After that you have to restage the application as suggested so that it includes the new credentials in its environment variables:
<pre class="terminal">
$ 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)
...
</pre>
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 <a href="https://github.com/xetorthio/jedis" target="_blank">Jedis</a> 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.
```groovy
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:
```java
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.
```java
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.
```java
@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:
```java
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 <a href="http://redis.io/topics/quickstart" target="_blank">Redis</a> 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:
<pre class="terminal">
$ gradle build
$ cf push my-java-app -p build/libs/cf-sample-app-java-1.0.0.jar
</pre>
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.
<div style="text-align:center;padding:3em;">
<a href="./manifest.html" class="btn btn-primary">I've bound a service to my App</a>
</div>