Skip to content

ZALANDO 2024-05-15

Read original ↗

Zalando — Transitioning to Appcraft: Evolution of Zalando's server-driven UI framework

Summary

A 2024-05-15 retrospective by the Zalando mobile platform team describing the 6-year evolution of Zalando's server-driven mobile UI framework from its predecessor Truly Native Apps (TNA) (2016) to Appcraft (2018, still in production 2024). TNA — a JSON-driven framework built around fixed high-level "Composed Tiles" (e.g. a "Showstopper Tile" with Version C / Version D variants) — had three structural pain points: every small UI tweak (moving a button) or new variant required a client-side change and App-Store release; business-logic changes forced coordinated schema versioning on server + both clients; and backward-compatibility tracking across unsynchronised iOS / Android releases was a source of incidents. The replacement, Appcraft, adopts three unifying design choices: the Elm architecture (Model/View/Update unidirectional data flow) as the mobile-side programming model; Flex as the layout language on top of Texture (iOS) and Litho (Android), so a server-declared flex layout "will be transformed by Appcraft into a native UICollectionView for iOS and into a RecyclerView for Android" (see patterns/flex-on-top-of-native-ui-framework); and a narrow set of Primitive Components (Label, Button, Image, Video, Layout) that the server composes into arbitrarily complex screens. Schema versioning moves entirely server-side (concepts/server-owned-schema-versioning), enabling per-app-version / per-platform / A/B component gating, hotfix-free remediation (disable a faulty component for a specific app version), minimum-version backward compatibility, and — the load-bearing business outcome — same-day delivery of new screens and layouts with a client release required only when adding a new primitive or extending an existing primitive's contract. New screens are created by configuring a deep-link → API route (patterns/deep-link-indirection-middleware); renderers are tested in an isolated demo app ( Appcraft Browser, patterns/demo-harness-for-sdui-rendering) or in a debug build of the Zalando app with a PR number pinned (patterns/pr-deployed-renderer-testing-in-debug-app). By 2024 Appcraft serves 13 dynamic pages in the Zalando app including Zalando Stories; it has survived two tracking migrations (2021, 2024) without per-app work, powered multiple App redesigns at reduced mobile effort, and performed in production during Cyber Week. Open challenges named: monitoring gaps (issues reach the platform team late), balancing generality-vs-restrictiveness when adding new features, interoperability (mixing non-Appcraft components into Appcraft screens and vice versa), dependency on third-party UI libraries (Texture / Litho) behaving consistently across platforms, and fuzzy ownership boundaries between mobile and web teams as the engineering org evolved. (Source: sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework.)

