Microfrontends – Concept and Implementation
It is well known today that it makes sense to divide large software systems into several services. There are numerous advantages over monolithic applications. They can be individually designed, developed and implemented by independent teams of different composition and expertise. Services that are more heavily used can be scaled individually, maintenance and testing are facilitated and deployments can take place independently. The resilience of the entire system increases, because if one service should fail, the others can still be used. And if a service has to be rewritten due to new requirements or changes in technology, it can be replaced independently without having to touch the other parts.
Distributed system with independent services
The Frontend Monolith
Unfortunately, it is often the case that the advantages mentioned stop at the frontend. Usually, there is a holistic interface that communicates with all services of the otherwise distributed system and carries all UI logic within itself. The problems that have been successfully overcome in the backends occur there in bundled form.
Over time, the frontend becomes steadily larger and harder to keep track of. Changes take longer and longer. The susceptibility to errors increases, and the danger of accidentally bringing the whole application to a standstill is again present in the frontend team. And should it be necessary to update or even replace the chosen framework, this comes close to a complete rewrite of the frontend.
Distributed system with monolithic frontend
The Concept of Microfrontends
The concept of microfrontends aims to counteract this. As a logical continuation of distributed systems in the frontend, it offers solutions on how a UI can be composed of many individual parts. The basic idea is that the services themselves deliver all the UI elements that are necessary to serve their specificity.
Services deliver their own UI elements, these are integrated into an interface
To ensure that a uniform user experience is created in the end, there are various ways of implementing these individual parts and integrating them into a common application. Microfrontends are particularly useful as part of self-contained systems. I would now like to explain this concept.
Self Contained Systems – Encapsulated Functionality
In a classic three-tier architecture, applications are divided horizontally into persistence, business logic and user interface. This results in database, backend and frontend teams.
Self-contained systems, on the other hand, rely on a vertical cut along the functionalities. This creates cross-functional teams that develop and are responsible for their applications through all layers. This will now be explained using e-commerce as an example.
In the development of web shops, the alignment of teams along the different steps of the customer journey has proven useful. In the example of our „Next Level Commerce“ project in the Klingel Group, in which we rely on Self Contained Systems and Microfrontends for the approximately 50 shops in nine European countries, the Customer Journey was divided into the verticals “Search”, “Discover”, “Select”, “Buy” and “Accompany”. As each area has a different customer need as a priority, there are also different requirements for the internal architecture.
Customer journey in the web shop subdivided into different steps
A large, uniform overall solution or fixed decision on database technology, for example, would be counterproductive here. Each team can best assess for itself which technology best supports its goal. It chooses the internal architecture within its system to suit its own framework conditions. If it also turns out that the current solution no longer offers enough flexibility to react to the changing requirements of the customers, the team must have the freedom to change their own systems and make new decisions – and to do so as quickly as possible. Likewise, the choice of framework in the frontend is in its hands.
Self Contained Systems are cut vertically according to subject matter and include all layers from surface to persistence
To ensure the independence of the verticals, self-contained systems should exchange data asynchronously whenever possible. The producing system provides the data as a feed, for example, which can be consumed and further processed by the interested parties at will. The redundancy in data storage is gladly accepted, because this way the consuming vertical has the possibility to read the data from its own database instead of having a runtime dependency through synchronous requests to another system.
UI Integration with Microfrontends
Microfrontends are based on the idea that different services display pages on which both their own content and UI elements from others are brought together. To achieve this, there are different ways of implementation. In the following, I would like to present the techniques that have proven to be particularly suitable for microfrontends.
With the custom elements from the Web Components standard, it is possible to define HTML elements that have their own appearance and behaviour. They have their own lifecycle, which can be used via callback functions to execute logic. This is particularly practical for executing API requests during initialisation or for reacting to external changes to attributes.
Custom Elements can independently request an API
This makes it possible for one team to integrate a custom element of another and for the latter to independently obtain the data it needs from its own system. The functionality is strongly encapsulated and the integrating team does not need any knowledge about request URLs or the internals of the element. Because of their own lifecycle, custom elements are very reactive and can respond directly to user input. It should be noted that they are only initialised when the DOM is processed in the client and are therefore less suitable for SEO.
Custom events are suitable for notification of occurrences in the interface. These are sent to the window, where interested parties react to them through event listeners. If custom elements are used for this purpose, they can independently query data and execute logic without the event-creating team having to exert any influence.
When clicked, a custom event is generated to which an event listener responds
Especially when communicating across teams, it is important to note that a custom event only informs about the specific event, but does not exchange data. The payload of the event should therefore be kept minimal in order not to create dependencies via the event system.
Server Side Includes
With Server Side Includes (SSIs), the vertical teams provide their UI elements to be included, the fragments, via a URL. These are then entered into the markup of the page with an include command. The integration takes place in three steps:
- When the request arrives from the client, the upstream proxy fetches the markup for the page from Team A. This contains SSIs from Team B and Team B’s SSIs. This contains SSIs from Team B and C.
- The proxy requests the specified URLs from Team B and C in parallel. It inserts the markup from the responses into Team A’s page in place of the SSI commands.
- As soon as all responses have been received and inserted, it sends the merged page back to the client.
It is important to note here that the response is not sent back until all responses from step two have been received. The slowest response time is therefore decisive for the response time of the entire page. It is therefore important to work with timeouts and fallbacks.
Server Side Includes with request and response
Since SSIs are resolved on the server side, they can achieve very fast loading times and are also perfectly suited for search engines. Also, the main computing load is not on the user’s device, which is especially convenient for mobile devices. On the other hand, the interactivity of server side rendering is limited and in order to react to the user’s input, the server must be requested again and the page reloaded.
SSIs and Custom Elements Combined
Server side includes and custom elements each have many advantages but also some disadvantages. To get the best of both worlds, they can be used in combination.
To do this, the team provides the UI element to be included as both an SSI fragment and a custom element. In the markup of the page that is to contain the element, a custom element is placed around the SSI command. The procedure is then as follows: First, the server side include is resolved and the HTML response is returned to the client. The initial page is then ready. As soon as the client processes the DOM, the custom element is initialised and the resulting inner HTML replaces the markup that previously came from the SSI.
In this way, you get both fast loading times and perfect SEO properties, as well as responsive UI elements that can react quickly to user input. After the initial setup, the additional effort to implement both is limited, resulting in a very usable solution for microfrontend components with universal rendering.
Microfrontends are only suitable for projects of a certain size and duration, because they are a rather complex frontend architecture. The setup effort at the beginning of a project is comparatively high and a lot of coordination is required. Implementation within the framework of self-contained systems results in redundancies in logic, data management, operation and infrastructure. But one should not be deterred by these disadvantages, especially in long-lasting projects and system landscapes that are to exist for several years. Many advantages outweigh these points.
Due to the independence and personal responsibility of the cross-functional development teams, it is possible to work faster and more freely with significantly fewer dependencies, which are often the biggest time eaters. The limited framework that the developers have to keep track of due to the functional division reduces the cognitive load and there are fewer unknowns that can lead to errors during changes and deplyoments.
At the same time, the development of technical expertise in the respective area is promoted and the internal architecture can be adapted to the circumstances in the best possible way. The reduction of dependency on a framework in a specific version should also not be underestimated. Updating the framework is much easier on a smaller scale and the danger of spending weeks and months of working time on a version update after a while is drastically reduced.
Microfrontends optimise feature development and are the logical continuation of distributed systems in the frontend that we have been waiting for for so long. Since it is an architectural concept, the concrete implementation can vary. The decisive factor here is the framework conditions of the respective context.
At the moment, a promising trend seems to be developing, which will certainly ensure that there will be a few more possibilities for implementation and simplifications in implementation in the coming years.
Jennifer Pelz has published another post in the t2informatik Blog:
Jennifer Pelz is a full stack software developer and did her Master’s in International Media Informatics in Berlin. As an author of technical articles and speaker at conferences, in addition to her passion for front-end development, she also deals with topics related to agile software development in large-scale projects, distributed systems and architectures, and self-organisation in companies.
Jennifer works at Qudosoft, a self-organised, hierarchy-free software development company that has even managed without an operational CEO since 2018.