Deployment Independence: Versioning and Backward Compatibility
What This Concept Is
Independent deployability (concept 01) is not a property of pipelines; it is a property of contracts. You can have separate CI/CD for every service and still be stuck in lockstep if any API change breaks a consumer.
Deployment independence means all of the following hold:
- A producer can deploy a new version without any consumer deploying simultaneously.
- During the transition, old and new consumers can coexist, each working with either old or new producers.
- Breaking changes are never rolled out in place; they are rolled out as a new version coexisting with the old, with a deprecation window.
The rule of thumb: at any moment in production, any two adjacent versions of a producer must work with any live consumer version.
Why It Matters Here
Without this property, microservices impose all the costs of distribution and deliver none of the benefits. The shortest path to losing deployment independence is treating contract changes casually -- one renamed field, and three teams have to coordinate a release window.
Compatibility Types
| Kind | Definition | Example |
|---|---|---|
| Backward compatible | New version works with old consumers | Add optional field, add new endpoint |
| Forward compatible | Old version works with new consumers (consumer ignores unknown fields) | Tolerant reader, unknown enum handling |
| Breaking | Either direction fails | Rename or remove field, change type, tighten validation, change URL |
The target is both backward and forward compatible for every change. When that is impossible, version and coexist.
Safe and Unsafe Changes
Safe (ship freely):
- Add a new endpoint.
- Add a new optional request field (producer must default it).
- Add a new response field (consumers using tolerant readers ignore it).
- Relax a validation (accept more inputs).
- Add a new event type.
- Add a new optional event field.
Unsafe (requires versioning or migration):
- Remove a field, endpoint, or event.
- Rename a field.
- Change a field's type (e.g., string -> int).
- Tighten a validation (reject inputs the old version accepted).
- Change the meaning of a field (semantic breaks are the worst kind -- the shape passes tests, but consumers misbehave).
- Add a required request field without a safe default.
Versioning Strategies
For sync APIs:
- URI versioning (
/v1/orders,/v2/orders). Loud, clear, easy to route at the gateway. Default in REST. - Header versioning (
Accept: application/vnd.orders.v2+json). Cleaner URLs, harder to discover, ignored by caches if misconfigured. - No versioning with strictly additive evolution. Works only when the whole org has strong tolerant-reader discipline.
For events:
- Include an
event_versionfield in every event. - When the new version is not backward compatible, publish both the old and new event types for a deprecation window, marking the old one deprecated.
- Consumers migrate at their own pace during the window. After the window closes (with all consumers off the old type -- verified in the broker or with CDC), retire the old event.
Expand-Contract (Parallel Change) Pattern
The canonical way to make a "breaking" change without breaking anyone:
- Expand. Add the new thing alongside the old. Both endpoints/fields/events exist.
- Migrate consumers. One by one, consumers move to the new version. Use CDC tests to know when everyone has moved.
- Contract. Remove the old thing, once no consumer is using it.
For example, renaming a field user_id -> customer_id in a response:
- v2.0: response contains both
user_idandcustomer_id(same value). Safe. - v2.1..v2.9: consumers switch to reading
customer_id. Each consumer's pact now expectscustomer_id. - v3.0:
user_idis removed.
At no point is any consumer broken.
Concrete Example: A Safe Rollout
Orders service adds the partially_shipped status value. Consumers currently handle pending, confirmed, cancelled, shipped.
- Pre-rollout. Update consumers with tolerant enum handling: unknown values are treated as a safe default (e.g., "unknown", don't crash).
- Verify with CDC. Every consumer's pact test now tolerates unknown enum values. CDC passes.
- Producer rollout. Orders begins emitting
partially_shippedwhere appropriate. No consumer crashes. - Consumer upgrade (as needed). Consumers that actually benefit from distinguishing
partially_shippedupdate their logic and ship independently.
Deployment Mechanics That Support Independence
- Blue/green or canary deploys. New version lives alongside old; traffic shifts gradually; easy rollback.
- Feature flags. Ship code inactive; enable per-cohort; decouple deploy from release.
- Schema migrations with expand-contract for databases, mirroring the pattern above.
- Independent pipelines per service. A red build in one service never blocks another.
Common Confusion / Misconception
"We just add versioning and we are fine." Versioning without tolerant readers and without CDC is still fragile. The three work together.
"Old versions are someone else's problem." No. The producer team owns both the old and new versions through the deprecation window. If that feels expensive, deprecation windows are too long; shorten them, but do not eliminate the overlap.
"Backward compatibility means never deleting anything." Deletion is fine -- it just happens in the "contract" phase of expand-contract, after the consumers have migrated.
How To Use It
- For every contract change, classify it as safe or unsafe.
- For unsafe changes, use expand-contract. Write down the three phases and the success criteria for moving between them.
- Use CDC tests (concept 09) to know when all consumers have migrated.
- Set a default deprecation window (e.g., 2 sprints) and stick to it.
- Prefer ship-often, small changes. Big bangs invite lockstep deploys by accident.
Check Yourself
- Why is independent deployability a property of contracts, not of pipelines?
- Explain expand-contract in terms a non-technical product manager would accept.
- What is the difference between backward compatibility and forward compatibility, and which one relies on tolerant readers?
Mini Drill or Application
Take a sync contract from concept 08. In 15 minutes, write the expand-contract plan for a field rename:
- Phase 1 (expand): what the response looks like.
- Phase 2 (migrate): how you know all consumers have moved.
- Phase 3 (contract): the removal step and its rollback plan.
How This Sits In The Module
This is where the previous 13 concepts pay off: bounded contexts gave you clear edges, data ownership gave you safe schemas, contracts and CDC gave you enforceable shapes, and now versioning + expand-contract lets those edges move without anyone tripping.
Database Expand-Contract
The same pattern applies to database schema migrations, where the stakes are higher (data is persistent). For a column rename users.email_addr -> users.email:
- Expand. Migration adds the new column
email. Application code writes to both columns (dual-write). Reads continue fromemail_addr(the authoritative column). No behavior change. - Backfill. Batch job copies
email_addr->emailfor all existing rows. Dual-write continues. - Switch reads. Application starts reading from
email. Dual-write continues for safety. - Stop writing the old column. Migration removes the write to
email_addr. Schema still has both columns; the old one is now stale but harmless. - Verify no reader remains. Use query logs or a "deprecated column" annotation to confirm.
- Contract. Drop
email_addrin a final migration.
Each step deploys independently. Rollback is safe at every point. This maps directly onto the expand-contract pattern for APIs above; see also Pramod Sadalage and Scott Ambler's Refactoring Databases for the full catalog.
Continuous Delivery Preconditions
Deployment independence relies on a working continuous delivery pipeline (Jez Humble, David Farley, Continuous Delivery, 2010). Concretely:
- Trunk-based development or short-lived feature branches (no long-running release branches).
- Every commit is a release candidate. No "this commit is QA, this commit is prod" distinction.
- Automated tests at each tier (unit, contract, component, a small number of end-to-end).
- Deploy automation that is idempotent, observable, and fast (< 15 min from merge to prod for typical services).
- Feature flags to decouple deploy from release (see Pete Hodgson's Feature Toggles).
Without these, versioning discipline alone does not deliver deployment independence; the pipeline becomes the bottleneck even if the contract is clean.
Read This Only If Stuck
Local chunks
- FoSA: Engineering Practices -- CI/CD baseline.
- FoSA: Operations / DevOps -- deployment mechanics.
- FoSA: Fitness Functions -- compatibility checks are fitness functions; CDC is one such.
- FoSA: Architecture Decisions and FoSA: Architecture Decision Records -- every breaking change should produce an ADR with deprecation plan.
- FoSA: Connascence -- the coupling vocabulary for why renaming a field is a connascence-of-name break.
- Primer: RDBMS and Replication -- background for blue/green DB migrations.
- Primer: Consistency Patterns -- the consistency model under which dual-write works.
External canonical references
- Martin Fowler, ParallelChange -- the expand-contract pattern.
- Martin Fowler, BlueGreenDeployment -- the pattern that makes the rollout safe.
- Martin Fowler, CanaryRelease -- percentage rollout.
- Pete Hodgson (martinfowler.com), Feature Toggles -- comprehensive treatment.
- Chris Richardson, Microservice chassis (versioning and deployment).
- Jez Humble, Continuous Delivery (site) -- principles and patterns summary.
- Google, API Design Guide -- Compatibility -- practical safe-change list used by many vendors.
- AWS, Database migration strategies -- enterprise framing of expand-contract.
- ThoughtWorks Technology Radar, Trunk-based development -- positioning.
Depth Path
- Jez Humble and David Farley, Continuous Delivery -- the underlying engineering practices. Return if your pipelines are still slow or flaky.
- Pramod Sadalage and Scott Ambler, Refactoring Databases -- the full catalog of expand-contract-style schema refactorings.
- Dave Farley, Modern Software Engineering (2021) -- the updated version of the CD argument, with microservices-era examples.