Key takeaways

  1. TNA's structural failure was client-bound UI variants. TNA (2016) rendered screens from JSON whose outermost container was a vertical list of Composed Tiles — whole UI components (e.g. a "Showstopper Tile") with fixed schemas. A button-position tweak or a second teaser variant ("Version D" alongside "Version C") required a new Tile version and both iOS + Android releases. Business- logic changes (e.g. price formatting) forced coordinated schema updates on server + both clients. Backward compatibility across unsynchronised iOS/Android releases caused incidents. (Source: sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework.)
  2. Appcraft replaces Composed Tiles with Primitives + Flex. Instead of shipping whole tiles, the client bundles a small set of primitive components (Label, Button, Image, Video, Layout container) — see concepts/ui-primitives-as-platform-building-blocks. The server composes them via Flex specifications; Appcraft translates those into the Texture node tree on iOS and the Litho node tree on Android, each of which sits on top of — not in place of — the native UI framework. "A scrollable layout with Flex specifications on the server will be transformed by Appcraft into a native UICollectionView for iOS and into a RecyclerView for Android." This preserves access to new OS-version APIs and is the canonical Flex-on-top-of- native-UI-framework instance in the wiki.
  3. Elm architecture on mobile. Appcraft is explicitly described as "a mobile version of the Elm architecture" — unidirectional data flow between Model (state), View (renders state), and Update (transitions state on events). See concepts/elm-architecture. The post pairs this with Flex as the "unifying principle that could bridge the design paradigms of Android and iOS" so that web developers can reason about mobile layouts without learning platform-specific frameworks.
  4. Components are typed trees with events + actions. A component is {type, id, flex, props, children, events} where events (e.g. tap, scroll-forward, dismiss) map to ordered action lists (e.g. [track, navigate]). The post's worked example is a layout root with an image child whose tap emits a track action then a navigate action. Event vocabulary includes both explicit interactions (tap, long-press) and implicit lifecycle events (component- in-view, scroll-forward, dismiss). Actions are the extension point and — alongside new primitives — the other trigger for a client release. (Source: sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework.)
  5. Schema versioning is server-only. With TNA, schema versions were negotiated between server and clients. With Appcraft, "the logic of binding data and layout [is kept] in the server", so the client's sole responsibility is rendering. The server owns the component-version table and can: enable/disable components targeting specific app versions / platforms / premises / A/B cohorts; resolve incidents without a hotfix by removing a faulty component for specific OS versions; retain backward compatibility via per-component minimum-app-version; add new pages without any client change by configuring a new route + minimum app version. Canonicalised as concepts/server-owned-schema-versioning.
  6. A client release is only needed for new primitives or extended primitive contracts. The explicit, load-bearing rule: "A client-release is only required when there's a need to introduce a new primitive or extend the contract of an existing one to support additional behaviour." The post gives the example of introducing a Composite Label (with styled sub-texts, font variations, sizes) because a plain Label wasn't enough to render prices. See concepts/client-release-needed-only-for-new-primitives.
  7. Same-day UI delivery is the business win. In a sprint-based release cadence, moving a label is a release- cycle bottleneck; Appcraft breaks that coupling because the presentation layer is defined on the server using pre-bundled primitives. See concepts/same-day-ui-delivery. Operational evidence cited: two tracking migrations (2021, 2024) handled on the server with no per-feature mobile work — mobile's only job was to adopt the new analytics SDK once, letting events flow through.
  8. New screens via deep-link → API indirection. New screens are added without client code changes by aligning stakeholders on a deep-link structure, then updating a routing/configuration middleware that converts the deep-link into an API request the Appcraft renderer understands. Deep-link creation happens on the fly; the clients' only commitment is that the deep-link format is resolvable. See concepts/deep-link-to-screen-config-resolution and patterns/deep-link-indirection-middleware. The post references a companion backend post (2021-09 micro-frontends part 2) as the "backend system that empowers this platform".
  9. Two testing surfaces: Appcraft Browser and PR-debug pinning. Web developers (who author Appcraft screen JSON) iterate locally against a stripped-down demo app, Appcraft Browser, which has a URL address bar accepting any endpoint that emits Appcraft JSON (including localhost). See patterns/demo-harness-for-sdui-rendering. For end-to-end verification in production-like context, renderer changes are deployed to staging from a PR, and the PR number is pasted into the debug build of the real Zalando app to exercise the change against the full app environment — patterns/pr-deployed-renderer-testing-in-debug-app.
  10. Business impact datapoints (2024). 13 dynamic pages served by Appcraft in the mobile app, including Zalando Stories; "notable reduction in engineering effort" across two app redesigns vs non-backend-driven pages; two tracking migrations (2021, 2024) shipped without per-Appcraft-screen mobile work; successful Cyber Week performance ("Zalando's biggest sales event for the last couple of years"); used as the fast-prototyping substrate for engineer+designer iteration within a week; reduced MTTR for incidents ("deploying changes on the server within the same day"). (Source: sources/2024-05-15-zalando-transitioning-to-appcraft-evolution-of-zalandos-server-driven-ui-framework.)
  11. Open challenges (explicitly named). (a) Monitoring gaps — platform team sometimes learns of issues on launched screens only once they've escalated. (b) Generality-vs-restrictiveness trade-off when adding new features. (c) Interoperability — mixing non-Appcraft components into Appcraft screens and Appcraft screens inside non-Appcraft features (e.g. Home Screen's tabular structure where each tab is an Appcraft screen). Canonicalised as concepts/appcraft-interoperability. (d) Third-party UI library dependence — Texture / Litho behave differently across OS versions, requiring custom compensation. (e) Ownership fuzziness between mobile and web teams as the Appcraft platform gave web developers direct authorship of what appears in the app.

