If you want to try out
Cloud Foundry the simplest way to do that is to download the excellent
PCF Dev or to create a trial account at the
Pivotal Web Services site.
The rest of the post assumes that you have an installation of Cloud Foundry available to you and that you have a high level understanding of Cloud Foundry. The objective of this post is to list out of the options you have in integrating your Java application to a service instance - this demo uses mysql as a sample service to integrate with but the approach is generic enough.
Overview of the Application
The application is fairly simple
Spring-Boot app, it is a REST service exposing three domain types and their relationships, representing a university - Course, Teacher and Student. The domain instances are persisted to a MySQL database. The entire source code and the approaches are available at
this github location if you want to jump ahead.
To try the application locally, first install a local mysql server database, on a Mac OSX box with
homebrew available, the following set of commands can be run:
brew install mysql
mysql.server start
mysql -u root
# on the mysql prompt:
CREATE USER 'univadmin'@'localhost' IDENTIFIED BY 'univadmin';
CREATE DATABASE univdb;
GRANT ALL ON univdb.* TO 'univadmin'@'localhost';
Bring up the Spring-Boot under cf-db-services-sample-auto:
mvn spring-boot:run
and an endpoint with a sample data will be available at http://localhost:8080/courses.
Trying this application on Cloud Foundry
If you have an installation of
PCF Dev running locally, you can try out a deployment of the application the following way:
cf api api.local.pcfdev.io --skip-ssl-validation
cf login # login with admin/admin credentials
Create a Mysql service instance:
cf create-service p-mysql 512mb mydb
and push the app! (manifest.yml provides the binding of the app to the service instance)
cf push
An endpoint should be available at http://cf-db-services-sample-auto.local.pcfdev.io/courses
Approaches to service connectivity
Now that we have an application that works locally and on a sample local Cloud Foundry, these are the approaches to connecting to a service instance.
Approach 1 - Do nothing, let the Java buildpack handle the connectivity details
This approach is demonstrated in the
cf-db-services-sample-auto project. Here the connectivity to the local database has been specified using Spring Boot and looks like this:
---
spring:
jpa:
show-sql: true
hibernate.ddl-auto: none
database: MYSQL
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/univdb?autoReconnect=true&useSSL=false
username: univadmin
password: univadmin
When this application is pushed to Cloud Foundry using the
Java Buildpack, a component called the
java-buildpack-auto-reconfiguration is injected into the application which reconfigures the connectivity to the service based on the runtime service binding.
Approach 2 - Disable Auto reconfiguration and use runtime properties
This approach is demonstrated in the
cf-db-services-sample-props project. When a service is bound to an application, there is a set of environment properties injected into the application under the key "VCAP_SERVICES". For this specific service the entry looks something along these lines:
"VCAP_SERVICES": {
"p-mysql": [
{
"credentials": {
"hostname": "mysql.local.pcfdev.io",
"jdbcUrl": "jdbc:mysql://mysql.local.pcfdev.io:3306/cf_456d9e1e_e31e_43bc_8e94_f8793dffdad5?user=**\u0026password=***",
"name": "cf_456d9e1e_e31e_43bc_8e94_f8793dffdad5",
"password": "***",
"port": 3306,
"uri": "mysql://***:***@mysql.local.pcfdev.io:3306/cf_456d9e1e_e31e_43bc_8e94_f8793dffdad5?reconnect=true",
"username": "***"
},
"label": "p-mysql",
"name": "mydb",
"plan": "512mb",
"provider": null,
"syslog_drain_url": null,
"tags": [
"mysql"
]
}
]
}
The raw json is a little unwieldy to consume, however Spring Boot automatically converts this data into a flat set of properties that looks like this:
"vcap.services.mydb.plan": "512mb",
"vcap.services.mydb.credentials.username": "******",
"vcap.services.mydb.credentials.port": "******",
"vcap.services.mydb.credentials.jdbcUrl": "******",
"vcap.services.mydb.credentials.hostname": "******",
"vcap.services.mydb.tags[0]": "mysql",
"vcap.services.mydb.credentials.uri": "******",
"vcap.services.mydb.tags": "mysql",
"vcap.services.mydb.credentials.name": "******",
"vcap.services.mydb.label": "p-mysql",
"vcap.services.mydb.syslog_drain_url": "",
"vcap.services.mydb.provider": "",
"vcap.services.mydb.credentials.password": "******",
"vcap.services.mydb.name": "mydb",
Given this, the connectivity to the database can be specified in a Spring Boot application the following way - in a application.yml file:
spring:
datasource:
url: ${vcap.services.mydb.credentials.jdbcUrl}
username: ${vcap.services.mydb.credentials.username}
password: ${vcap.services.mydb.credentials.password}
One small catch though is that since I am now explicitly taking control of specifying the service connectivity, the runtime java-buildpack-auto-reconfiguration has to be disabled, which can done by a manifest metadata:
---
applications:
- name: cf-db-services-sample-props
path: target/cf-db-services-sample-props-1.0.0.RELEASE.jar
memory: 512M
env:
JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom
SPRING_PROFILES_ACTIVE: cloud
services:
- mydb
buildpack: https://github.com/cloudfoundry/java-buildpack.git
env:
JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}'
Approach 3 - Using Spring Cloud Connectors
The third approach is to use the excellent
Spring Cloud Connectors project and a configuration which specifies a service connectivity looks like this and is demonstrated in the
cf-db-services-sample-connector sub-project:
@Configuration
@Profile("cloud")
public class CloudFoundryDatabaseConfig {
@Bean
public Cloud cloud() {
return new CloudFactory().getCloud();
}
@Bean
public DataSource dataSource() {
DataSource dataSource = cloud().getServiceConnector("mydb", DataSource.class, null);
return dataSource;
}
}
Pros and Cons
These are the Pros and Cons with each of these approaches:
Approaches | Pros | Cons |
Approach 1 - Let Buildpack handle it |
1. Simple, the application that works locally will work without any changes on the cloud |
1. Magical - the auto-reconfiguration may appear magical to someone who does not understand the underlying flow
2. The number of service types supported is fairly limited -
say for eg, if a connectivity is required to Cassandra then Auto-reconfiguration will not work
|
Approach 2 - Explicit Properties | 1. Fairly straightforward.
2. Follows the Spring Boot approach and uses some of the best practices of Boot based applications - for eg, there is a certain order in which datasource connection pools are created, all those best practices just flow in using this approach. | 1. The Auto-reconfiguration will have to be explicitly disabled
2. Need to know what the flattened properties look like
3. A "cloud" profile may have to be manually injected through environment properties to differentiate local development and cloud deployment
4. Difficult to encapsulate reusability of connectivity to newer service types - say Cassandra or DynamoDB. |
Approach 3 - Spring Cloud Connectors | 1. Simple to integrate
2. Easy to add in re-usable integration to newer service types | 1. Bypasses the optimizations of Spring Boot connection pool logic. |
Conclusion
My personal preference is to go with Approach 2 as it most closely matches the Spring Boot defaults, not withstanding the cons of the approach. If more complicated connectivity to a service is required I will likely go with approach 3. Your mileage may vary though
References
1.
Scott Frederick's
spring-music has been a constant guide.
2. I have generously borrowed from
Ben Hale's
pong_matcher_spring sample.