Backward Compatibility
About Backward Compatibility
If an API provider implementing a change to a contract would become incompatible with existing consumers due to the change, the changes to the contract are NOT backward compatible.
Aim to make all changes to a contract backward compatible, to ensure that an updated API provider can be deployed as soon as it is ready, without waiting for consumers to catchup.
Specmatic can provide instant feedback when a change to an API provider will break consumers by looking at the old and new contract. This feedback only requires the contract. No code needs to be written, saving the provider the effort of writing code.
Watch this video to see it in action. Read on and even try it out yourself!
Comparing Two Contracts (Contract vs Contract)
Create a file named api_products_v1.yaml.
# filename api_products_v1.yaml
openapi: 3.0.0
info:
title: Sample Product API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://localhost:8080
description: Local
- url: http://localhost:9000
description: Specmatic Stub Server
paths:
/products/{id}:
get:
summary: Get Products
description: Get Products
parameters:
- in: path
name: id
schema:
type: number
required: true
description: Numerical Product Id
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: string
This contract contains an API for fetching the details of a product.
Let’s add a new api to create a product record:
# filename api_products_v1-2.yaml
openapi: 3.0.0
info:
title: Sample Product API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://localhost:8080
description: Local
- url: http://localhost:9000
description: Specmatic Stub Server
paths:
/products/{id}:
get:
summary: Get Products
description: Get Products
parameters:
- in: path
name: id
schema:
type: number
required: true
description: Numerical Product Id
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: string
/products:
post:
summary: Add Product
description: Add Product
requestBody:
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: string
nullable: true
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- id
properties:
id:
type: integer
The old /products/{id} API remains intact, and the new /products API is added on.
The newer contract is backward compatible with the older, as existing consumers are only using the old API, which remains unchanged.
Run the specmatic compare command to confirm this, and see the result:
> java -jar specmatic.jar compare api_products_v1.yaml api_products_v1-2.yaml
The newer contract is backward compatible
Let’s change the original contract of square to return “sku” as a numeric value instead of string in the response:
# filename api_products_v2.yaml
openapi: 3.0.0
info:
title: Sample Product API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://localhost:8080
description: Local
- url: http://localhost:9000
description: Specmatic Stub Server
paths:
/products/{id}:
get:
summary: Get Products
description: Get Products
parameters:
- in: path
name: id
schema:
type: number
required: true
description: Numerical Product Id
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: integer
Note that the file name of the above file is api_products_v2.yaml.
Now try it again:
> java -jar specmatic.jar compare api_math_v1.yaml api_math_v2.yaml
In scenario "Get Products. Response: Returns Product With Id"
API: GET /products/(id:number) -> 200
>> RESPONSE.BODY.sku
This is number in the new contract response but string in the old contract
The newer contract is not backward compatible.
Specmatic will show you an error message, saying that the change is not backward compatible. The reason for this is that existing consumers are expecting a string “sku”, but will get an “integer” instead.
If the change is not backward compatible, the compare command exits with exit code 1. You can use this in a script.
Validating Changes In Git On Your Laptop
If api_products_v1.yaml is in a git repository, and the change is backward compatible, make the change directly to the v1 file instead of creating a new one.
Then to confirm that it is a backward compatible change, before committing the change, run this command:
java -jar specmatic.jar compatible git file ./run/specmatic/examples/api_products_v1.yaml
This command exits with exit code 1 if the change is backward incompatible. It can be configured as a git pre-commit hook.
Validating Changes In CI
In CI, you will need to compare the changes in a contract from one commit to the next.
You can do this with the following command:
> java -jar specmatic.jar compatible git commits api_products_v1.yaml HEAD HEAD^1
You can even use commit hashes here if you wish to compare any other pair of commits.
This command exits with exit code 1 if the change is backward incompatible.
Handling Contracts In Progress
APIs whose design is still in progress can be tagged WIP in the OpenAPI contract. Specmatic will not break builds or return failure on when it see backward incompatible changes to WIP APIs. It will still print the error feedback.
# filename api_products_v1.yaml
openapi: 3.0.0
info:
title: Sample Product API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://localhost:8080
description: Local
- url: http://localhost:9000
description: Specmatic Stub Server
paths:
/products/{id}:
get:
summary: Get Products
description: Get Products
parameters:
- in: path
name: id
schema:
type: number
required: true
description: Numerical Product Id
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: string
/products:
post:
summary: Add Product
description: Add Product
requestBody:
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
sku:
type: string
nullable: true
responses:
'200':
description: Returns Product With Id
content:
application/json:
schema:
type: object
required:
- id
properties:
id:
type: integer
Once the contract is complete you can remove the WIP tag.
Backward Compatibility Rules
Maintaining backward compatibility is about changing the API provider WITHOUT breaking any existing consumer. Consumers should just continue working as-is, without needing to “keep up”.