Nicholas Blair Software Engineer

Combining Spring Boot with Bower dependencies

In this post I’ll describe a technique we recently employed to split a site in half, then re-assemble for deployment.

We’ll start with the Spring Boot runnable jar providing a REST API. With a few Maven tricks, we can combine that jar with a static site retrievable via bower, and end up with a new shippable Spring Boot runnable jar with both.

This post was updated on July 14, 2016, see the follow up at the bottom.

Background

We have this site we are working on that has 2 discrete areas of focus:

  • Integration with one of our institution’s data sources
  • A single page application user interface for viewing and managing that data

Pretty run of the mill business application. This is a green field project, with each one we get a chance to find a more efficient way to work.

My team is composed of 2 sub-teams (front-end for presentation, back-end for integration), each specializing in the areas of focus described above. We wanted the back-end team to be able to work in the way they are familiar, and likewise for the front-end team.

We decided to use Spring Boot for the integration project, with Gradle and Java. We wanted to use AngularJS for the front end, but use npm and bower instead.

We also wanted to experiment with a way for the front end team to be able to work without having the overhead of the Spring Boot project. The larger question we wanted to answer:

Is there a way for the front-end team to work in the site using their familiar tool chain and not have to adopt the tool chain that the back-end team uses?

Project Structure

We have two git repositories, one for the back-end using Gradle and Spring Boot, and one for the front-end using Node for a local web server and Bower for dependency management.

The analogues we’ll use are spring-boot-sample-tomcat for the back-end and angular-seed for the front-end.

Workflow

Back end team works dutifully in the back-end project. These folks have JDK, Gradle, and an IDE. They write unit and integration tests. The only “front-end” for this application is a Swagger UI if they need to do any end-to-end testing.

Front end team works dutifully in the front end of the site. They don’t need to have the same IDE installed, nor do they need a JDK or Gradle. They work in text editors like Sublime or Atom. Changes they make to the site are picked up immediately, no waiting for a gradle bootRun to re-compile classes and run unit tests.

Node is running superstatic-with-proxy for two reasons:

  1. Angular’s html5mode requires that the server be capable of rewriting/forwarding requests to Angular routes back to /index.html.
  2. We need the REST API provided by the back-end to be served from the same domain, the -proxy fork of superstatic gives us the ability to do that.

The front end developers can proxy the deployed REST API environment of their choice. The back-end team also publishes a docker image for local development if they need something that’s not deployed.

Assembly for deployment

The teams have working releases of their repositories, now we need to stitch them together for deployment.

I’ve shared a git repository demonstrating how this works:

https://github.com/nblair/spring-boot-plus-front-end

What happens when you run mvn package on this thing?

  1. maven-dependency-plugin fires first, downloading the Spring Boot jar from our Artifact Repository. It then unpacks the jar to the specified <outputDirectory> (best to nest this under ${project.build.directory}).
  2. frontend-maven-plugin fires next. If it hasn’t downloaded node for your system, it will (under ${basedir}/node/). Then it will run npm install (really just to get bower), then it will run bower install. bower.json declares a dependency on our front end project, and now we have a copy sitting in ${project.build.directory} next to the back-end project.
  3. Then, during the prepare-package phase, the maven-resources-plugin has 3 executions:
    1. Copy the bower_components (which was generated by the bower install in step 2) folder from the front-end site into META-INF/resources/bower_components in the outputDirectory of step 1.
    2. Copy the rest of the assets of the front end project site (excluding bower_components) into into META-INF/resources in the outputDirectory of step 1.
    3. Copy any beta specific configuration into the project (root of the classpath).
  4. Now sitting on the filesystem is an exploded Spring Boot jar with our assets from the front end site piled into it. The last plugin to fire is the maven-jar-plugin, with a few bits of custom configuration:
    • classesDirectory is set to be the root of our exploded Spring Boot jar.
    • A manifestFile - the original from the Spring Boot jar - is explicitly declared. If we don’t do this, the maven-jar-plugin will create one on it’s own that’s missing the properties Spring Boot expects.
    • The compress flag MUST be false. Spring Boot jars contain jars (inside the lib directory), Spring Boot won’t start properly if these jars get compressed.

Now you have a new Spring Boot jar, composed of both projects and environment specific configuration. Ship this jar to your deployment environment. If you use docker for deployment, use the following Dockerfile:

FROM java:8
COPY target/spring-boot-plus-front-end-*.jar /usr/src/spring-boot-plus-front-end.jar
EXPOSE 8080
CMD ["java", "-jar", "/usr/src/spring-boot-plus-front-end.jar"]

URL Rewriting

You may not have to do this, but if your front-end site uses Angular’s $locationProvider.html5Mode(true) you’ll need Spring Boot to handle forwarding requests to your Angular routes to /index.html (or whatever the root of your site is).

There is a great project called UrlRewriteFilter that will give you what you need.

You’ll need to add a Spring @Configuration class to your back-end project, here is a gist. You’ll also need to provide a configuration file, I’ve provided a commented example in the spring-boot-plus-front-end project.

Questions

The natural question (I myself even asked):

Why didn’t you just put the front-end assets in src/main/resources/META-INF/resources for the back end project?

We’ve done this before, and it’s got a negative impact on the front-end team. The projects they work on use a completely different tool chain.

The front-end team doesn’t have to wait for the back end unit tests to complete, or a full build. They can just run npm start and their front-end app is up and running. No restarts even after a git pull upstream to catch latest merged changes. Using superstatic-with-proxy allows them to encapsulate the back end.

Another question:

Instead of stitching the 2 projects together, couldn’t you just deploy both?

The answer to this is yes. Running the 2 services could give you some resiliency - you’d still be able to deliver content even though the back end of the application is down. Deploying this way would require some magic to get the 2 services running on 2 different ports to be presented by 1 port. You’d need some sort of intermediate layer, like a load balancer or apache with mod_proxy.

superstatic-with-proxy isn’t a good choice for deployment, because in the latest version at time of writing, an HTTP request to a proxied path will cause node to shutdown with an error if the proxied server is offline. That behavior defeats the purpose of trying to mimic the developer’s proxy setup in production.

Summary

So, with this approach, can the front end work in the tool chain of their choice and not have to adopt the tool chain of the back end team? It has been a bit tricky to get to this point, but we believe so. We will continue to experiment, and try to apply this same approach to other projects.

Having one deployable out of the multiple separate sites is nice, simple to deploy. We think it is worth experimenting with deploying the two constituent projects, but there is some complexity there we aren’t quite sure how to resolve.

Experience

I’ve written about our experience with this project structure, make sure to read Follow up to ‘Combining Spring Boot with Bower dependencies.