Micro Frontends: from Fragments to Renderers (Part 1)
In 2015, we wanted to improve how we delivered features to customers and move away from a monolithic shop system. Project Mosaic and its microservices approach for the frontend were vital to support this transition. Mosaic enabled a relatively large number of teams to work on the main Zalando website independently and without performance compromises. At its core, Mosaic architecture relies on page Fragments, which are owned by different teams.
Mosaic helped us deliver features quickly and experiment at scale, contributing to Zalando’s growth, but we identified limitations to the Fragments approach. The main pain points for Zalando at that time were:
- Differences in tech stacks, bundling, and deployment practices across fragments led to inconsistent user experience and cross-team collaboration difficulties
- A high entry barrier for teams contributing to the customer experience. To be able to add new features to the website, engineers had to
- build and operate their fragments (usually frontend and backend services)
- discover and integrate with all the data sources
- re-implement or adapt the UI
- re-implement or adjust tracking & A/B testing
In 2018, we started designing Interface Framework (IF) to overcome these issues. The new transition’s key goal was to build a platform that unified the tech stack and centralized the deployment and operation process for various parts of the Zalando website. It would enable a fully personalized customer experience, and guarantee overall UX consistency based on a new design language.
Now, we'd like to give you an update on our approach to frontend development in the form of a blog series. The first part covers the key features of the new framework and provides an overview of its architecture.
Why Interface Framework
Consistent Entity Data
We identified a reasonably small amount of content pieces in use by Zalando that can be visualized or catered for personalization purposes. For example, a Product, a Collection, or an Outfit. When organized in tree-like structures, they can be used to define layouts and content of the Zalando core user journey pages. When used individually, they can be the common language used across microservices to exchange data.
We call them Entities. Each Entity has a type and a unique id.
Dynamic View & Content Composition
Interface Framework supports dynamic composition of the user interface. It composes a page by forming a tree of nested Entities and transforming it into a tree of matching Renderers. The mapping of Entities to Renderers is specified in a declarative set of layout rules, which we call rendering rules. A Renderer is responsible for visualizing data related to an Entity.
Let's assume we are presenting a product page with some slots below the article to show additional content. Our personalization service chooses to provide three pieces of content: a collection, an outfit, and another collection. It determines what content the customers see on the page.
The Rendering Engine then decides to visualize the collection as a carousel, outfit as a card component, and the third collection as another carousel. It is responsible for how the content gets rendered to the customers.
Integrated Monitoring
Interface Framework automatically connects all views to the internal monitoring tools, ensuring that only the unified, user consent compliant, and thoroughly tested implementation is used. It helps to prevent incidents and disruptions in business reporting and personalization.
Orchestrated A/B Testing
A/B tests can now run in an orchestrated way to compare the results and make informed choices. This ensures features are tested with a representative user base, using standardized A/B testing scenarios and KPIs to ease comparison between features. Defining and setting up global A/B tests also means reducing the overhead of doing it for every page.
The integration of Zalando’s A/B testing platform in IF allows us to:
- Implement experiments with only a few lines of code, and get the implementation automatically validated
- Track experiments automatically without additional efforts to analyze the results
- Continue managing experiments via the intuitive A/B testing platform UI
- Keep experiment latency overhead low by batching all requests to the A/B testing platform for all Renderers
Integrated Testing for Developers
As Interface Framework provides a single integration point where all code is developed and deployed, we give developers access to deployment previews, which allow any open pull request to be previewed in an environment very close to production. This setup is different from the traditional staging approach. The preview deployment is connected to production endpoints and follows 100% production routing while ensuring that only authenticated developers can access it.
Consistent UX Design
All pages running on Interface Framework, the look & feel, accessibility features, and actual components used are all defined by a design system. Our server-side rendering framework, which we call Rendering Engine, takes over the complexity of component version management and optimizes client code bundle size.
Page Performance Quality Gates
We evaluated best practices from CI/CD pipelines for Fragments from various teams and combined them to measure the performance for pages served by Interface Framework. We do support the following tools:
- Lighthouse CI: a tool to automatically run performance and accessibility tests for specific pages. It validates assertions with results and decides whether the current score is good enough for production.
- Bundle Size Limits: we have a tool to automatically compute and check bundle sizes for Renderers on every pull request. It shows the results for all Renderers that have changed with the current version being released.
- Client Metrics: we provide a built-in layer to report Web Vitals and custom metrics to capture all Zalando pages’ user experience.
Increased Organizational Speed and Efficiency
We are still organized around feature teams which have frontend engineers embedded. The main difference is that now they are working in a monolithic repository providing a unified and automated environment that offers new joiners a quick onboarding. The teams develop features and UI elements within Renderers. These Renderers are associated with Entities that make up our new page semantic.
There is quite a cultural shift as some ownership lines are now blurred in Renderers, with multiple teams contributing to most of them. As a result, we now have a much more collaborative development environment where teams benefit from their best practices. A centralized repository also means it is easier to ship large project changes and contribute to other teams' code, supported by a set of contribution guidelines.
We now have an aligned set of modern frontend technologies (React, TypeScript, GraphQL), a centralized server infrastructure, a release process, and a robust set of monitoring capabilities with dashboards and alerts. We are more efficient in terms of operations, and new reliability patterns immediately impact the whole website.
Architecture Overview
The following chart gives an overview of the underlying architecture. It contains all the core components of Interface Framework.
The GraphQL API is a data aggregation layer. It is to become the primary data source for all web pages at Zalando and reduce data integration costs across many teams. It provides a unified way for accessing content as an output of personalization services like the Recommendation System.
The Rendering Engine is a backend service and client-side runtime running in Node.js and the browser. Its primary purpose is to resolve and render a tree of Entities for a given request. The Recommendation System controls the structure of this tree.
A Renderer is a self-contained, reusable piece of code that runs inside the Rendering Engine. It declaratively specifies all of its data dependencies via GraphQL and uses the Zalando Design System to represent a single Entity visually.
The mapping of Entities to Renderers is one-to-many since different visual representations are possible for an Entity. An outfit Entity, for example, can be represented as a main view or a card component within a collection. Each Renderer, on the other hand, corresponds to one specific Entity type.
We do support a hybrid approach with Interface Framework. The Rendering Engine can serve views in different configurations:
- View is a Mosaic Template and only uses Fragments.
- View contains both Renderers and Fragments.
- View only consists of Renderers.
This support for both rendering modes was and is still very beneficial for teams migrating their page from Mosaic to IF. Currently, we serve around 90% of traffic via Interface Framework.
Future Posts
In upcoming posts, we will dive deeper into the framework’s core components and share what we have learned during the transition from Mosaic to Interface Framework.
Update 2023/07: See Rendering Engine Tales: Road to Concurrent React for an update on Rendering Engine and how we integrated React Concurrent features as part of our upgrade to React 18.
We're hiring! Do you like working in an ever evolving organization such as Zalando? Consider joining our teams as a Frontend Engineer!