A Lifecycle Approach to API Versioning

Posted in

“Successful software always gets changed.”

-Frederick P. Brooks

What is true for software in general also holds for APIs: successful APIs get changed. The reason is simple: successful APIs are used by various API consumers, who demand new features, extensions, bug fixes, and optimizations. From this perspective, APIs changes are inevitable.

But this is only half the story. Various consumers use APIs and rely on them to remain stable. Otherwise, it would break their existing integrations. Their software clients have dependencies on the API, and even a small change in the API is enough to break these clients. From this perspective, changes in APIs are undesirable.

Obviously, there is a dilemma. And it is the task of the API provider to resolve this dilemma. In this article, I want to give you some tips for dealing with it.

A Lifecycle Approach to Versioning

To deal with change and versioning effectively, we need to take a lifecycle approach: studying the API and its ability to deal with change from its inception and design all the way to its retirement. And I hope it does not come as a surprise to you — coping with change is also about being prepared. This means thinking about API changes way before concrete change proposals appear on the radar.

We can divide the lifecycle of an API into two major phases: the design phase before publishing the API and the phase after publishing the API. In these two phases, you must deal with changes in a very different manner. In fact, when transitioning from one phase to the next, your mindset about change needs to switch 180 degrees.

Before Publishing the API

When you initially design an API, you might not even think about future changes. But they will come. So here are three tips on how to support the evolution and versioning of your API already during design time.

Before Publishing the API: Welcome Feedback in Iterative API Design

Before the API is published, change is great — you should welcome it and seek it out. Use an iterative development methodology, elicit feedback from beta customers, and adapt the API spec based on the feedback you have received on API mocks and beta-versions of the API. This iterative development should ensure that the API you create is actually relatively stable. It should also reduce the likelihood of immediate future changes. But don’t stop with your API design here and instantly publish the API.

Before Publishing the API: Prepare an API Design Pattern for Versioning

Create an API design pattern for versioning right from the start. It might seem superfluous at the time of initial design, but it allows you to roll out consistent versions in the future and sets the right expectation for API consumers. Ideally, the API design pattern for versioning chosen for your API is documented in your API design style guide.

While the most commonly observed pattern realizes API versioning as URI Path Parameter, there are various alternative API design patterns for making major versions accessible:

  • API Versioning in the Accept Header: The client can use the HTTP Accept header to explicitly indicate the version of the API. The Accept header still contains the MIME-types as usual, but in addition, the version is appended. The URI of the API does not contain any version information. For new versions, the URL does not change, but the request header does.
  • API Versioning as URI Path Parameter: The most common technique for versioning uses a URI parameter with the version number.
  • API Versioning in a Custom HTTP Header: A custom HTTP header could be defined for the version.
  • API Versioning as Query Parameter: A query parameter could be defined for the version.
  • API Versioning as a new Subdomain: A subdomain could be defined for the new version.

Before Publishing the API: Anticipate Future Change

Anticipate future changes to your API and accommodate for it in your API design. Everyone knows that this is difficult, as there are no hard facts about the future. But it is essential preparation for the evolution and versioning of your API. Base your assessment on experience and domain knowledge. Maybe you know there is an upcoming industry standard in your domain that needs to be supported. Perhaps you know that some scalar data fields will likely need to become an array in the future. The point is to prepare the API spec so that evolution in the anticipated direction is possible without breaking changes.

After Publishing the API

After the API has been published, your attitude to change in the API needs to be much more conservative, in the interest of your existing API consumers. Change needs to be managed much more rigorously. The basic rule is that the API’s externally observable behavior (from the clients’ perspective) cannot be changed once published. Of course, the exception to the rule is creating a new version of the API.

Managing Change: Step 0 – Is the Change Really Necessary?

First, assess if the proposed change is really necessary. If you can avoid the change: great. One less problem.

Managing Change: Step 1 – Is the Change Backward-Compatible?

If the change is really necessary, determine what the change means for the API contract — typically your Open API spec — and for the API consumers relying on the API contract. Ideally, you have an API spec for the published API and a new API spec that has all the proposed changes in it. Assess each of the changes and determine whether they are backward-compatible or incompatible.

Backward-compatible changes:

  • Adding query parameters (they should always be optional).
  • Adding header or form parameters, as long as they are optional.
  • Adding new fields in JSON or XML data structures, as long as they are optional.
  • Adding endpoints, e.g., a new REST resource.
  • Adding operations to an existing endpoint, e.g., when using SOAP.
  • Adding optional fields to the request interfaces.
  • Changing mandatory fields to optional fields in an existing API.

Incompatible changes (a.k.a. breaking changes):

  • Removing or changing data structures, i.e., changing, removing, or redefining fields in the data structure.
  • Removing fields from the request or response (as opposed to making it optional).
  • Changing a previously optional request field in the body or parameter into a mandatory field.
  • Changing a previously required response field in the body or parameter into an optional field.
  • Changing the URI of the API, such as hostname, port, or path.
  • Changing the structure or relationship between request or response fields, e.g., making an existing field a child of some other field.
  • Adding a new mandatory field to the data structure.

Managing Change: Step 2 – Dealing With Backward-Compatible Changes

If you have found no incompatible changes, only backward-compatible ones, you should be able to implement them without breaking clients. Apply semantic versioning and give the API release that covers the changes a new minor version. This new minor version will replace the published API already available. Old clients should not be affected by the changes, while new clients can benefit from the new functionality.
There is one general issue with changes, even if they are backward-compatible. Backward compatibility is based on the API spec, the OpenAPI specification, which serves as the “official contract” with the API consumer. But Hyrum’s law tells us that, given enough API consumers, there will be an API consumer relying on any observable behavior of your API, not only on the behavior described in the OpenAPI specification.

Managing Change: Step 3 – Dealing With Breaking Changes

If you have found at least one incompatible change, it will break clients that rely on the changed feature. A new major version of this API is required, and this new version needs to be maintained in parallel with the previous version. Moreover, significant versions need to be visible to the consumers, and they must be accessible by the consumers. Thankfully, if you have followed this guide from the start, you already have a mechanism in place for picking different versions. See section (Prepare an API design pattern for versioning).

Managing Change: Step 4 – Sunsetting

Operating and maintaining multiple versions of the API requires a lot of effort on the side of the API provider. Sooner or later, old versions of the API must be deprecated. But this should not be a sudden event, rather a gradual one — like a sunset. That’s why we call this phase the API sunset.

Set clear expectations for the API sunset and communicate it as early as possible to all affected API consumers, giving them as much time as possible to deal with the sunset and lift their API clients to the new version. You must obtain your API consumer’s contact details, which are typically collected when they onboard. Get API consumers on a mailing list — not for marketing purposes but for product changes. Send out a targeted message on the mailing list to all API consumers when a new version of the API is published, including a timeline for switching off the old API. Motivate your API consumers to switch to the new version in time and explain the benefits of using the latest version.

Conclusion

The implications of changing APIs can be challenging to manage. Therefore, developers must prepare early in the life of an API. We have seen that API versioning begins way before the first breaking change is introduced — even before the API is initially published. A properly designed API will likely be able to live without breaking modifications because it is iteratively designed, and future changes have been anticipated, and a versioning pattern is defined. And if those changes eventually come, you can categorize the changes as backward-compatible and breaking changes. Then, react accordingly with minor and major versions and eventually sunset old API versions.