Semantic versioning and micro-services

Semantic versioning is about assigning software version numbers that convey meaning about the extent of changes that have been made. Perhaps the most popular scheme is the one specified at semver.org. A summary of the semver scheme is given in the introduction:

"Given a version number MAJOR.MINOR.PATCH, increment the:

    1. MAJOR version when you make incompatible API changes,
    2. MINOR version when you add functionality in a backwards-compatible manner, and
    3. PATCH version when you make backwards-compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format."

There have been some criticisms of semantic versioning; for example, the article, "Why Semantic Versioning Isn't", in which the author argues that there is no substitute for detailed release notes. Fair enough, but I think semantic versioning has value nevertheless.

I will make one observation from personal experience about the use of semantic versioning in the context of microservices.

Since the meaning of the term 'microservices' is not (yet) universally agreed, I'll be specific about my understanding of it, which comports with that provided by Martin Fowler:

"...an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API." -- http://martinfowler.com/articles/microservices.html

I believe that semantic versioning is even more helpful for development teams that use microservices because it does what the compiler cannot: provide a way to inform developers about potential incompatibilities between microservices that interact with each other.

Imagine an application that offers a platform for plug-ins, components that can be integrated with the core of the application at run-time. Suppose those plug-ins are to be deployed as microservices, all of them complying with a specification of the REST end-points that the plug-ins must provide:

REST API 1.0.0

GET /item          
Returns a list of items: { itemNo: string, name: string }

GET /item/{itemNo} 
Returns an item { itemNo: string, name: string }

POST /item?name={name} 
Creates a new item

Every plug-in that wants to be registered with the core must provide those end-points. The Core is released, along with two plug-ins: PlugInA and PlugInB.

Core 1.0.0      (compatible with API 1.0.0)
- PlugInA 1.0.0 (compatible with API 1.0.0)
- PlugInB 1.0.0 (compatible with API 1.0.0)

Note that there are version numbers assigned to the software components, and also to the API; everything is at 1.0.0. It's sunshine and lollipops all around.

Now suppose that the developers want to enhance PlugInA so that it uses a description of each item in addition to the item name. The developers make the necessary alterations to the REST API, specifying that the item descriptions are optional elements of each end-point.

REST API 1.1.0

GET /item          
Returns a list of items: { itemNo: string, name: string[, desc: string] }

GET /item/{itemNo} 
Returns an item { itemNo: string, name: string[, desc: string] }

POST /item?name={name}[&desc={desc}] 
Creates a new item

To indicate that the updated API is backwards-compatible with the original one, the developers assign it a version number that differs only in the MINOR part.

The developers now make changes to the Core and to PlugInA to support REST API 1.1.0. They assign the updated Core version 1.1.0, and they assign the updated PlugInA version 1.1.0 too. PlugInB is not updated at this point because there isn't time to work on it before the next big release.

Because of the semantics attached to the API version numbers, we can draw conclusions about which versions of the plug-ins work with which versions of the Core:

Core 1.0.0      (compatible with API 1.0.0)
- PlugInA 1.0.0 (compatible with API 1.0.0)
- PlugInB 1.0.0 (compatible with API 1.0.0)

Core 1.1.0      (compatible with API 1.0.0 and 1.1.0)
- PlugInA 1.0.0 (compatible with API 1.0.0)
- PlugInA 1.1.0 (compatible with API 1.0.0 and 1.1.0)
- PlugInB 1.0.0 (compatible with API 1.0.0)

Without the adherence to our semantic versioning scheme, the overall project could become very messy indeed, as more plug-ins are built, and their development cycles proceed independently of each other.

My recent experience on one such complex microservices-based project has proven semantic versioning to be essential. Throughout the project, the developers have maintained a wiki page containing a matrix of software component versions, mapped to REST API versions. We were pleased we did.