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 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 namedx-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 theversion
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 thex-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
Breaking change - not allowed 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:
If there are any changes, the
version
field needs to be increasedFor each endpoint, test if a new
deprecated
property was addedIf it was added, verify if
x-deprecated-version
is equal to theversion
field
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
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 thex-deprecated-version
in the to be removed endpoint.
If an endpoint is changed breakingly, the test fails
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:
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
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 |
|
transaction-processing-manager | product-manager | 8 | 5 |
|
customer-manager | account-manager | 19 | 21 |
|
…. (one row per consumer - provider connection)… |