PATTERN Cited by 1 source
Deep-link indirection middleware¶
Problem¶
In an SDUI-first mobile app, you want new screens to ship
without client releases. But deep-links — zalando://stories/
123, https://…/stories/123 via universal links, push
notification payloads — conventionally map to a hardcoded
route table compiled into the client binary. A new screen
therefore needs:
- A new route in the client route table.
- A client build + App Store release.
- Wait for user adoption before the link resolves.
This breaks same-day delivery.
Pattern¶
Introduce a middleware component between the deep-link and the client's Appcraft runtime. The client does not have a route table; instead, it:
- Parses the deep-link into a canonical request shape.
- Hands it to the middleware, which is a server-side routing table.
- Receives an API endpoint to hit.
- Fetches the screen JSON from that endpoint.
- Renders it with the Appcraft runtime.
deep-link → middleware → API endpoint → screen JSON
(client sends) (server-side routing) (server-side) (client renders)
New screens = add a new entry to the middleware's routing table = server config change = no client release.
Appcraft's implementation¶
Appcraft explicitly uses this pattern (Source: sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework):
"To achieve this, we've introduced a middle-man component that takes a deep-link and converts it into an API request that our framework can understand. This way, every time a new screen is needed, our stakeholders simply align on the deep-link structure and update the configuration according to the agreed-upon contract. With these adjustments in place, the setup is complete. The next step involves the renderer, which will then interpret the updated configuration and render the new screen accordingly."
The post includes a "deeplink-resolution-sequence-diagram" referenced visually in the post.
Client-side commitments¶
The client still commits to a family of deep-link shapes —
e.g. "all deep-links of the form zalando://X/Y/Z where X,
Y, Z are URL-safe strings". This commitment forms the stable
contract; new deep-link forms beyond the family would still
require a release. In practice, URL-like deep-link shapes are
flexible enough that the family can accommodate most new
screens without ever needing to evolve.
Where new screens are born¶
A new screen ships as follows:
- Stakeholders agree on the deep-link:
zalando://stories/ 123. - Backend adds an entry to the middleware's routing table:
/stories/{id} → /api/appcraft/screens/stories?id={id}. - Backend creates the server-side Appcraft screen
configuration for the
storiesscreen. - (Optional) Backend configures min-app-version so old clients without a required primitive are gracefully handled.
- Deploy. No client release.
Related indirection patterns elsewhere¶
- patterns/control-plane-proxy-with-etag-cache — a similar "client doesn't talk directly to the authoritative source, middleware fans out" shape, but at a different altitude (control-plane ↔ Kubernetes API rather than deep- link ↔ screen-config).
- Client-side routing libraries (React Router, React Navigation) solve the same problem at a different layer — they still need route table updates, but no App Store release because the web reloads on every visit. Deep-link indirection is the mobile-native analogue.
- GraphQL persisted queries (see Zalando's own patterns/automatic-persisted-queries) — a parallel indirection: the client commits to a query ID, the server looks up the query text.
Variants¶
- Single-hop — deep-link → API endpoint → screen JSON (Appcraft's apparent shape).
- Multi-hop with auth / A/B / cohort routing — deep-link → A/B decision → maybe a different API endpoint → screen JSON; the middleware does more work than mere lookup.
- Fallback shapes — deep-link resolution fails (server unreachable, unknown route) → cached screen or static fallback. Appcraft's exact fallback shape isn't detailed in the 2024 post.
Anti-patterns¶
- Hardcoding new routes in the client for every new screen — reintroduces the release-boundary you were trying to eliminate.
- Letting the client build URLs to API endpoints directly — couples client to API surface; any API change needs a client release. Keep URL → API translation in the middleware.
- Treating deep-links as opaque tokens the client can't parse — the client still needs to validate the deep-link family shape to reject malformed links without a server round-trip.
Seen in¶
- sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework — Appcraft's new-screen-creation mechanism; explicit "middle-man component" framing; stakeholders align on deep-link structure + update routing configuration; no client change.