API Architecture - Versioning Best Practices

Background

Let’s just begin with acknowledging that API Versioning is tough business. Over the course of an API’s lifecycle, development teams can make all kinds of changes to it (enhancements, bug fixes, adding new capabilities, etc.).

How would you determine whether a particular change will require a new API version? How would you communicate that to consumers? How much time would consumers have to upgrade? …..

All of these questions can be answered through some deliberate planning.

Rule of Thumb for API Versioning 👍

Each time you enhance your API, it is also evolving. And that API evolution can at times keep your API implementation the same, or it can introduce breaking changes, which would then negatively impact your API consumers.

You need to determine a versioning method, how to version your APIs, and what general principles to be mindful of when upgrading your APIs to keep consumers happy and APIs functional.

Without further ado, let’s dive into it!

Tip #1 - Keep a Detailed & Up-to-date API Spec

An API Specifications document provides software engineers and end users with the following details:

  • Contract — An API Spec is the contractual agreement between the API software engineers and the API consumers (or applications). It should list details of all the features in the API that are made available to the application.
  • Operations — The specific operation(s) that the API supports, also called methods and functions.
  • Requests — The specific request(s) that the API supports, and applications make the to API.
  • Responses — The specific response(s) that the API supports, and applications expect to receive from the API.

An application will function incorrectly with the API if the API service does not conform to its agreed-upon corresponding API Spec.

Tip #2 - Devise a Versioning Strategy

This is to handle the evolution (enhancements, bug fixes) to APIs over time. Software engineers may leverage a versioning strategy does the following:

  1. Preserve the existing functionality(s) of existing applications
  2. Provide additional functionality(s) via new versions of the API

How to handle the versioning

As a software engineer, you should take these concepts into account when looking to version your API:

Postel’s Law

When implementing versions, Postel’s law suggests the following:

  • Change responses (payloads) conservatively
  • Accept requests from applications/consumers liberally

With this mindset, you can use these commonly-used guidelines to help you achieve the following:

  • Create new API versions due to any of the following changes:
    - Major changes
    - Minor changes
    - Patch updates

It is possible to disrupt the flow/consumption of current applications/consumers that are consuming your API.

So I would recommend you preserve previous versions of your API, and sunset them gradually as you release new versions of an API.

Surprises are not Good! In the API world at least…

You want to follow the Principle of Least Astonishment, and recall the following guidelines when deciding if an API change is of the breaking type:

  • Consumers are dependent on the calls/methods the changes involve
  • The design should reflect the following consumer attributes:
    - Schemas
    - Mental models
    - Expectations

In order to reduce surprises (or, astonishments), software engineers can perform the following:

  • Have the API match the existing expectations of the consumers
  • Build and implement the new features to behave according to existing mental models that consumers and end users have created

If teams deviate too far from an existing mental model, they may also determine that a potential change is a breaking change, even if they consider it minimal/benign. That is because the consumer and end user does not have the underlying systems knowledge that the software engineer supporting the API does.

Tip #3 - Try to use Semantic Versioning

Semantic versioning is a strategy that allows engineers to build out patches and minor version changes without breaking existing integrations.

Patch updates

Patches are API changes that do not change the functionality of the API. Could be something as simple calling a new dependency.

Minor changes

Minor changes could add some new functionality, but they generally do not produce breaking changes (ex: like a newly required field).

Major changes

Major changes are those that require application developers/consumers to change how they call and consume an API (ex: supplying a required parameter(s) in an endpoint).

Versioning effects

In full scope, versioning changes are costly and time-consuming for both, the software engineers building and supporting the API. As well as the applications/consumers consuming the API, because they require long timeframes to properly test and ensure the upgrades are working with the application as desired.

Leveraging Semantic Versioning

Using Semantic versioning, we can do the following:

  1. Identify our APIs with the following pattern for the versioning: Major.Minor.Patch
  2. Release your API Specs initially with the first version being 1.0.0
  3. Number the versions created before the initial release as: 0.x.x
    - Quick note: The 0 indicates that the API was still in development mode.
  4. Number the minor and patch versions in sequential order from 0 .
  5. Restart the numbering for minor and patch versions at 0 when the next higher version change happens.
  6. Include the major version number in the path when creating your API.

For your endpoint base (also known as, basePath), you can use pretty much any design strategy, but here’s what I have seen work often:

  1. Domain
  2. Sub-domain
  3. Version
  4. Resource (coffeeshop)

From my experience, and in my opinion, I recommend including the major version number in the basePath for these different reasons:

  • Semantic versioning will restrict the API software engineer’s abilities to make updates and changes to the API Spec and existing APIs.
  • Minor and Patch versions SHOULD NOT introduce breaking changes.

