SaFi Bank Space : REST API versioning approach

This page discusses how the services should maintain their REST API as a contract between provider and consumer.

API Contract evolution

At any point in time, the following holds for every microservice:

  • There are OpenAPI contracts published per service in different versions, and each published version of the contract is available for consumers & providers to download.

  • There is only a single OpenAPI contract used by a running instance of the microservice in each of the environments (DEV, STG, PROD)

    • This contract contains all endpoints the microservice offers for the consumers to call

    • The contract has a version, which is unique per contract for the microservice. More specifically, the combination of {service_name}-{open_api_version} uniquely identifies a REST API contract. Hence, a published contract with a version is immutable. Any changes to the contract should create a new version of the contract.

  • The version of the contract is a single number. This number is greater than all previously published contracts for the specific service.

  • Endpoints marked as deprecated in a specific contract are not usable (e.g. not generated) by the generated API client.

    • The API provider does still continue to provide the deprecated endpoints until they are completely removed from the contract. This is to satisfy consumers with older API client versions once the provider deprecates the endpoint and to provide them with a transition period to upgrade to a newer API client.

Making changes to the API contract

The contract of the service needs to evolve. However, this needs to be done without breaking it for the consumers.

Adding a new endpoint

This is always allowed (tick) as it’s syntactically a non-breaking change.

Delete an endpoint

Deleting an endpoint is the same as removing a functionality which the provider contractually said it will provide. In that sense, it’s always a breaking change. However, the following rules were designed to handle the change in a non-breaking fashion.

  • When an endpoint is marked deprecated, the endpoint has to contain an extension field named x-deprecated-version with value specifying the version when it was deprecated. E.g. x-deprecated-version: "17"

    • The version in x-deprecated-version must be the same as the version of the newly changed contract.

  • An endpoint can be removed if the following holds:

    • it is marked as deprecated

    • the API contract versions of clients of this service in PROD have their version greater or equal to the x-deprecated-version in the endpoint to be removed.

  • The consumers which generate API client from a contract will not be able to use the deprecated endpoints. This ensures that after the clients are upgraded above a certain version in the environment, the provider can safely remove the endpoint.

How to add the custom field

You can add the custom x-deprecated-version field by leveraging the Extensions from Swagger annotations:

@Operation(
    extensions = [
        Extension(
            properties = [
                ExtensionProperty(
                    name = "deprecated-version",
                    value = "17" // The version must match the new version of the contract
                )
            ]
        )
    ],
    deprecated = true
)

Later on there will be a custom annotation for this specifically, making it more concise and convenient to use.

Editing and endpoint

If the edit the provider is making is:

  • Non-breaking change - allowed (tick)

  • Breaking change - not allowed (error) In this case, the provider should create a new endpoint and follow the guideline above for removing the old endpoint.

Automated checks for contract changes

An automated contract test will run which tests whenever the contract has changed:

  1. If there are any changes, the version field needs to be increased

  2. For each endpoint, test if a new deprecated property was added

    • If it was added, verify if x-deprecated-version is equal to the version field

  3. Test for breaking change made in the contract using available tools (TBD).

    • If there are breaking changes, then:

      • If an endpoint is removed, verify if the endpoint was marked as deprecated

        • If not, test fails (error)

        • If yes, fetch the current client versions of all of the service API clients, and verify whether each of their api client versions in PROD are greater or equal to the x-deprecated-version in the to be removed endpoint.

      • If an endpoint is changed breakingly, the test fails (error)

Promoting the service to STG and PROD

Currently, the promotion to a higher environment is done by manually by hand. Also, the check for whether the promotion is safe to do or not will be manual in the first iteration.

A service should not be deployed to an environment if it consumes an API of a version which is not yet supported by the provider in that environment (i.e. it should prevent that consumer upgrades faster than the provider)

A simple script will be developed which:

  1. Fetches the API contract versions of the providers and the API contract versions of the clients for deployed services in DEV & STG or STG & PROD

  2. Returns an ASCII table which states for each service whether or not can it be promoted to the next environment.

Example of the table returned for upgrade from DEV to STG:

Such script is already available in https://github.com/SafiBank/SaFiMono/tree/main/common/deploy-checker-script

Consumer service (DEV)

Provider

Required provider version

Provider version (STG)

Safe upgrade

transaction-processing-manager

account-manager

17

21

(tick)

transaction-processing-manager

product-manager

8

5

(error)

customer-manager

account-manager

19

21

(tick)

…. (one row per consumer - provider connection)…