All event-sourcing patterns
Production pattern430 words · verified 10 September 2026

Event Schema Evolution Without Rewriting History

Evolve event schemas by preserving stored facts, registering versioned contracts, making compatible additive changes, and translating old payloads at read time when semantics change. Never rewrite production history merely to match newest application model.

Problem

Why this pattern exists

Events outlive services that wrote them. A field rename, unit change, split concept, or stricter required value may break projection rebuild years later even when live traffic appears healthy. Database migration that edits old event payloads damages auditability and makes historical evidence depend on mutable scripts.

Schema shape and event meaning are separate. Adding optional field is often structurally backward compatible; changing money from pounds to pence may keep JSON number type while changing semantics. Compatibility review needs examples, ownership, and replay tests—not validation keyword alone.

Design decisions

Make boundaries explicit

  1. 01

    Prefer additive evolution

    Add optional fields with deterministic defaults for old events. Keep old readers working during deployment overlap and avoid reusing a field name for different meaning.

  2. 02

    Create new event for new meaning

    When business fact changes materially, publish new event type or major schema version. Document relation to predecessor instead of forcing incompatible payload under familiar name.

  3. 03

    Upcast at boundary

    Translate historical representation into current in-memory shape before reducer. Upcaster must be deterministic, side-effect free, version-aware, and tested against captured fixtures.

AllSource implementation

Apply pattern to durable Core history

AllSource Core includes schema registry with subjects, versions, validation, and None, Backward, Forward, or Full compatibility modes. Register contract for each governed event type and validate producer payloads before acceptance. Tag schema versions and owners so operators can trace why a change was allowed.

Store original event unchanged. Consumer identifies schema or event version, runs required upcasters, then applies current reducer. Before release, replay representative histories—including oldest retained payloads—into new projection version. If semantic default cannot be derived from event itself, emit compensating enrichment fact rather than querying today's mutable external state during replay.

Compatible order event evolution
v1 order.placed { "total_pence": 14999 }
v2 order.placed { "total_pence": 14999, "currency": "GBP" }

read v1 -> default currency from recorded tenant contract version
read v2 -> use explicit currency
stored v1 payload remains unchanged

Failure modes

Detect weak implementations early

New required field makes historical replay fail.

Fix: Make field optional with deterministic default or introduce new version and upcaster.

Same event type silently changes business meaning.

Fix: Create distinct fact or major contract version and document transition.

Migration rewrites old payloads and loses original evidence.

Fix: Preserve source event; translate on read or emit explicit correction event.

Production checklist

Ready when each statement is true

  • Every governed event has owner and registered subject.
  • Compatibility mode matches deployment and reader requirements.
  • Semantic changes receive explicit version or event type.
  • Upcasters are deterministic and covered by historical fixtures.
  • Full projection replay passes before producer rollout completes.

Related patterns

Store history once

Rebuild every useful view from durable events.