CONCEPT Cited by 1 source
Declarative lifecycle API¶
Definition¶
A declarative lifecycle API is a contribution-surface design where a component author describes a fixed set of named phases (e.g. data-dependencies, processing, rendering) but does not execute them — the framework owns execution, ordering, input/output plumbing, and error handling. The author's code reads as configuration, not control flow. The framework is free to reorder, parallelise, cache, memoise, stream, and short-circuit the lifecycle because it sees all the pieces.
Canonical form (Zalando Rendering Engine)¶
Zalando's Rendering Engine exposes each Renderer as a fluent
interface (see also: chained-builder pattern) where
successive .withX(...) calls register functions for each
lifecycle step
(Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2):
export default tile()
.withQueries(({ entity: { id } }) => ({
carousel: { query, variables: { id } },
}))
.withProcessDependencies(({ data }) => {
if (data === null) {
return { action: "error", message: "No collection data found." };
}
return {
action: "render",
data,
tiles: { entities: getCollectionEntities(data) },
};
})
.withRender((props) => {
// React component for the root output
});
Three lifecycle slots are canonical:
withQueries— declares GraphQL data dependencies. The framework fetches them before subsequent slots run.withProcessDependencies— given fetched data, chooses an action (render / redirect / error) and computes downstream inputs including child entities to recur into.withRender— returns the React component tree to render.
The author writes three callbacks. The framework runs them, in order, with the right inputs, and handles everything in between — data fetching, error handling, child-entity resolution, streaming, hydration, A/B test tracking.
Why the framework benefits¶
Because the framework sees the whole lifecycle up front, it can:
- Fetch data up front, not inside render. No "render, then fetch, then re-render" waterfalls.
- Batch requests across Renderers in the same tree (see DataLoader).
- Stream HTML as each Renderer's output becomes ready (see concepts/streaming-ssr), without the Renderer author needing to know anything about streaming.
- Isolate failures — a Renderer whose lifecycle returns
action: "error"can be swapped for an error card without collapsing the parent tree. - Cache or memoise at any lifecycle boundary without the author's cooperation.
- Insert platform concerns (monitoring, A/B testing, tracking, Web Vitals) around the lifecycle invisibly.
The inversion is: the author says what the unit needs and what it outputs; the framework decides when and how.
Why the contribution surface benefits¶
- Small, learnable API — three named hooks, each with a well-defined input/output contract.
- Typed end-to-end — in Zalando's case, TypeScript types
flow through
withQueries→withProcessDependencies→withRender;propsin the render function is inferred. - No ceremony — the author doesn't import a framework context, wire up a reducer, or subscribe to a lifecycle bus. They just provide callbacks.
- Platform features land for free — Web Vitals, logging aggregation, OpenTracing are wired in at the framework level with "zero-integration time for the Renderer developers" (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2).
Contrast with imperative alternatives¶
- React function components with hooks: imperative —
author calls
useState,useEffect, does fetches inline, owns when data arrives. Powerful but burdens the author with orchestration. The framework cannot know ahead of time what data a component needs. - Server components / RSC: partially declarative — the server can fetch data before render, but the author still writes the fetch in imperative JS/TS.
- Redux-style saga/effects: declarative intent
(
put,call), but spread across an event-driven bus divorced from the component that needs the data. - Zalando
tile()lifecycle: fully declarative — the component configuration tells the framework everything it needs, and rendering is just one of several hooks.
Tradeoffs¶
- Expressiveness ceiling. Once the lifecycle is fixed,
anything not accommodated has to ride in a generic
escape hatch (React state via hooks inside
withRender, or framework-providedRenderer Statefor re-running lifecycle). - Framework becomes load-bearing. Authors no longer own their data-loading path; the framework's fetch layer is a hard dependency.
- Debugging is harder. "Why didn't my data arrive?" may live inside the framework, not the author's code.
Seen in¶
- systems/zalando-rendering-engine — canonical wiki
instance. The
tile()fluent-interface API withwithQueries/withProcessDependencies/withRenderlifecycle. (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2.)