Ensuring backwards compatibility in deployments by leveraging git tags


I recently came across a blog post on injecting variables into Golang at build time and that gave me the idea for this blog post. Automating versioning and ensuring backwards compatibility.

You can find the code for this application here, on my GitHub

Git tags and versioning

This blog post assumes some basic understanding of the git version control system. For anyone new to it you can probably get by with thinking that commits are incremental changes to the software and tags are user-created labels that reference a specific commit.

Versioning using git tags is a pretty common practice. The way I like to have my automation setup is the main branch of a repository is wired to auto-deploy any changes to a staging/QA environment for testing, while any tags that meet certain criteria are wired to auto-deploy to a production environment. For example tags could be in vMajor.Minor.Revision or release/vMajor.Minor.Revision format.

For this blog post we’re using the format of vMajor.Minor.Revision.


Injecting the version during the build

The blog post I linked from Alex Ellis does a good job explaining injecting variables at build time so I don’t go too deep into that.


The basic gist of what we’re doing is creating a string variable named APIVersion and then injecting the value we want that to be in when we build the application, in this case we want it to be v# where # is the major version number of our application.


To build our application we use go build -ldflags "-X main.APIVersion=[versionhere]", so for example for v1 we would use go build -ldflags "-X main.APIVersion=v1".


Ensuring backwards compatibility

With a properly versioned API it’s almost trivial to maintain and ensure backwards compatibility, but it does take a little thought during the initial setup to make everything smooth and easily maintainable.


For this blog post we’re using the format of http://domain.com/[apiVersion]/..., where apiVersion is the vMajor version from our git tags.


In the sample API I wrote for this blog post you can see I’ve created several git tags, the first being v1.0.0 and it references the initial commit to the repo. We’ve created the initial git tag and from here on out we can’t make any changes that would break applications relying on the format and structure of the API URLs and JSON response.


However, we do have some wiggle room here. Since we’re returning JSON we can add new fields to the JSON output without breaking backwards compatibility, the next git tag v1.0.1 does this and adds a Description field in the JSON response.


We can also add new API endpoints because any application created prior to the new additions will still work as intended, it just wont have the new functionality. This keeps backwards compatibility while adding new features. The next git tag of v1.0.2 does this by allowing us to append /itemNumber to the end of the /items endpoint to return a specific item.


If you look at the commit referenced with tag v1.0.2 you’ll notice that we do slightly change a legacy endpoint from /items to /items/, this is okay because /items now automatically redirects to /items/. Even if a legacy application is using the URL without the slash it will still work as intended, so backwards compatibility is still ensured.


Making a breaking change

The next versioning tag in the sample API makes a breaking change, we’re altering the structure of the JSON response for the /items/ endpoint, returning [{item1},{item2},...] instead of {"Items": [{item1},{item2},...]}.


Since this is a breaking change any application that relies on the previous structure will stop working, to prevent this we cannot deploy the new API version to the previous endpoint, and this is where versioning comes in handy.


The previous versions of the API were tagged with v1.0.x and use a url format of /v1/..., with our current setup to maintain backwards compatibility all we have to do is bump the major version number of our git tags, so our new version becomes v2.0.0 instead of v1.0.3

Automating the build process

Given the vast amount of tools for automation and CI/CD setups I’m not going to go in depth into any particular software but instead speak mostly in generic process outline here.


The first step to automating is getting our latest git tag, cutting it down from vMajor.Minor.Revision to vMajor, and injecting that into our build process. The simplest way to do this is something along the lines of this: git describe --abbrev=0 --tags | cut -d'.' -f1.


Now that we have our latest git tag we save it as a variable in our automation software of choice and inject it into the build with go build -ldfags "-X main.APIVersion=${gitTagVersion}".


From here we have our API versioned, built to use /[apiVersion]/... for our endpoint URLs, and now it can be deployed.


We now have an RESTful API with automated versioning based on tags in our code revisioning system, and build process that ensures backwards compatibility.


There are a couple of caveats though, for example once you break compatibility and bump the vMajor version of a git tag you can’t edit a previous version on the main branch of the repository. So for example once we went from v1.0.3 to v2.0.0 we couldn’t go back and create a v1.0.4. To do this we would need to create a new branch off the commit that v1.0.3 references, make our changes, and create a tag there for v1.0.4. This isn’t a huge issue since once the major version is bumped up there should be minimal work put into the legacy version, but it is something to be aware of.


Another caveat is you can’t run multiple versions of the API from the same address and port, this is because only one service can listen on a port at a time. To get around this your CI/CD pipeline should include a loadbalancer that passes traffic based on the /vMajor/ portion of the URL. For local testing you can setup a very lightweight nginx service, run the versions on different ports, and have nginx load balance from localhost:80 based on the request URL and send it to the ports your services are listening on.

How do you rate this article?


The Finance Hacker
The Finance Hacker

I'm a nerd. I do stuff.


Random collection of IT-related blogs I've written

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.