Every deployment is a distributed system migration. For a few minutes, old and new versions of a service run simultaneously. If the new version changes an API contract, a database schema, or a message format, those few minutes can corrupt data.
The Rule
Never deploy a change that breaks the previous version. Every deployment must be backward-compatible with the version it is replacing. This means:
- API changes: Add new fields, never remove or rename existing ones. Consumers of the old version must be able to read responses from the new version and vice versa.
- Database migrations: Add columns, never drop them in the same deployment. Drop columns in a separate deployment after all services have stopped reading them.
- Message formats: Use schema evolution (Avro, Protobuf) or always include a version field in JSON messages.
How to Enforce It
A pre-deployment check in the CI pipeline compares the OpenAPI spec of the new version against the previous version. It fails if any breaking change is detected: removed endpoints, removed fields, changed types, or renamed parameters.
For database migrations, Flyway scripts can run in a "validate-only" mode during PR review. The reviewer sees exactly which columns are added, altered, or dropped. Any migration that drops a column should include a link to the previous migration that removed all readers.
The Escape Hatch
Sometimes a breaking change is unavoidable. When that happens, the expand-contract pattern works: deploy the new version alongside the old one, migrate all traffic to the new version, then remove the old one. This takes multiple deployments instead of one, but it is zero-downtime and zero-data-loss.