In my previous three posts (Building “Bootiful” Java Apps, Building REST APIs With Spring Boot, Securing REST APIs With Spring Boot)  I have described how to build and secure a Spring Boot app.  In the final post in this series we will deploy the app to IBM Bluemix.  If you don’t already have a Bluemix account you can register for a free 30 day trial.  Spring Boot apps can be packaged as either a war or a jar, however there is a small benefit to deploying your application as a war when deploying to Bluemix.  Since the jar will contain Tomcat it has some extra size.  When you package your application as a war the Tomcat dependencies are removed so the package is smaller and there is less to push over the wire.

In addition we will also need to change how we tell our application about the location of the Mongo DB.  Right now the application will assume the Mongo DB is running on localhost, that will not be the case in Bluemix.  Lets address this problem first.

As you probably already know if you are familiar with services in a Cloud Foundry based PaaS, a service’s credentials are made available to an application via the environment variable VCAP_SERVICES.  One option to access the Mongo DB credentials is to parse the VCAP_SERVICES environment variable extract the credentials for the Mongo DB service and use those to instantiate our Mongo code in our app.  Most applications will also have the requirement that the app be able to run locally, so in addition to parsing the VCAP_SERVICES environment variable we will also need to add some code to figure out if we are running in the cloud or not.  This code would not be hard to write, but there are better options.  There is a nice library that can do all this for us called Spring Cloud Connectors.  The Spring Cloud Connectors project makes it easy to use client libraries for various services when your application is running in the cloud.  In addition to supporting various cloud environments, the Spring Cloud Connectors project can support the same code running locally as well.  To get started using the Spring Cloud Connectors project we need to add some dependencies to our POM.  Add the following dependencies to your POM file.

 

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-spring-service-connector</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-localconfig-connector</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
		</dependency>

The spring-cloud-spring-service-connector artifact provides us the with the necessary magic when running in a Spring app.  The spring-cloud-local-config-connector artifact provides the ability to run the app locally.  The spring-cloud-cloudfoundry-connector project provides the necessary magic when running on PaaS based on Cloud Foundry, like Bluemix.

Now that we have our dependencies, lets add some code.  In the demo package of our application add the following class

package demo;

import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;

@Configuration
public class CloudConfig extends AbstractCloudConfig {
  @Bean
  public MongoDbFactory documentMongoDbFactory() {
      return connectionFactory().mongoDbFactory();
  }
}

That’s it, that is all the code we need to write.  No need to parse JSON and figure out if we are running locally or in the cloud, all that will be taken care of for us by the Spring Cloud Connectors project.

We do need to do a little more configuration to make sure our application will run locally.  If you had added spring.data.mongodb.uri to your application.properties file you can remove that property, it will no longer be needed.  The Spring Cloud Connectors project uses a separate configuration file for all the cloud services you want to use when running locally.  Somewhere locally on your machine create a file called spring-cloud.properties (you can name it whatever you want if you would like).  I usually put the file in my home directory.  In the file create a property called spring.cloud.appId and set it to any value you would like.  Add another property called spring.cloud.mongo and set its value to mongodb://localhost:27017.  If your Mongo DB server is not running on localhost or requires a username and password be sure to make the necessary changes to the URI.  Your properties file should now look like this.

spring.cloud.appId: mongo-rest
spring.cloud.mongo: mongodb://localhost:27017

Save the file if you have not done so already.  Now we need to tell our app about this properties file.  We can do that in several ways, but for this example we will specify it in a properties file on the classpath.  In src/main/resources create a new file called spring-cloud-bootstrap.properties.  In this file add the property spring.cloud.propertiesFile and set it to the path of your spring-cloud.properties file.  If you placed your properties file your home directory you can use the variable ${user.home} in the path to represent your home directory.  For example

spring.cloud.propertiesFile: ${user.home}/spring-cloud.properties

Once the spring.cloud.propertiesFile is created you should be able to run your application locally and should work just as it did before.  The only difference is that we can now deploy the application to a Cloud Foundry PaaS and as long as there is a Mongo DB service bound to the application it will work there as well.

Now we need to change how we package the application.  I like to be able to setup my POM so I can build both a jar and war file, I find being able to produce a self container jar convenient.  One way to do this is to create a Maven profile to package the application as a war.  Open the POM for the application and add the following XML to the POM.

<packaging>${packaging.type}</packaging>
  
<properties>
    <packaging.type>jar</packaging.type>  
</properties>  
<profiles>
  <profile>
      <id>war</id>
      <properties>
        <packaging.type>war</packaging.type>
      </properties>
      <dependencies>
        <dependency>
		  <groupId>org.springframework.boot</groupId>
		  <artifactId>spring-boot-starter-tomcat</artifactId>
		  <scope>provided</scope>
		</dependency>
      </dependencies>
      <build>
	    <finalName>${project.artifactId}</finalName>
	    <plugins>
	      <plugin>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-maven-plugin</artifactId>
	      </plugin>
	      <plugin>
            <artifactId>maven-clean-plugin</artifactId>
            <version>2.5</version>
            <executions>
              <execution>
                <id>auto-clean</id>
                <phase>initialize</phase>
                <goals>
                  <goal>clean</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
	</plugins>
    </build>
  </profile>
</profiles>

If you already have a packaging element replace it with the one above.  If you already have a properties element add the new property packaging.type to it.

When the war profile is activated the build will package your application as a war without the Tomcat dependencies.

To get started deploying the Spring Boot application to Bluemix you should login, head to the Catalog, and create a new MongoLab service.  Name the service spring-boot-mongo and leave it unbound for now, we will bind it to the app once we deploy it.  Next package your application by running $ mvn package -P war, this should produce a war file in your target directory.  Finally push your application (the following assumes you are in the root of your project).

$ cf push mongo-demo -p target/mongo-demo.war –no-start

$ cf bind-service mongo-demo spring-boot-mongo

$ cf start mongo-demo

Once your application is deployed test it out by using the REST APIs we defined in the previous posts.  It should work exactly the same locally as it does in the cloud!


Ryan J Baxter

Husband, Father, Software Engineer