The Consumer’s Perspective
design every change for the WEAKEST reader:
old consumer code:
order = parse(event)
ship(order.address) ← reads ONLY address
new event adds tax_amount: old consumer unaffected ✓
new event RENAMES address→shipping_address:
old consumer reads .address → null → SHIPS TO NOWHERE ✗
consumers don't upgrade on your schedule.
compatibility = old readers survive ALL future writes.
Field-Level Rules
ADDING a field:
✓ with DEFAULT value (consumers ignore unknown; defaults fill)
✓ as OPTIONAL/nullable
✗ REQUIRED without default — old events lack it;
new consumer can't parse OLD history (breaks backward!)
REMOVING a field:
✓ only after census: zero readers confirmed
(stop-writing-first grace period helps detect stragglers)
✗ immediately — some consumer somewhere reads it. always.
CHANGING type/meaning:
✗ int→string, cents→dollars, status enum values redefined
→ NEW FIELD instead (amount_cents_v2), deprecate old
RENAMING:
⚠ alias support in avro/protobuf makes it safe-ish;
JSON world: add-new + stop-old is the path.
The Enum and Nesting Traps
ENUMS (status: PENDING|PAID):
- ADDING a value breaks exhaustive switches downstream!
old consumer: match(status) → PAID_REFUNDED → crash/default-wrong
- protocol: reserve handling for UNKNOWN values explicitly
(else-branch that queues for human/DLQ rather than guessing)
NESTED structures:
- same rules recurse: additive inside nested objects ✓
- restructuring (flat→nested) = breaking; wrap via new versioned type
LISTS: adding elements fine; reordering semantics must be
documented or consumers break silently (order-dependent logic).
Testing Compatibility For Real
the check that matters: CROSS-VERSION MATRIX
for each producer version P and consumer version C:
can C consume P's output? including HISTORY replay?
CI implementation sketch:
□ golden event samples per schema version checked in
□ consumer tests run against ALL historical samples
□ registry blocks incompatible publishes pre-merge ✓
□ consumer contract tests pin fields they actually read
this turns "we think it's compatible" into a gate.
| Change | Backward | Forward | Verdict |
|---|---|---|---|
| Add optional field | ✓ | ✓ | safe |
| Add required field | ✗ | ✓ | forbidden |
| Remove optional | ✓* | ✓ | after census |
| Widen number type | ✓ | ⚠ lang-dependent | usually ok |
| Narrow type | ✗ | ✗ | forbidden |
| Add enum value | ✓ w/unknown-handling | ✗ for strict readers | coordinate |
Interview Framing
“Add a field to an event consumed by 14 services” scored answer: default-valued optional addition (safe both directions), rolling-deploy coexistence explained, enum-style caution if it’s a union/enum, removal path documented for later (census+deprecation), registry/CI enforcement named. Fluency here signals someone whose event systems stay up for years — rare and valued.
Premium Content
Unlock Backward-Compatible Events and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans