Skip to content
Language:en Author:Laborci Gergely
distributed-systems

Communication Contract and Coexisting Versions

Objectives

By the end of the lesson, you can distinguish the layers of a contract, recognise compatible and breaking changes, and explain the expand–migrate–contract method.

Why is it needed?

Services can be released independently only if old and new versions work together for a while. A contract is therefore more than a list of fields: it states the meaning of the operation, the limits of the input, the output, the errors and the permitted repetition.

The layers of a contract

Layer What does it describe? Example
Syntactic The format: field types, required fields, encoding price is a number
Semantic What the values mean price is understood in dollars, gross
Behavioural The side effects and conditions Does reserve only check or also reserve; until when is it valid; what happens on repetition
Operational Time limit, message size, load limit Maximum request size

The type does not imply the interpretation: the same number can mean dollars, cents or an internal unit.

Compatible and breaking changes

A new optional field is generally easier to introduce than renaming an old required one. Compatibility, however, also depends on the consumer's behaviour: a strict decoder may reject an unknown field, and a new enumeration value can cause trouble if the client treats the possible values as closed. According to Fowler's tolerant reader pattern, the consumer reads only what it needs and ignores the rest, so the provider breaks clients in fewer cases. The pattern rests on Postel's law: be conservative in what you send and liberal in what you accept.

A semantic change is the most dangerous: if the total earlier meant gross and later means net, the schema can remain unchanged and cooperation still breaks. Contract checking therefore has to examine behaviour too.

Parallel change: expand, migrate, contract

According to Fowler's description, a backward-incompatible change can be made safely in three phases:

  1. Expand: the new version is added next to the old; existing clients keep working.
  2. Migrate: clients are moved gradually to the new one; for external consumers this is usually the longest phase.
  3. Contract: when nothing uses the old one any more, it is removed.
flowchart LR
  E["Expand:<br/>old and new together"] --> M["Migrate:<br/>clients move over"] --> C["Contract:<br/>remove the old"]

The risk is that the provider maintains two versions and the contraction never happens. For a renamed field both fields may be present temporarily; it must be stated which one is authoritative, and what happens with contradictory values.

Contract checking

The provider can check that its response conforms to the published schema and examples. A consumer-driven contract test records the actual expectations of the consumer: according to the Pact description, the consumer's tests produce the contract in the form of concrete request–response examples, so they check only what the consumer actually uses. According to the Pact site this does not replace the schema (that is a static description of the possible states), and the provider checking itself against its own contract does not replace the consumer's test either. A response valid according to the schema can be a wrong business result, and a compatible API can still be unreachable because of bad routing.

Common misconceptions

Statement Clarification
"If the schema does not change, the contract does not either." A change of meaning can also be breaking.
"Adding a new field is always safe." With a strict client it can cause a break.
"A version number solves compatibility." It only separates the versions.
"Checking the schema is enough." Behaviour and integration must be examined too.

In short

  • A contract has syntactic, semantic, behavioural and operational layers.
  • Compatibility also depends on the consumer's behaviour; the tolerant reader reduces breakage.
  • Expand–migrate–contract: extending, moving over, narrowing.
  • Consumer-driven contract tests check the actual expectations.

Review questions

  1. What is the difference between a syntactic and a semantic contract?
  2. Why can a new enumeration value be breaking?
  3. What must be stated in the transitional period of a renamed field?

Learning resources

Prerequisites (1)

  • remote call

Teaches (3)

  • communication contract
  • API compatibility
  • parallel change