Building Microservices in Go: REST APIs - Versioning
Tags: go, rest, microservices
Published: 2021-06-27 16:44:51

## What is versioning in software?

Versioning is the idea of identifying a concrete piece of software _based on a value_, where this value could used to reference the contract being followed by both the provider of that software as well as the user of that software.

One example, in the context of REST APIs, would be to identify the fields and types used in the payload of the same resource over time.

There are multiple ways to create this _identity_, from uniquely generated values to sequentially incremented numbers as well as values using release dates. In some cases special rules could be applied to those identifiers, like using odd-numbers to indicate unstable releases.

Choosing a versioning technique with clearly defined rules is important for both the team in charge of building the product as well as your users to understand _how_, _when_  and _if_ a version update is needed, this is where _Semantic Versioning_ comes in.

---

> **Disclaimer:** This post includes Amazon affiliate links. If you click on one of them and you make a purchase I'll earn a commission. Please notice your final price **is not** affected at all by using those links.

## What is Semantic Versioning?

[_Semantic Versioning_](https://semver.org/) is a collection of rules and requirements that determine how version numbers are assigned and incremented. Go, for example, uses it for managing dependencies as part of [Go modules](https://blog.golang.org/using-go-modules).

The way it works is by defining a versioning format consisting of three numbers with specific names and rules: `X.Y.Z`, where:

* `X`: represents a major value,
* `Y`: represents a minor value, and
* `Z`: represents a patch value,

Each value indicates _how compatible_ the version is compared to another one, all of this is better explained by the [Semantic Versioning 2.0 Summary](https://semver.org/#summary) (emphasis mine):

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

> 1. MAJOR version when you **make incompatible API changes**,
> 1. MINOR version when you **add functionality in a backwards compatible manner**, and
> 1. 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.

## How can we implement versioning for REST APIs?

There is no _official_ recommendation, but different companies in the software industry like to take any of the following approaches, all of them with specific tradeoffs:

* Using a _path_
* Defining _subdomains_
* Receiving _query arguments_
* Accepting _headers_

## Versioning using a path

Differentiate versions by a parent path, which identifies (most of the times) _the major version_, for example:

* `GET https://todo.app/**v1**/tasks/{taskId}`
* `GET https://todo.app/**v2**/tasks/{taskId}`

A real life example of this is the [Blogger API](https://developers.google.com/blogger), which at the moment defines two supported APIs with similar resources:

* [Version 2](https://developers.google.com/blogger/docs/2.0/json/reference) and
* [Version 3](https://developers.google.com/blogger/docs/3.0/reference)

For example the `Blogs: Get` API:

* [Version 2](https://developers.google.com/blogger/docs/2.0/json/reference/blogs/get): `GET https://www.googleapis.com/blogger/v2/blogs/{blogId}` and
* [Version 3](https://developers.google.com/blogger/docs/3.0/reference/blogs/get): `GET https://www.googleapis.com/blogger/v3/blogs/{blogId}`

Implementation details:

* In Go: each version defines a concrete handler that happens to be using specific paths, requests and response types.
* Not Go: two different microservices using a gateway that uses the path to redirect traffic to specific versions.
* OpenAPI/Swagger: can be used.

## Versioning using subdomains

Differentiate versions by subdomain, which identifies (most of the times) _the major version_, for example: 

* `GET https://**v1**.todo.app/tasks/{taskId}`
* `GET https://**v2**.todo.app/tasks/{taskId}`

Implementation details:

* They could literally be two different microservices, totally independent of each other.
* OpenAPI/Swagger: can be used.

## Versioning using query arguments

Differentiate versions by query arguments, with the possibility to indicate exactly the version to use, for example:

* `GET https://todo.app/tasks/{taskId}?**v=1**`
* `GET https://todo.app/tasks/{taskId}?**v=2**`

For example [Microsoft Azure DevOps Services](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/rest-api-versioning?view=azure-devops) uses this approach where a way to specify the version would be using something like:

`GET https://dev.azure.com/{organization}/_apis/{area}/{resource}?api-version=1.0`

Implementation details:

* When using different technologies: a gateway could be used,
* When using the same technologies: complicated to maintain because both may be using the same underlying handler, either a `switch`-like style or `map`-based functions; or literally implement a new microservice and use a gateway,
* OpenAPI/Swagger: hard to define requests and schemas, but possible, [Microsoft Azure Cognitive Search: api-version](https://docs.microsoft.com/en-us/rest/api/searchmanagement/management-api-versions) does that.

## Versioning using headers

Differentiate versions by values in the HTTP headers:

* Using a custom Header:

  `GET -H **"Version: <VERSION>"** https://todo.app/tasks/{taskId}`, or 

* Using Content Negotiation, for example [Github](https://docs.github.com/en/rest/overview/media-types#request-specific-version) uses a [vendor prefix](https://tools.ietf.org/html/rfc4288#section-3.2) to do so:

  `GET -H **"Accept: application/vnd.todo.<VERSION>+json"** https://todo.app/tasks/{taskId}`

Implementation details:

* When using different technologies: a gateway could be used,
* When using the same technologies: complicated to maintain because both may be using the same underlying handler, either a `switch`-like style or `map`-based functions; or literally implement a new microservice and use a gateway,
* OpenAPI/Swagger: hard to define requests and schemas, but possible, [Github](https://github.com/github/rest-api-description/tree/main/descriptions) does that.

## Conclusion

So what is _the best option?_ The usual answer: _"It depends"_.

In the end I believe the customer experience matters the most and perhaps taking the simplest approach, like using _subdomains_ or _paths_, makes adopting your API easier for your customers increasing your market-share.

Besides selecting a way to version HTTP REST APIs, the important thing to know is how and when to introduce breaking changes, the approach I like taking is the following:

* **Before** adding a new major version:
  * Delay **v1** as much as possible, literally _stay with v0 "forever"_, or at least make it clear to your customers breaking changes may be expected.
  * Try to make your changes additive only and deprecate old ones.
* **While** dealing with multiple versions. Consider a hybrid, for example _Paths_:
  * _v1_: `GET https://todo.app/v1/tasks/{taskId}`
  * _v1_: **deprecated** `PUT https://todo.app/v1/tasks/{taskId}`
  * _v2_: `PUT https://todo.app/v2/tasks/{taskId}`
* **After**:
  * Encourage users to upgrade to new version by clearly indicating new features and capabilities,
  * Provide a way to automatically upgrade, and
  * Give a concrete deadline, plan deprecating older versions, if you can; in some cases building a _bridge_ API to convert from one to another could be needed.

## Recommended Reading

If you're looking to sink your teeth into more REST and Web Programming I recommend the following books:

* [The Design of Web APIs (2019)](https://amzn.to/32yFwFe)
* [Irresistible APIs (2016)](https://amzn.to/2MQHYmo)
* [Go Web Programming (2016)](https://amzn.to/30euJ1U)
* [REST in Practice (2010)](https://amzn.to/3n6ztRZ)