← Concepts

阅读中文版本

Backward Compatibility

Backward compatibility lets new code read old data and new clients work with older servers, enabling independent deployment and safe rollback.

architecture

Definition

Backward compatibility means a new version of a system can consume data, requests, or APIs produced for an older version. In APIs, adding optional fields and endpoints is compatible; removing fields or changing types is not. In data, new code must read old records or events. In distributed systems, compatibility allows old and new versions to run at the same time during a rolling deployment or rollback.

Context and scope

Backward compatibility is essential for independent service deployment, mobile clients that cannot update instantly, event replay, and database migrations. It is a property of a change, not a permanent guarantee for every change.

Common misconceptions

  • Backward compatibility means never changing anything — additive changes and versioned deprecations are compatible
  • If clients update automatically, compatibility does not matter — mobile and third-party clients update slowly or never
  • A new API version makes the old one irrelevant — old versions still need security fixes and a deprecation plan

Anti-patterns

  • Removing a response field without a deprecation period
  • Changing a field from optional to required in the same version
  • Deploying a schema change that old application instances cannot read

Related concepts

Related examples

Authoritative source

https://martinfowler.com/bliki/TolerantReader.html

Compatibility is about mixed versions

During a rolling deploy, old and new instances run together. A mobile client may use an API version for months. An event replay may process records written years ago. Backward compatibility ensures the new code can handle the old shape and the old code can survive the new one, depending on the direction required.

type User = { id: string; name: string; avatarUrl?: string };

function render(user: User) {
  return user.avatarUrl ?? '/default-avatar.png';
}

The optional field and default keep old payloads renderable. Removing name or changing id to a number would break existing clients.

Add, deprecate, remove

Prefer additive changes: new optional fields, new endpoints, new event types. Announce deprecations with a timeline, instrument usage, and remove only after the old version is no longer used. For breaking changes, introduce a new version and keep the old one available for a defined period. Do not silently change semantics under the same name.

Test the old shape

Contract tests, replay tests, and golden fixtures should include historical payloads. A test that only uses the newest shape will miss a compatibility break. For databases, test a migration with old application code reading the migrated schema. For events, keep a corpus of old events and verify upcasting.

When to use it

Use backward compatibility when independent clients, rolling deployments, or long-lived data exist. The advantage is safe evolution and rollback; the drawback is the discipline of versioning and the cost of supporting old versions. Compared to a coordinated big-bang upgrade, compatibility is slower to remove old paths but avoids synchronized outages.