Refactoring to Microservices

Right now there is a ton of hype and pushback around Microservices. Most of the current debate revolves around when Microservices make sense with smart people arguing all across the spectrum. As with all architectural topics the right answer is “it depends” so you should never blindly chose Microservices without understanding your goals and how they align with Microservices.

Using the open source WebJars project as an example I’d like to walk through a process of deciding where to use Microservices and then refactor part of the webjars.org app to a Microservice. First a little background on WebJars… WebJars are JavaScript & CSS libraries packaged into Jar files and published on Maven Central for easy consumption by JVM build tools. The webjars.org site is a Play Framework + Scala app that provides search, publishing, and file service for the jsDelivr CDN.

Here is my checklist for determining whether a piece of functionality should be broken out into a separate Microservice:

  1. The piece of functionality does NOT have shared mutable state.

    When using a Microservice a copy of the data will be shared. Mutating that copy will likely not propagate those changes back to the original source and all of the other possible copies of the data. While shared mutable state is common in many OO apps, this makes it very hard to switch to Microservices. Functional Programming on the other hand encourages immutable data which makes it much easier to switch to Microservices where copies can be mutated but it is clear that those mutations do not act on the original or other copies.

  2. The piece of functionality has independent operational or computational needs.

    If SLAs, scaling, or deployment needs vary between different pieces of functionality then Microservices might make sense. For example, if one piece of a system requires five nines but rarely changes while another piece does not have an SLA requirement and changes multiple times a day, Microservices make sense. Likewise you shouldn’t need to scale up every part of a system just because one piece of functionality has significant computation needs.

  3. The piece of functionality has cross-platform clients.

    While sharing code across platforms (e.g. JVM, Ruby, Node.js, etc) is sometimes possible, it is often easier and more maintainable to just expose the needed piece of functionality as a Microservice so that any platform can use it. For example, webjars.org uses a bower-as-a-service Microservice that runs in Node.js because it uses the Bower NPM package. The webjars.org app is a cross-platform (JVM) client to the Node.js Microservice.

The whole webjars.org app is functional and uses immutable data so there isn’t any shared mutable state that would make it hard to break pieces of functionality out into Microservices. In Play Framework a controller is really just a stateless function that takes a request and returns a response. This means that any of the web endpoints can be easily moved without impacting the system.

One possible candidate for a Microservice in webjars.org is a utility that converts SemVer-style version ranges to Maven-style version ranges. The SemVer.convertSemVerToMaven() function is not side-effecting so it could easily become a Microservice. But at this time the utility does not have independent operational or computation needs and it also does not have any other clients than the webjars.org app. If the functionality was needed outside of webjars.org then it could easily be turned into a library but a Microservice would definitely be overkill.

Another candidate for a Microservice in webjars.org is a web endpoint that serves a file from a WebJar. The Application.file controller function is stateless and does not use shared mutable state so it could easily become a Microservice. This function is what provides the content for WebJars on the jsDelivr CDN. When a request for a WebJar file on jsDelivr is received, if the CDN does not have the asset it gets it from webjars.org. For example:

https://cdn.jsdelivr.net/webjars/org.webjars/jquery/2.1.0/jquery.js

Is backed by:

https://webjars.herokuapp.com/files/org.webjars/jquery/2.1.0/jquery.js

The operational and computational needs of this piece of functionality are pretty different from the rest of the webjars.org app. Let’s compare the needs:

The webjars.org File Service Rest of webjars.org
SLA If it goes down then many production sites break No production uptime requirements
Scaling Most load is handled by the CDN but sometimes load spikes when caches are stale or invalidated Very light load
Deployment Rarely changes Changes a few times a week

So this seems like a great candidate for a Microservice! Here are the steps I used to break out this functionality into a Microservice.

Step 1) Create a new Play + Scala app

I used Typesafe Activator to create a new Play Framework + Scala app:

activator new webjars-file-service play-scala

Here is the commit from that starting place:

https://github.com/webjars/webjars-file-service/commit/4ef3567a8ea901c31cc1060af67ed80331b9b6a4

Step 2) Clean up the build and copy the code into the new project

I copy and pasted the parts of the code that I wanted to move to the Microservice into the new project. Here is the full change set:

https://github.com/webjars/webjars-file-service/commit/5cc050f0dab16e8cb14296d811a965df9616ca0b

There was very minimal refactoring between the original source and the new Microservice. Everything worked great locally so it was time to deploy the Microservice.

Step 3) Create a new Heroku app and setup GitHub auto-deployment

I created a new app on Heroku:

heroku create webjars-file-service

Instead of doing the usual git push heroku master I setup auto-deployment so that whenever I push to GitHub, Heroku deploys the changes. Check out a screencast of how to do that:

https://www.youtube.com/watch?v=QUvxrzINj5Q

Now that the webjars-file-service is deployed let’s try it out:

https://webjars-file-service.herokuapp.com/files/org.webjars/jquery/2.1.0/jquery.js

Everything is working great so lets switch webjars.org over to the new Microservice.

Step 4) Make the webjars.org app use the new Microservice

To make webjars.org use the new Microservice I removed the actual logic but I didn’t want to break any clients that were using the endpoints. To do this I added redirects for the actual file service functionality and for the file listing functionality I added a utility that wraps the new webjars-file-service Microservice. Along the way I had to do a small refactor of some Memcache-related functionality. Here is the full change set:

https://github.com/webjars/webjars/commit/5b7cd4b8fa410cd6e4ef9824a69d28790f42e9bd

After pushing the changes to GitHub, Codeship verified that the tests passed, and Heroku deployed webjars.org.

This whole process only took a few hours and so far everything has been working great! Because the file service functionality in webjars.org did not have shared mutable state it was incredibly easy to move to a Microservice which enables me to handle it’s unique operational and computational needs.

The decision to move something to a Microservice is always full of “it depends” factors. Microservices are certainly not a silver bullet especially when dealing with code bases that have shared mutable state. Like any tool, Microservice can be a powerful way to help you, or they can be the chainsaw you use to cut down the tree that falls on you. Handle with care!