Approaches to binding a Spring Boot application to a service in Cloud Foundry
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.
Reference: | Approaches to binding a Spring Boot application to a service in Cloud Foundry from our JCG partner Biju Kunjummen at the all and sundry blog. |
The first on Good tutorial, but I have a question:
I used the second option, disable the autoconfiguration Buildpack and get the VCAP_SERVICES envirotment variable to configure Spring Boot conection. But I made a small trap, that is: in the Spring Boot configuration:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: ${vcap.services.mydb.credentials.jdbcUrl}
username: ${vcap.services.mydb.credentials.username}
password: xxx
The App deply correctly and works correctly the MySQL service, how could it be possible??, the password is wrong!!
Regards
The password is probably also included in the jdbcUrl