Skip to content

CONCEPT Cited by 1 source

Component version migrate function

Definition

A component version migrate function is an explicit, per-component function — typically auto-generated as a stub but implemented by developers — that backports an instance of a newer major version of a component to the previous major version. It is the codified hook that keeps old clients working when a newer version of a component is live on the backend.

def migrate(model: ComponentV<N+1>) -> ComponentV<N>:
    """Backport V(N+1) to V(N)."""
    ...

Callers: the serialisation runtime, when it determines the client cannot accept the newer version (see concepts/client-spec-version).

Canonical wiki instance (Yelp Konbini)

Konbini's example. Yelp wants to change Button's text parameter from String to a custom FormattedText supporting http links. This is a breaking change (version 0.8 → 1.0); older clients would receive a type they can't decode. Konbini auto-generates an empty migrate() method on CookbookButtonV1:

def migrate(model: CookbookButtonV1) -> CookbookButtonV0:
    """
    The migrate function defined here is meant to backport an
    instance of CookbookButtonV1 to an instance of
    CookbookContainerV0. Its default behavior is to throw an
    error signifying that backporting the model is not
    supported. However, we highly recommend replacing this
    behavior with your own custom logic ...
    """
    return CookbookButtonV0(
        text=model.text.toString(),       # FormattedText -> String
        style=model.style,
        on_click=model.on_click,
        size=model.size,
        background_color=model.background_color,
    )

Yelp: "This method is called whenever the spec version on the client is lower than the one on the backend. In that case, the backend tries to instantiate a CookbookButtonV1 class but it realizes the client only supports CookbookButtonV0."

Design properties

  • Default is fail-safe. Generated default throws. If developers forget to implement migrate(), the backend errors loudly rather than silently sending an incompatible object; the CHAOS feature gets dropped via the feature error wrapper rather than crashing the view.
  • Explicit opt-out required. Choosing not to support older clients for a given feature is an explicit act — leaving migrate at default-throws. Yelp's framing: "highly recommend replacing this behavior".
  • Field-level responsibility. Migration logic is per-field. Converting FormattedText → String is a .toString(); other conversions may need summarisation or data loss. The developer knows the domain; the codegen provides the plumbing.
  • Chain-free default. The post shows only V1 → V0. It doesn't state whether V2 → V0 is expected to chain (V2 → V1 → V0 via composed migrates) or be a separate pairwise function — a caveat worth watching.

When migrate functions don't apply

  • Non-breaking additions. Adding a new optional parameter is non-breaking; no migrate needed. The field is absent on old clients; they ignore it.
  • Enum additions. A new enum value is breaking only if older clients would misinterpret it. Usually handled with explicit unknown-case mapping rather than a migrate.
  • Deprecations without type change. If a field is deprecated but still sent, migrate is unnecessary; it's just stale data.

Contrasts

  • Protobuf / Thrift / Avro field compatibility — traditionally solved by "add fields, don't remove, reserve tags". Migrate functions are the richer escape hatch when type change is unavoidable.
  • GraphQL directive-based field lifecycle — see patterns/directive-based-field-lifecycle. GraphQL deprecates fields; it does not provide a server-side per-client backport the way migrate does.
  • API versioning (v1 vs v2 endpoints) — whole-endpoint versioning is coarser than per-component major-version migration; Konbini migrate is finer-grained.

Seen in

Last updated · 550 distilled / 1,221 read