Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreThe 12 Factor App Manifesto talks about backing services at length. A backing service is, basically, any networked attached service that your application consumes to do its job. This might be a MongoDB instance, PostgreSQL database, a binary store like Amazon's S3, metrics-gathering services like New Relic, a RabbitMQ or ActiveMQ message queue, a Memcached or Redis-based cache, an FTP service, an email service or indeed anything else. The distinction is not so much what the service is so much as how it's exposed and consumed in an application. To the app, both are attached resources, accessed via a URL or other locator/credentials stored in the configuration.
We looked at how to use configuration to extricate magic strings like locators and credentials from the application code and externalize it. We also looked at the use of service registries to keep a sort of living phone book of microservices in a dynamic (typically cloud) environment.
In this post, we'll look at the way Platform-as-a-Service (PaaS) environments like Cloud Foundry or Heroku typically expose backing services, and look at ways to consume those services from inside a Spring application. For our examples, we'll use Cloud Foundry because it's open-source and easy to run in any datacenter or hosted, though most of this is pretty straightforward to apply on Heroku.
My friend Abby Gregory Kearns put together a very nice high-level overview of the role and value of Cloud Foundry's backing services portfolio.
A PaaS like Cloud Foundry exposes backing services as operating system process-local environment variables. Environment variables are convenient because they work for all languages and runtimes, and they're easy to change from one environment to another. This is way simpler than trying to get JNDI up and running on a local machine, for example, and promotes portable builds. I intend to look at backing services specifically through the lens of current-day Cloud Foundry in this post. Keep in mind though the approach is specifically intended to promote portable builds outside of a cloud environment. Spring is tailor-made to be portable; dependency injection promotes decoupling the a beans (like, for example, that which comes from a backing service) initialization and acquisition logic from the site of its use. We can use Spring to write code that thinks about javax.sql.DataSources
and then write configuration to source that DataSource
from the right contexts and configuration as the application moves from one environment to another.
The runtime under the next version of Cloud Foundry (hitherto referred to as Diego in the press) is Docker-first and Docker-native. Docker, of course, makes it easy to containerize applications, and the interface between the containerized application and the outside world is kept intentionally minimal, again, to promote portable applications. One of the key inputs in a Docker image is, you guessed it, environment variables! Our pal Chris Richardson has done a few nice posts on both packaging and building Spring Boot-based Docker images and on standing up backing-services. We won't look at Docker in this post (though, stay tuned!), but it's important to internalize: environment variables are easy and pliable ways fo externalizing backing service connection information.
DataSource
Here's a simple Spring Boot application that inserts and exposes a few records from a DataSource
bean which Spring Boot will automatically create for us because we have the H2 embedded database driver on the CLASSPATH. If Spring Boot does not detect a bean of type javax.sql.DataSource
and it does detect an embedded database driver (H2, Derby, HSQL), it will automatically create an embedded javax.sql.DataSource
bean. This example uses JPA to map records to the database. Here are the Maven dependencies:
Group ID | Artifact ID |
---|---|
com.h2database |
h2 |
org.springframework.boot |
spring-boot-starter-data-jpa |
org.springframework.boot |
spring-boot-starter-data-rest |
org.springframework.boot |
spring-boot-starter-test |
org.springframework.boot |
spring-boot-starter-actuator |
Here is the sample Java code:
package demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Arrays;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
CommandLineRunner seed(ReservationRepository rr) {
return args -> Arrays.asList("Phil,Webb", "Josh,Long", "Dave,Syer", "Spencer,Gibb").stream()
.map(s -> s.split(","))
.forEach(namePair -> rr.save(new Reservation(namePair[0], namePair[1])));
}
}
@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}
@Entity
class Reservation {
@Id
@GeneratedValue
private Long id;
private String firstName, lastName;
Reservation() {
}
public Reservation(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
The application works locally, but let's turn now to making it run on Cloud Foundry where it'll need to be told how to connect to the backing service (an instance of PostgreSQL) exposed for it.
Cloud Foundry comes in many flavors. Like Heroku, you can run it hosted in an AWS-based flavor that's available as Pivotal Web Services. You can get a free trial account. You can use Pivotal's shrink-wrapped variant, Pivotal Cloud Foundry, and run that in your data center, if you like. Alternatively, you can use the open-source bits and run that, too. Or use any of the numerous other implementations from the likes of IBM and HP. At any rate, you'll end up with a PaaS that behaves basically the same with respect to backing services.
Adding a backing service is a declarative function: simply create a service and then bind it. Cloud Foundry features a marketplace command: cf marketplace
. For the 80% cases, you should be able to pick from among the options in the cf marketplace
output. Once you've picked a service, create an instance of it, like this:
cf create-service elephantsql turtle postgresql-db
Note specifically that postgresql-db
is the backing service name, elephantsql
is the service provider name, and turtle
is the (free) tier plan name. With that in hand, you can bind the service to your deployed application. You can use the cf
CLI, or simply declare a backing service dependency in your application's manifest.yml
file.
Here's the basic manifest.yml
for all our samples. The name
and host
change from one manifest to another, but the examples for this post use this newly created postgresql-db
service.
---
applications:
- name: simple-backing-services
memory: 512M
instances: 1
host: simple-backing-services-${random-word}
domain: cfapps.io
path: target/simple.jar
services:
- postgresql-db
env:
SPRING_PROFILES_ACTIVE: cloud
DEBUG: "true"
debug: "true"
Now, let's look at a few different ways to consume the backing service and look at some of their strengths and weaknesses.
We'll use Spring Boot, at a minimum, for all of these examples. Spring Boot offers the Spring Boot Actuator module. It provides very helpful information about the application - metrics, environment dumps, a listing of all the defined beans, etc. We'll use some of these endpoints to get insight into the environment variables and system properties exposed when running a Spring application, and to understand things like the profile under which Spring Boot is running. Add the Spring Boot Actuator in your Maven or Gradle build. The groupId
is org.springframework.boot
and the artifactId
is spring-boot-starter-actuator
. You don't need to specify the version if you're using the a Spring Boot generated from start.spring.io
or the spring init
CLI command.
The Cloud Foundry Java buildpack does auto reconfiguration for you. From the docs:
Auto-reconfiguration consists of three parts. First, it adds the
cloud
profile to Spring's list of active profiles. Second it exposes all of the properties contributed by Cloud Foundry as aPropertySource
in theApplicationContext
. Finally it re-writes the bean defintitions of various types to connect automatically with services bound to the application. The types that are rewritten are as follows:
Bean Type Service Type javax.sql.DataSource
Relational Data Services (e.g. ClearDB, ElephantSQL) org.springframework.amqp.rabbit.connection.ConnectionFactory
RabbitMQ Service (e.g. CloudAMQP) org.springframework.data.mongodb.MongoDbFactory
Mongo Service (e.g. MongoLab) org.springframework.data.redis.connection.RedisConnectionFactory
Redis Service (e.g. Redis Cloud) org.springframework.orm.hibernate3.AbstractSessionFactoryBean
Relational Data Services (e.g. ClearDB, ElephantSQL) org.springframework.orm.hibernate4.LocalSessionFactoryBean
Relational Data Services (e.g. ClearDB, ElephantSQL) org.springframework.orm.jpa.AbstractEntityManagerFactoryBean
Relational Data Services (e.g. ClearDB, ElephantSQL)
The effect is that an application running on H2 locally will automatically run against PostgreSQL on Cloud Foundry, assuming you have a backing-service pointing to a PostgreSQL instance created and bound to the application, as we do above. This is very convenient! If your only two destination targets are localhost and Cloud Foundry, this works perfectly.
Environment
properties for your Spring ApplicationThe default Java buildpack (which you can ovveride when doing a cf push
or by declaring it in your manifest.yml
) adds a Spring Environment
PropertySource
that registers a slew of properties that start with cloud.
. You can see them if you visit the REST /env
endpoint in the above application once you've pushed the application to Cloud Foundry. Here's some of the output for my application:
{
...
cloud.services.postgresql-db.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
...
cloud.services.postgresql-db.connection.uri: "postgres://AUSER:[email protected]:5432/AUSER",
cloud.services.postgresql-db.connection.scheme: "postgres",
cloud.services.postgresql.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
cloud.services.postgresql.connection.port: 5432,
cloud.services.postgresql.connection.path: "AUSER",
cloud.application.host: "0.0.0.0",
cloud.services.postgresql-db.connection.password: "******",
cloud.services.postgresql-db.connection.username: "AUSER",
...
cloud.application.application_name: "simple-backing-services",
cloud.application.limits: {
mem: 512,
disk: 1024,
fds: 16384
},
cloud.services.postgresql-db.id: "postgresql-db",
cloud.application.application_uris: [
"simple-backing-services-fattiest-teniafuge.cfapps.io",
"simple-backing-services-unmummifying-prehnite.cfapps.io"
],
cloud.application.instance_index: 0,
...
}
You can use these properties in Spring just like you would any other property. They're very convenient, too, as they provide not only a fairly standard Heroku-esque connection URI (cloud.services.postgresql-db.connection.uri
), but also one that can be used in a JDBC context, directly, cloud.services.postgresql.connection.jdbcurl
. As long as you use this (or a fork of this) buildpack, you'll benefit from these properties.
Cloud Foundry exposes all of this information as standard, language and technology netural environment variables (VCAP_SERVICES
and VCAP_APPLICATION
). In theory, you should be able to write an application for any Cloud Foundry implementation and target these variables. Spring Boot also provides an auto-configuration for those variables, and this approach works whether you use the aforementioned Java buildpack or not.
Spring Boot maps these environment variables into a set of properties accessible from the Spring Environment
abstraction. Here's some sample output from the VCAP_*
properties that Spring Boot exposes, also from /env
:
{
...
vcap.application.start: "2015-01-27 09:58:13 +0000",
vcap.application.application_version: "9e6ba76e-039f-4585-9573-8efa9f7e9b7e",
vcap.application.application_uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
vcap.application.uris: "simple-backing-services-fattiest-teniafuge.cfapps.io,simple-backing-services-grottoed-distillment.cfapps.io,...",
vcap.application.space_name: "joshlong",
vcap.application.started_at: "2015-01-27 09:58:13 +0000",
vcap.services.postgresql-db.tags: "Data Stores,Data Store,postgresql,relational,New Product",
vcap.services.postgresql-db.credentials.uri: "postgres://AUSER:[email protected]:5432/hqsugvxo",
vcap.services.postgresql-db.tags[1]: "Data Store",
vcap.services.postgresql-db.tags[4]: "New Product",
vcap.application.application_name: "simple-backing-services",
vcap.application.name: "simple-backing-services",
vcap.application.uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
...
}
I tend to rely a little on each approach. The Spring Boot properties are convenient because they provided indexed properties. vcap.application.application_uris[2]
provides a way to index into the array of possible routes for this application. This is ideal if you want to tell your running application what its externally adressable URI is, for example, if it needs to establish callbacks at startup before any request has come in. It also provides the equivalent technology-agnostic URIs, but not the JDBC-specific connection string. So, I'd use both. This apprroach is convenient, particularly in Spring Boot, because I can be explicit in my configuration and set properties (like spring.datasource.*
) to instruct Spring Boot on how to set things up. This is useful in the interest of being explicit, or if I have more than one backing service of the same type (like a JDBC javax.sql.DataSource
) bound to the same application. In this case, the buildpack won't know what to do so you need to be explicit and disambiguate which backing service reference should be injected and where.
Spring Boot, by default, loads src/main/resources/application.(properties,yml)
. It will also load profile specific property files of the form, src/main/resources/application-PROFILE.yml
, where PROFILE
is the name of an active Spring profile. Earlier, we saw that our manifest.yml
specifically activates the cloud
profile by setting an environment variable. So, suppose you wanted to have a configuration that was activated only when running in a cloud
profile, and another that was activated when there was no specific profile activated - this is called the default
profile. You could create three files: src/main/resources/application-cloud.(properties,yml)
which will be activated whenever the cloud
profile is activated, src/main/resources/application-default.(properties,yml)
which will be activated when no other profile is specifically activated, and src/main/resources/application.(properties,yml)
which will be activated for all situations, no matter what.
A sample src/main/resources/application.properties
:
spring.jpa.generate-ddl=true
A sample src/main/resources/application-cloud.properties
:
spring.datasource.url=${cloud.services.postgresql-db.connection.jdbcurl}
A sample src/main/resources/application-default.properties
:
# empty in this case because I rely on the embedded H2 instance being created
# though you could point it to another, local,
# PostgresSQL instancefor dev workstation configuration
All of these options thus far take advantage of the Environment
abstraction. No doubt, they're far simpler than manually picking apart the JSON structure in the VCAP_SERVICES
variable, but we can do better. As the docs for the Spring Cloud Connectors project says:
Spring Cloud provides a simple abstraction for JVM-based applications running on cloud platforms to discover bound services and deployment information at runtime and provides support for registering discovered services as Spring beans. It is based on a plugin model so that the identical compiled application can be deployed locally or on multiple clouds, and it supports custom service definitions through Java SPI.
Let's look at our revised example:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.sql.DataSource;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Configuration
@Profile("cloud")
public static class DataSourceConfig extends AbstractCloudConfig {
@Bean
DataSource reservationsPostgreSqlDb() {
return connectionFactory().dataSource("postgresql-db");
}
}
}
@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}
@Entity
class Reservation {
@Id
@GeneratedValue
private Long id;
public Long getId() {
return id;
}
private String firstName, lastName;
Reservation() {
}
public Reservation(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Instead of configuring a javax.sql.DataSource
using properties and Spring Boot, we explicitly factory an object of the right type. There are other methods for other backing services like MongoDB, Redis, SendGrid, etc., and you can easily provide your own. We're using the Spring Cloud Connectors plugin for Cloud Foundry, though there's no reason you couldn't use the Spring Cloud Connectors plugin for Heroku, or the property-based alternative for local applications. Your application is then identical across environments, and only the external configuration differs.
@Bean
sSo far, we've relied on common-sense defaults provided by the platform or the framework, but you don't need to give up any control. You could, for example, explicitly define a bean in XML or Java configuration, using the values from the environment. You might do this if your application wants to use a custom connection pool or in some otherway customize the configuration of the backing service. You might also do this if the platform and framework don't have automagic support for the backing service you're trying to consume.
@Bean
@Profile("cloud")
DataSource dataSource(
@Value("${cloud.services.postgresql-db.connection.jdbcurl}") String jdbcUrl) {
try {
return new SimpleDriverDataSource(
org.postgresql.Driver.class.newInstance() , jdbcUrl);
}
catch (Exception e) {
throw new RuntimeException(e) ;
}
}
Thus far we've looked only at consuming services that Cloud Foundry exposes. If you want to consume a service that you've got off-PaaS, it's easy enough to treat it like any other backing service using Cloud Foundry's user provided services. This mechanism is just a fancy way of telling Cloud Foundry about the locator and credential information for a custom service that you want your applications to talk to. Once you've done that, Cloud Foundry applications and services can bind that service to their applications and consume it just as they normally would. User provided services are ideal or services, like a fixed Oracle instance, that you don't intend for Cloud Foundry to manage. Cloud Foundry won't add new instances, it won't delete them, and it doesn't control authorization.
If you want Cloud Foundry to manage a service, you need to adapt it to Cloud Foundry using the service broker API. This is more important when deploying Cloud Foundry in your own environment, and requires admin privileges (which you won't have in the hosted Pivotal Cloud Foundry, for example). The service broker API is a set of well-known REST callbacks that Cloud Foundry needs to be made aware of. It's easy enough to implement your own custom service broker, and there's even this convenient Spring Boot-based project and corresponding sample.
Check out the examples, too!