Transitioning to Appcraft: Evolution of Zalando’s server-driven UI framework
At the heart of Zalando's mobile content strategy lies the Appcraft platform, fueling 13 dynamic pages within the app. This framework is instrumental in delivering top-tier content formats, including the popular Zalando Stories. In this post we explain the origins and inner workings of the platform.
The TNA Dilemma
The Flexible Layout Kit (formerly known as Truly Native Apps, TNA was a framework used in Zalando App to render content dynamically. This framework processed JSON input, which defines the slots and elements of a screen. These elements were characterised by their types and a set of attributes. The primary container of the screen was a vertical list type, which encapsulated a series of Composed Tiles within client-side Apps. While this system initially provided simplicity and a robust foundation for dynamic landing pages within our Apps, its fixed UI structure imposed constraints. Notably, maintaining the high-level composed UI components across both iOS and Android clients proved challenging, mainly due to versioning but also due to constant UI design changes and the introduction of multiple variants for a single Tile in order to support our different business logic and content formats. These limitations inhibited innovation and hindered the seamless integration of dynamic content.
Example of a component in TNA: These were the Showstopper Tile variants (C and D shown below) in TNA framework
Version C | Version D |
The json for Version D looked like below:
{ "element-type": "teaser", "attributes": { "trackingParameters": {}, "saleBoxColor": "#FF0000", "teaserVersion": "VERSION_D" }, "subelements": [ { "attributes": { }, "element-type": "image" }, { "attributes": { }, "element-type": "text" }, ... { "attributes": { }, "element-type": "use-voucher" }, { "attributes": { }, "element-type": "show-info" } ] }
To summarise, these were the pain points with the TNA framework:
-
Small UI changes within a Tile, such as moving a button to the right or left, or stakeholders requiring two UI presentation variants, would prompt a new version and necessitate a client-side change and release to the App Stores.
-
For other cases involving changes to business logic, such as a price format change, the contract or schema for the price component on both clients and the server had to be modified.
-
Maintaining backward compatibility and versioning was challenging and led to a few incidents. It also necessitated coordination between clients, especially when the app release versions between iOS and Android were not synchronised.
-
More over back then several backend services including TNA needed to be migrated and the team had to face a decision of either maintain or decommission TNA backend.
These shortcomings encouraged us to replace TNA with a new Framework in which we aimed at:
- A common and more flexible design layout system.
- Simplified Versioning capabilities.
- Same-day delivery for new Screens and Layouts.
Enter Appcraft
A common design layout system
In 2018, after experimenting with web-like architectures and several layout systems provided by native and third-party frameworks, we decided to implement a mobile version of the Elm architecture, together with Flex, as a unifying principle that could bridge the design paradigms of Android and iOS. Here's how:
ELM architecture, inspired by the Elm programming language, follows a unidirectional data flow pattern consisting of three main components: Model, View, and Update. The Model represents the application state, the View displays this state to the user, and the Update modifies the state based on user interactions. This clear separation of concerns simplifies code maintenance and enhances predictability, making ELM architecture popular for building scalable and maintainable web applications.
Flex was key in helping to build a common understanding of layout concepts for mobile clients, which web developers could also grasp without the need to learn the individual mechanisms each platform uses to lay out views on a screen. It offers flexibility for dynamic and responsive designs across platforms, streamlines development, fosters cross-platform compatibility, and benefits from a large community of developers.
The challenges
While the decision to use Flex was agreed within the cross-platform team, the challenge lay in adding Flex support to iOS and Android, each of which internally uses its own native layout framework. Based on this, we experimented with a few third-party layout libraries already available, each with a fair reputation, comparing their performance and integration efforts. Once these libraries were chosen for iOS and Android, most of the effort went into translating the Flex definitions from the server into the Flex library APIs for each platform and comparing them to ensure consistent results between both. One important consideration while choosing the library was finding one that sits on top of the native UI frameworks to assist with positioning and sizing, without replacing or altering the behaviour of the native UI framework. This means that, for example, 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 approach ensures that we still have access to new APIs and improvements available on the native UI frameworks for newer OS versions. We decided to move further with Texture on iOS and Litho on Android.
Primitives
We've established a set of Primitive Components to serve as the foundation for constructing High-level UI Components. Starting with essentials such as Label
, Button
, Image
, Video
, and a Layout
container, these primitives form the building blocks for crafting intricate UI components. With these foundational elements in place, developers possess the flexibility to combine and customise them according to their application's unique requirements, unlocking a plethora of possibilities for UI design and interaction.
Behaviour
Users engage with apps through various events such as scrolling, tapping, long-pressing, and more. Each of these triggers a specific action as a response which in most cases results in a UI update or a side effect. We've devised a comprehensive set of actions to ensure the system effectively responds to these user-triggered and component life-cycle events for e.g., tap
is an event navigate
is an action. Additionally, there are implicit events designed to track user interactions, ranging from detailed events like scroll-forward
to simpler ones like dismiss
.
This is what a component looks like in Appcraft:
{ "type": "layout", "id": "root-container-layout-id", "flex": {}, "props": {}, "chidlren": [ { "type": "image", "id": "id1", "flex": {}, "props": {}, "events": { "tap": [ { "id": "id2", "props": {}, "type": "track" }, { "id": "id3", "props": {}, "type": "navigate" } ] } } ], "events": {} }
Simplified Versioning capabilities
With the previous TNA system, both server and clients had to exchange information about the schema version, adding complexity. We sought alternatives to reduce errors and simplify maintenance. With a more flexible layout structure and by keeping the logic of binding data and layout in the server, we achieved reduced complexity in the clients by leaving the sole responsibility of rendering to the app. The schema versioning remained on the server, making it easier to resolve issues such as retrieving the right component version for each client and allowing us the flexibility of customising UI and behaviours for each platform independently. While it was not immediately apparent, maintaining this flexibility on the server allowed us to:
- Enable or disable components and their behaviour targeting specific app versions, platforms, premises and A/B testing.
- Resolve incidents quicker without the need of a hotfix by removing for example faulty components for specific app versions or OS due to bugs or performance reasons from the server.
- Retain backward compatibility logic on the server, as we can specify a minimum version for a component.
- Adding new appcraft pages in the App without the need of client changes, by just configuring the new page route and the minimum app version supported.
Same-day delivery
In Zalando mobile engineering we operate in sprints, with each sprint culminating in an app release. In this model, even simple UI adjustments may require waiting for a new app version and even longer for the full adoption, which can be a significant bottleneck in a fast paced organisation like Zalando itself. In an ideal scenario, without the need for hotfixes, waiting for a complete release cycle for moving a label from left to right seems counterproductive. Appcraft is designed to be agile and responsive to user needs, and such delays can hinder our ability to deliver a dynamic user experience. With the introduction of the Appcraft framework, the delivery is not tied to app releases or sprint duration, changes can be made at any point during a sprint. Now, the presentation layer can be defined directly on the server using pre-defined primitives that are packaged within the app.
What does it look like when a new screen is required?
When a new screen is required, the process is streamlined and dynamic in our mobile applications. We heavily rely on deep-link navigation, allowing seamless transitions between different screens. In a truly dynamic system, the creation of deep-links should happen on the fly without the need to manually add routes in the clients every time.
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.
So when is a client-release needed?
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.
For example: When a simple label was not enough, we decided to introduce a Composite Label with the ability to add subtexts with their own font styling decoration and sizing and this is currently the primitive used for example to render price due to its flexibility.
How is a newly created screen tested?
We developed a demo app named the Appcraft Browser, featuring an address bar where any URL emitting appcraft screen JSON can be provided as input. The screen definition is then rendered in an isolated environment with only the bare minimum dependencies, facilitating faster development without the need to build the entire app. This tool allows web developers to insert a local host URL and test their development seamlessly while working on the renderer.
After the development stage, web developers open a PR which allows them to deploy the rendering changes in a staging environment, changes are then validated in a debug version of the Zalando app by incorporating the deployed PR number into the app debug settings. This allows testing in production screens and the actual app environment.
Appcraft's Business Impact
-
Dynamic content - Currently Appcraft platform serves 13 different dynamic pages in the mobile app which contribute to Zalando’s effort of consistently delivering quality content formats to mobile users for inspiration and personalisation around brands, recommendations, outfits, creators, collections and campaigns. Check out the most recently shipped feature powered by Appcraft called Zalando Stories and its press release.
-
App Theming/Redesign - Since the inception of the Zalando App, the company has undergone several app redesigns, each demanding significant engineering effort and collaboration across multiple teams. However, when it comes to pages served by Appcraft, there has been a notable reduction in engineering effort compared to non-backend-driven UI. This is because the majority of changes are implemented on the server, benefiting both mobile platforms and all supported premises through common rules.
-
Tracking Migrations over time - Similar to UI redesigns, since the introduction of Appcraft platform, the mobile apps have gone through two different tracking migrations, first in 2021 and now in 2024. For Appcraft screens, akin to UI changes, all tracking events and their schema are defined on the server. The mobile client's only task was adopting a new SDK or in-house backend solution to pass by the events to the new analytics framework.
-
Quick Prototyping - We use Appcraft for fast prototyping. By creating new renderers in the backend, the engineers and designers were able to quickly iterate on different UI designs over the course of a week.
-
Resilience - Appcraft’s resilience has matured over time, with past incidents triggering some of the improvements. By deploying changes on the server within the same day, the MTTR for incidents is notably reduced. Moreover, the platform is used with success during Cyber Week, Zalando's biggest sales event for the last couple of years.
-
User experience - When a concept is added in Appcraft, it scales immediately to all screens via the backend. We are actively working on enhancing the user experience to be more delightful. We're currently exploring screen transitions, fluidity concepts, and micro-animations on the Appcraft platform.
Current challenges and evolution
-
While thoroughly enjoying the flexibility of adding screens without the involvement of any app engineers throughout the content experiences, we, as the platform team, find it challenging to keep track of the launched screens due to gaps in monitoring. Sometimes issues arise and reach us only when they become urgent fixes.
-
Striking the right balance between generality and restrictiveness when creating a new feature in a backend-driven mobile framework is essential. It involves carefully considering factors such as usability, flexibility, consistency, performance, and compatibility to ensure that the feature meets the needs of both developers and end-users effectively.
-
Testing has also gotten easier only over time. We enhanced the developer experience by enabling local testing for web developers, providing screen context injection for A/B testing, and eventually facilitating testing for pending changes to renderers (open PRs).
-
We are currently addressing another significant challenge, known as Interoperability, which refers to the reuse of existing non-Appcraft components in Appcraft and vice versa. To tackle this, we've introduced the capability of embedding non-Appcraft components in Appcraft screens and the embedding of entire Appcraft screens within larger features. Examples of this can be seen on the Tabular structure on Home Screen where each tab is an appcraft screen.
-
Dependency on third-party UI technology could pose a challenge because iOS and Android libraries may behave differently, requiring additional customization or default code to achieve consistent functionality and user experience across both platforms.
-
Due to organisational changes over the years – such as transitioning from a strong web engineering team with limited mobile resources to having equally strong web and mobile teams – the allocation of effort has become a topic of debate. Consequently, we've observed that feature ownership (mobile vs. web) can sometimes become unclear.
Appcraft has been serving as a stalwart in the realm of backend driven screen frameworks. Read all about the backend system that empowers this platform.
We're hiring! Do you like working in an ever evolving organization such as Zalando? Consider joining our teams as a Mobile Engineer!