Introduction

All service versioning adheres to semantic versioning.

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

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

A version increase may be due to many factors, however, the kind of increment is dependent on the nature of the change and its externally perceivable effect.

Services will be atomically versioned and continue to evolve and preserve backwards compatibility. Only the latest deployed version of a service will be active in any given environment (with the exception of dual running alongside the previous version during an upgrade).

If backwards incompatibility cannot be maintained, a number of strategies (such as new services, endpoints or content negotiation) may be employed to allow the client base to migrate in a timely fashion.

Client-Service Contract

The following contract is adhered to:

  • Services are atomically versioned.
  • Services must adhere to semantic versioning.
  • Services must provide a version history.
  • Services must not make backwards incompatible changes (employing the strategy of only add, never remove).
  • Services must include their version in every response.
  • Clients should inform services of their service compliance version.
  • If clients are outdated, services must inform clients of successor versions.
  • On receiving a representation, a service must be magnanimous and expect no more data than any previous version.
  • On receiving a representation, a client must be tolerant and accept extra data over any previous version.

Versioning Granularity

A given service is atomically versioned. That is, a service is the smallest division which merits its own version. Whilst there may be some merit in versioning collections of endpoints, the overall simplicity in versioning at the service level outweighs that.

It is therefore important to consider the following principle factors affecting versioning of services:

  • Interface - the model of interaction and endpoints used
  • Representation - the data schema of the representation accepted and returned by a service
  • Implementation - the internal mechanics of the service

Versioning Scheme

Services MUST adhere to semantic versioning.

The version of a service is affected by changes to its external contract and/or internal implementation. In either case, any change must be backwards compatible.

The service contract is comprised of the model of interaction, endpoints and data schema of the representation accepted and returned. Ordinarily, a change to the contract would warrant an increment to the MINOR version.

The service implementation is the encapsulated implementation. Typically, a change in the implementation would warrant an increment to the PATCH version, or in the case of significant refactoring an increment to the MINOR version.

Version History

Services MUST provide a versions resource allowing clients to query version history. It must support both the list and id list forms, i.e. /versions and /versions/{id}.

The response MUST be a JSON object with a versions property that is a JSON object. Each version in the response will be a key value pair in the versions object, where the key is the version number as a string and the value a string array of changes notable in the version. The change descriptions are for human consumption and no machine understanding of their content is expected.

For example:

{
  "versions": {
    "1.2.0": [
      "Feature B"
    ],
    "1.1.1": [
      "Fixes #14",
      "Fixes #15"
    ],
    "1.1.0": [
      "Feature A"
    ]
  }
}

Backwards Compatibility

Services MUST NOT make backwards incompatible changes, employing the strategy of only add, never remove.

Version Header

Services MUST return X-Version header containing the version of the service.

Client Service Compliance Version

Clients SHOULD indicate their service compliance version in requests using the X-Accept-Version. Failure to do so will assume that the client is outdated.

In the situation where a version is no longer available (see End of Life), the service MUST return a 410 GONE status code in the response.

Outdated Notices

Services MUST inform clients of successor versions if the client is outdated. This is done using a Link header with relation type of outdated and location pointing to the /versions endpoint and the ascending list of successor versions in comma separated form. In the case where clients do not provide a compliance version header, the location shall be /versions.

Clients MUST capture and appropriately handle outdated notices in API responses. Client developers should actively address the notices, and having done so, increase the compliance version header subsequently sent.

Tolerance

In order to preserve forwards compatibility, clients and services MUST be flexible in their handling of representations.

On receiving a representation, a service MUST be magnanimous and expect no more data than any previous version, i.e. services must suitably default any additional data allowed for older clients

On receiving a representation, a client MUST be tolerant and accept extra data over any previous version, i.e. clients must not error in the case of fields being added by newer services.

Example

A service starts at v1.1.0. A bug is fixed in v1.1.1 and a new feature is introduced in v1.2.0.

