Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Backward-Compatible Events
HLD

Backward-Compatible Events

The concrete rules of never-breaking-consumers — field-by-field what's safe, what's not, and why.

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.
ChangeBackwardForwardVerdict
Add optional fieldsafe
Add required fieldforbidden
Remove optional✓*after census
Widen number type⚠ lang-dependentusually ok
Narrow typeforbidden
Add enum value✓ w/unknown-handling✗ for strict readerscoordinate

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.

My Private Notes

Notes are auto-saved locally to this device.