Introduce Version Identifier

Updated:

 Note: This is a preview release and subject to change. Feedback welcome! Contact Information and Background (PDF)

also known as: Apply Semantic API Versioning, Make Version Explicit (in Messages), Provide Compatibility Metadata

Context and Motivation

Version Identifier is one of the Microservice API Patterns (MAP). Its is applicable in the following context: “An API that runs in production evolves. New versions with improved functionality are offered over time. At some point in time, the changes of a new version are no longer backward-compatible, thereby requiring to break existing clients.”

As an API provider, I want to communicate versions and their compatibility properties explicitly so that clients can react accordingly on changes that affect them during API evolution.

Stakeholder Concerns (including Quality Attributes and Design Forces)

#maintainability
Move Operation explains this quality: “There are many reasons to change an API (in addition to quality refactorings) [Stocker and Zimmermann 2021]. It should be changed as much as required and as little as possible, and ripple effects be avoided. One of the first steps in related maintenance tasks is to determine which parts of the system should be changed.” The impact of the changes on other parts should be kept minimal, but are worth communicating when they occur.
#developer-experience
Explicit versioning might appear to be costly and anti-agile or not RESTful at first glance. However, fixing bugs caused by not knowing about versioning mismatches often is expensive. As Version Identifiers can be placed in protocol headers (in most protocols) or in the message payload, additional learning and decision making is required. Accessing protocol headers differs from accessing payload in terms of code to be written, tool and library support, and portability.

Initial Position Sketch

Before introducing a Version Identifier to the API, all clients invoke operations on the same endpoint:

Introduce Version Identifier: Initial Position Sketch

Introduce Version Identifier: Initial Position Sketch

The entire API or individual parts such as endpoints, operations, or message parts can be versioned. Here, we primarily target endpoint versioning.

Design Smells

Tacit semantic changes up to incompatibilities creep in
While the technical API contract remains unchanged, the meaning of the received or returned data might change over time. Such semantic mismatches between older and newer version should be documented in the API Description and examples, and then caught during testing, ideally in an automated fashion. Implicit versioning and tolerant reading might hide such changes and their impact for quite some time.
Resistance to change caused by uncertainty
A provider might hesitate to implement necessary API changes due to a lack of clarity in its strategy for evolving the API. Clients might be reluctant to upgrade to new versions because they are unable to assess the imposed changes on their side.

See “Interface Evolution Patterns — Balancing Compatibility and Extensibility across Service Life Cycles” [Lübke et al. 2019] for other smells related to API versioning and examples.

Instructions (Steps)

The introduction and continued use of explicit Version Identifiers has to be planned, executed, and sustained:

  1. Decide on scope and naming conventions for the Version Identifier, for instance following the Semantic Versioning specification when assigning and communicating version numbers (currently standing at Version 2.0.0).1
  2. Decide where to place the Version Identifier for each API element to be versioned. For instance, possible locations are endpoint address, message payload, and protocol header.
  3. Define an evolution roadmap, selecting one or more lifecycle management strategies to define the lifetime of the version; see the related refactorings Tighten Evolution Strategy and Relax Evolution Strategy.
  4. Place the new endpoint addresses and message definitions in the API implementation.
  5. Update the API documentation with the new Version Identifier(s) and meta information about the meaning of this version information (for instance, consequences of certain version numbers regarding compatibility).

See the pattern description of Version Identifier for more information about versioning scopes, identifier placement (location), and compatibility considerations.

Target Solution Sketch (Evolution Outline)

Once a Version Identifier has been introduced, clients are able to choose which version of an endpoint they want to work with (assuming that multiple versions are supported, as described in the Two in Production pattern):

Introduce Version Identifier: Target Solution Sketch

Introduce Version Identifier: Target Solution Sketch

Example(s)

Depending on the chosen location of the Version Identifier, clients enact their usage decision in the message payload or a header (see section “Instructions (Steps)” above). In HTTP, it may be part of the endpoint address (relative URI path):

GET /customers/1234
Accept: text/json+customer; version=1.0
...

or

GET /v2/customers/1234
...

The API Stylebook compiled by “API Handyman” Arnaud Lauret points at many additional examples in its design topic “Updates and Versioning”.

Hints and Pitfalls to Avoid

Before and when applying this refactoring, make sure to:

  • Involve API clients in the decisions about and planning of API evolution (assuming that they are known and willing to participate).
  • Stay backward-compatible whenever possible, but do not hesitate to upgrade the major version when necessary.
  • Provide migration aids such as change logs, code snippets, and mappings of identifiers and parameters from old to new version.
  • Be aware of design challenges caused by automatic routing. For example, Version Identifiers in encrypted message payloads might not be visible to intermediaries and therefore can not be used fo routing purposes.

“Interface Evolution Patterns — Balancing Compatibility and Extensibility across Service Life Cycles” has further hints. For example, “stick to a standardized and consistent versioning strategy, e.g., decide which objects to version consistently (operations, data types etc.) or which versioning schema to use (e.g., Semantic Versioning).” [Lübke et al. 2019]

Other refactorings dealing with API evolution are Introduce Version Mediator, Tighten Evolution Strategy and Relax Evolution Strategy.

The Evolution patterns in MAP cover versioning. For instance, there is a pattern called Semantic Versioning. Service Level Agreement is captured in pattern form as well.

Arnaud Lauret’s book on Web API design covers the topic [Lauret 2019], and the blog post “5 Ways to Version APIs “ also discusses options with pros and cons.

References

Lauret, Arnaud. 2019. The Design of Web APIs. Manning.

Lübke, Daniel, Olaf Zimmermann, Cesare Pautasso, Uwe Zdun, and Mirko Stocker. 2019. “Interface Evolution Patterns: Balancing Compatibility and Extensibility Across Service Life Cycles.” In Proceedings of the 24th European Conference on Pattern Languages of Programs. EuroPLop ’19. New York, NY, USA: Association for Computing Machinery. https://doi.org/10.1145/3361149.3361164.

Stocker, Mirko, and Olaf Zimmermann. 2021. “From Code Refactoring to API Refactoring: Agile Service Design and Evolution.” In Service-Oriented Computing, edited by Johanna Barzen, 174–93. Cham: Springer International Publishing.

  1. Note that the Semantic Versioning specification has a Version Identifier and applies Semantic Versioning itself.