Example: Coffeeshop would use an endpoint (something like this) for its pumpkin lattes API call: https://coffeeshop/drinks/sales/lattes/v1/pumpkinLattes

Versioning your URIs

My personal recommendation is to use URI Versioning to keep previous versions of an API, in the process while you migrate your consumers to the newer API versions.

You should keep the following items in mind when updating versions:

  • Indicate the version(s) consistently.
  • Keep different resources separate.
  • Create a new API call each time the software engineer would like to supply a newer version of the API Spec.
  • Preserve the functionality(s) of applications using the previous API.

Quick note: I recommend using only the major version in your basePath since minor and patch versions should not ever break integrations.

So this way, software engineers can support and catch the following scenarios by updating the basePath for new API Specs:

  • Software engineers can preserve existing APIs.
  • Engineers can create new API endpoints to support the new API Specs.
  • Applications that have not fully switched over to the updated URI are unimpacted by the version change.
  • Existing consumer applications can continue to function business as usual.

Tip #5 - Handling and Onboarding Breaking Changes

Typically, breaking changes lead to an increment in the major version number, while the non-breaking changes result in an increase to the minor version number.

When you roll/change the major version number, you need to consider all of your current API consumers.

I have seen many software engineers “sunset” the current version when rolling out a new version of an API.

During the sunset phase, software engineers run the previous version of the old API version to run alongside of the new API version.

And this gradually lets the API consumers to migrate to the new version during the API sunset phase.

Examples of Breaking Changes

Breaking changes can be created when the change(s) would cause the applications on the previous version to no longer work as desired.

Here are some examples:

  • Paths
    - Changing the basePath to any of the existing API methods
    - Changing the name of an existing resource, or collection
  • Requests
    - Adding or removing required parameters in the HTTP Headers (i.e. URL Parameters, or Request Body)
    - Adding or removing a required response header, or property from a response body
    - Adding or removing HTTP Status Codes.
  • Methods
    - Changing the behavior of the API.
    - Changing the fault tolerance/error handling behavior of the API.

Example: Say your API supplies an endpoint like this to send details about pumpkin lattes in the following format:

https://coffeeshop.com/drinks/lattes/v1/pumpkin

{
"latte": {
"flavor": "Pumpkin Swirl",
"size": "Large",
"holiday": "Halloween"
}
}

And if one day you chose to adjust the payload, and remove the holiday details, then that would count as a breaking change since consumer applications that relied on that detail would now no longer function as usual.

{
"latte": {
"flavor": "Pumpkin Swirl",
"size": "Large"
}
}

Or, you may also choose to break the key model into the following in order to make your payload more granular:

  • flavor
  • syrup

This will also count as a breaking change because of the following conditions:

  • An existing consumer application may still be using the older format of the payload
  • Engineers have not configured the existing application to consume the payload in the new format
{
"latte": {
"flavor": "Pumpkin",
"syrup": "Swirl",
"size": "Large",
"holiday": "Halloween"
}
}

Tip #6 - Handling and Onboarding Non-Breaking Changes

For minor version changes, engineers typically perform the following updates:

  • Incrementing the minor version by one
  • Setting the patch version back to zero

Patch changes

API Specs would only include patch changes when updates will not afect the functionality of the Spec (such as typos in the description, or other types of errors that don’t affect general functionality(s)).

Routing changes

You can update the URL endpoint, and it would automatically route the correct API call. I always recommend including the version in the basePath in order to accomplish this. Ultimately, you are responsible for contacting your downstream API consumers, and having them update the the new basePath when you need them to use your new API version.

Tip #7 - Gracefully Sunset and Deprecate legacy API versions

When you roll out an upgraded version of an API, you will want to deprecate the former versions, so you don’t need to maintain two service implementations for the API.

Sun-Setting an API places an API version into a limited-maintenance mode, which gives API subscribers time to migrate to a newer version of the API.

Software engineers should provide a sun-setting and deprecation policy, stating upfront the policy for any sunset period.

For the best customer service, always try to do the following when you sunset and deprecate APIs:

  1. Make an announcement to inform all API users that they must move to the new version of the API. This notice should include any sunset period and when you plan to deprecate the service. Keep an up-to-date list of contacts of your API consumers.
  2. Sunset the API with a warning that defines the sunset period (ex: 1 month, x number of weeks, etc.).
  3. Deprecate the API when users have discontinued usage of the former versions.

Want to learn more about API Architecture? 📝

Check out my series linked below! 🙂

APIs & Technical Architecture Best Practices

5 stories

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abdul Wahab

Abdul Wahab

1.7K Followers

Multi-disciplinary Software Engineer specialized in building products users love. Today, I manage & secure big data in the cloud. All views shared are my own.