Systems extracted

  • systems/zalando-appcraft — canonical system page for the 2018-present server-driven mobile UI framework. Elm architecture + Flex on top of Texture/Litho + primitive components (Label/Button/Image/Video/Layout) + server-owned schema versioning + deep-link → API indirection; 13 dynamic pages in production by 2024; backend story deferred to the 2021-09 micro-frontends part-2 companion post.
  • systems/zalando-truly-native-apps-tnapredecessor framework (2016). JSON-driven, fixed-UI, vertical-list container of Composed Tiles (Showstopper / teaser / etc.) with per-tile schema versioning shared between server and clients. Retired in favour of Appcraft. Documented by cross- reference in the Appcraft post and the earlier TNA introduction post linked from it.
  • systems/zalando-appcraft-browser — the Appcraft demo harness. URL address bar, loads any endpoint emitting Appcraft screen JSON (localhost supported), renders in an isolated environment with minimum dependencies. Used by web developers to iterate on renderer logic without building the full app.
  • systems/texture-asyncdisplaykit — iOS declarative UI + async layout library (formerly AsyncDisplayKit at Facebook). Appcraft's iOS layout substrate. Chosen specifically because it sits on top of UIKit (still producing UICollectionView etc.), preserving access to OS-version APIs.
  • systems/litho — Meta/Facebook's Android declarative UI framework. Appcraft's Android layout substrate. Same rationale as Texture on iOS — sits on top of RecyclerView / View rather than replacing them.

Concepts extracted

Patterns extracted

Operational numbers

  • 6 years in production — Appcraft designed 2018, still primary mobile dynamic-content framework 2024.
  • 13 dynamic pages served by Appcraft in the mobile app (2024).
  • 2 tracking-SDK migrations (2021, 2024) handled with mobile effort limited to adopting the new SDK — no per-screen mobile work.
  • Multiple App redesigns — reduced mobile engineering effort on Appcraft-served pages vs non-backend-driven pages.
  • Primitives: 5 named (Label, Button, Image, Video, Layout) plus "Composite Label" called out as a later addition for price rendering.
  • Layout substrates: 2 — Texture (iOS) + Litho (Android).
  • Screen schema: JSON tree of {type, id, flex, props, children, events} with events mapping to ordered action lists; worked example in the post shows a layout root with an image child emitting track then navigate on tap.

Caveats

  • No latency / throughput / rendering-performance numbers. The post is an architectural retrospective focused on delivery cadence + business impact, not on render performance. Frame-rate, cold-start, and payload-size numbers are absent.
  • No Appcraft-JSON → native-tree transform implementation detail. The post describes the shape (Flex → UICollection / RecyclerView) but not the parser / reconciler internals. The companion [2021-09 micro-frontends part-2 post] (https://engineering.zalando.com/posts/2021/09/micro-frontends-part2.html) covers a related web-side Rendering Engine (Node.js, React reconciler, streaming SSR) which is not the same as Appcraft's mobile-native path, though both emit JSON screen-configs — worth not conflating. See the contrast section on Appcraft.
  • No public Appcraft specification. Unlike Yelp CHAOS (open-source intent via Konbini codegen) or Airbnb Ghost Platform (public talks), Appcraft is internal-only; the post is the primary public source.
  • Third-party-library risk is acknowledged but unquantified. Texture and Litho behaviour drift is named as a challenge but no incident count / impact is given.
  • Monitoring gaps are acknowledged. "Sometimes issues arise and reach us only when they become urgent fixes" — implies the observability layer for dynamic screens is weaker than for statically-shipped ones.
  • Tier-2 on-scope — Zalando Engineering; architectural retrospective of a 6-year production framework with concrete primitive vocabulary, explicit schema-versioning-ownership rule, deep-link-to-screen indirection mechanism, two named third-party UI substrates, documented business outcomes, 5 named open challenges. Architecture density ~90%; remainder is product outcome + hiring callout at the foot.

Source

Last updated · 550 distilled / 1,221 read