CONCEPT Cited by 1 source
Recursive entity tree resolution¶
Definition¶
Recursive entity tree resolution is the tree-walk algorithm implemented by entity-based page composition runtimes: take a root Entity, resolve it to a Renderer via the rendering rules, run the Renderer's lifecycle, extract any child Entities the Renderer emits, and recur into each child — producing a tree of Renderer invocations from a tree of Entities.
Canonical instance is Zalando's Rendering Engine as disclosed in the Part 2 post (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2).
The algorithm¶
resolve(entityNode, renderingRules):
renderer = pickRenderer(entityNode.type, entityNode.context, renderingRules)
data = await runLifecycle(renderer, entityNode) # withQueries + withProcessDependencies
markup = renderer.withRender(data) # React element tree
for each childEntity in data.tiles.entities: # entities the renderer "suggests"
resolve(childEntity, renderingRules) # recur; does NOT block parent's stream
emit(markup) # stream to client as soon as ready
Two properties make this a tree resolution rather than a flat render:
- The Renderer chooses child Entities, not child Renderers. Per the post: "a given renderer does not know which renderers will be used for its child entities". The Renderer emits Entities; the rules map them to Renderers.
- Rendering rules are themselves hierarchical. The
rule shape has
children: [...]sub-rules that scope Entity→Renderer mapping to the placement context (e.g. "an Outfit appearing under a Collection under an OutfitView is rendered as a small outfit-card, not a main view").
Non-blocking behaviour¶
"We do not block the resolution process. As soon as the entity is matched to a Renderer and the data resolved, the Rendering Engine kicks off the rendering process and starts streaming the HTML content to the client." (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2.)
The consequence — each Renderer's HTML can be streamed as its subtree resolves, without waiting for the deepest leaves. This is the streaming SSR property the Rendering Engine exposes to the Web; the same tree walk is also how the Native-apps JSON is produced by the custom reconciler path (see concepts/react-custom-reconciler).
Worked example — Zalando outfit page¶
The Part 2 post gives a four-level rule tree for the outfit page:
OutfitView (root Entity: outfit:4NXOAez0Qti)
└─ outfit_view renderer
├─ child: outfit → outfit_highlight-b
│ └─ child: product → product_horizontal-highlight-product-card
└─ child: collection → collection_simple-carousel
└─ child: outfit → outfit_outfit-card
The same Entity type outfit appears twice — once as root,
once as a leaf inside a collection — and is mapped to two
different Renderers (outfit_view vs
outfit_outfit-card) by the placement-scoped rules. This is
the concrete proof that placement is the discriminator for
the 1-to-many Entity→Renderer relationship; the tree walk
carries the placement context through the recursion.
Why this shape¶
- Personalisation is a tree. The personalisation system naturally produces trees (a product page with related outfits and a similar-products collection), not lists. A recursive resolver matches the data shape.
- Each Renderer owns one Entity. The invariant from patterns/entity-to-renderer-mapping requires that child Entities be handed back to the engine for re-resolution, not rendered inline. Recursion is the only shape that preserves this.
- GraphQL data fetching composes over the tree. Because each Renderer declares its data dependencies (see concepts/declarative-lifecycle-api), the engine can fan out all queries across the tree and let DataLoader batch/dedup within a single request — which only works if the tree is walked breadth/depth-first before rendering.
Failure-isolation implications (open question)¶
Zalando's post does not explicitly describe what happens if
one Renderer in the tree throws. Streaming + per-Renderer
execution implies that neighbouring subtrees should continue,
but the error-propagation contract is not disclosed. The
withProcessDependencies slot explicitly allows a Renderer
to return action: "error" — suggesting the framework
substitutes an error-card Renderer without collapsing the
parent — but this is implicit, not spelled out.
Contrast with flat composition¶
A Mosaic-style Fragment-based page template has a fixed set of slots filled with independent Fragments. There is no recursion: Fragment boundaries do not suggest sub-Fragments. That shape is simpler but cannot express personalisation-driven tree structure — the shape IF's Entity trees encode naturally.
Seen in¶
- systems/zalando-rendering-engine — canonical wiki instance. The Rendering Engine walks the Entity tree recursively, runs each Renderer's lifecycle, extracts child Entities, and streams HTML (or emits JSON for Native) as subtrees resolve (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2).