The GET /versions endpoint returns its version history:

{
  "versions": {
    "1.2.0": [
      "Feature B"
    ],
    "1.1.1": [
      "Fixes #14",
      "Fixes #15"
    ],
    "1.1.0": [
      "Feature A"
    ]
  }
}

A client that is compliant with v1.1.0 of the API makes a request. The outdated link header references v1.1.1 and v1.2.0.

interaction between v1.1.0 compliant client and v1.2.0 service

The client is coded to auto-upgrade any PATCH versions, and subsequent requests are stated as compliant with v1.1.1. The outdated link header references v1.2.0.

interaction between v1.1.1 compliant client and v1.2.0 service

On the next upgrade to the client, modification are made to make it v1.2.0 compliant. Now that the client and service match versions, the outdated link header is no longer present.

interaction between v1.2.0 compliant client and v1.2.0 service

Another client that is not compliant with the versioning approach does not supply an X-Accept-Version header. The outdated link header references the version list.

interaction between non-compliant client and v1.2.0 service

Service Upgrades

Depending on environment criteria, new service versions may be deployed immediately or enqueued for deployment in the next deployment window.

Ordinarily, only the latest deployed version of a service MUST be active in any given environment.

During the deployment period, the new version will be deployed to servers marked as canary servers, allowing the new service version to be monitored with live traffic prior to wholesale upgrade to the new service version.

Previous service versions are kept available but inactive until the release is confirmed.

Rollback to a previous version is a matter of rerouting traffic back to the previous service versions that are still available.

Handling Major Changes

Various strategies can be employed to allow for major changes and still preserve backwards compatibility.

New Endpoint

A new endpoint may be introduced that supersedes the existing endpoint. The service continues to support the existing endpoint in its present form and only critical fixes should alter its behaviour. Further evolution of the functionality is only applied to the new endpoint.

When this strategy is used, it is often desirable to increment the MAJOR version even though this is technically backwards compatible. This ensures that the previous major version enters into End of Life allowing for the endpoint to be removed once the notice period is reached.

New Service

Wholesale changes to a service, such as a change in the underlying technology, are often better handled through the introduction of a new service. The new service may choose to reset its versioning, or preserve it as a marker to its heritage.

This allows the existing service to continue to be supported and only critical fixes applied. The existing service should enter into End of Life.

New Media Type

Where there is only a change in the representation, this may be handled through the use of a new media type and content negotiation.

Services can advertise that they now support a new media type in the version history, and clients that wish to leverage the new media type can do so through the use of the Accept header.

End of Life

Over time it is necessary to end of life a service or major version of it. Some reasons for this include:

  • A service has been superseded by another service (such as to support a backwards incompatible change as outline above).
  • A service has increased its MAJOR version due to significant change history (allowing the service to start to reduce technical debt).
  • A service is no longer of business value.

Monitoring

One of the key benefits in clients supplying their service compliance version is to allow monitoring of which versions are still in use. This information is of great benefit in determining if and when End of Life should occur.

Notification

Any customer using a service MUST register with the change notification mailing list.

End of Life notices MUST be notified to the change notification mailing list.

The notice period for End of Life MUST not be less than 12 months and included in notifications.

Evolution Strategy

End of Life can be used to ensure that services can continue to evolve and shed older implementation and technical debt. For that reason, services that have accumulated significant history may choose to move to a new MAJOR version so that it can enforce that the client base evolves too.

For example, consider a service with significant change under v1. The decision is taken to move to v2 to instigate end of life on v1. After 12 months, v1 is no longer supported and a true backwards incompatible v3 release is made which removed the v1 technical debt.

Alternatively, it may be suitable to make small backwards incompatible changes and remove technical debt if it is well known that no clients are using an old version of a service (through monitoring).

Which ever approach is taken, it must be carefully planned to ensure that clients and customers are not unduly affected.