Microservices are emerging nowadays for the simple reason that monolithic applications are complex to maintain, evolve and administer. It was therefore necessary to segment our monolith into a multitude of fragments. These fragments allow for a much simpler technical and/or functional evolution.
This evolution is now common in the backend domain. However, the problem of the monolith is not reserved exclusively for the backend, it can also be found in frontend technologies. SPAs have become more and more widespread with the help of frameworks such as Angular, React, or even Vue, but they remain monoliths. Although a multitude of tools within the frameworks allow us to improve the architecture and optimize the segmentation, the code remains mutualized. It is therefore necessary, to get as close as possible to microservices, to segment and evolve towards micro-frontends.
Microservices have appeared in backend architectures with the aim of segmenting the code into a multitude of autonomous and independent applications. This approach allows for improved scalability and maintainability of an application.
In the Frontend domain, practices are moving in the same direction with the use of modules and libraries that can be reused throughout the application. This segmentation has advantages on both a technical and organizational level. Indeed, this practice gives a team the possibility of managing a logical business from A to Z. From the management of the database, through the exposure of the data (Microservice), to the implementation of a graphic interface (Frontend). Thus the evolution of a particular functionality would be mastered by a single team that would be focused on the integration of that functionality and not on the integration of that functionality into a given architecture.
Evolution from Monolith to Microservices
As we can see today on our product lines, application development was done through a monolithic architecture which was composed of all the bricks of an application (the database access layer, the business logic, and the presentation).
As mentioned above, the architecture of an application has evolved or rather been segmented in order to optimize its maintenance and evolution. Figure 1 below highlights this evolution and guides our thinking for the Microfrontends.
All the bricks that make up an application are located in the same application. Access to the database, the business logic and the user interface are grouped together. During maintenance or development, all the bricks must be redeployed.
Architecture Backend / Frontend
The segmentation of the backend and the frontend appeared with the arrival of “xml http requests” which allowed the manipulation and communication of data using the http protocol. The frontend is now autonomous and the backend is still composed of business/technical logic and data access.
The segmentation of the monolith on the backend with the use of Microservices has made it possible to define an application by business domain and thus to better distribute the loads on the development teams (one domain associated with one team, for example). We therefore find ourselves with a set of autonomous and independent applications capable of providing a single service associated with its business logic. With this type of architecture, we see the appearance of API Gateways enabling incoming requests to be redirected and secured.
And the monolith on the front end?
Finally, when we take a closer look at our Microservice architecture, we notice that this segmentation has only been done on the backend. Indeed, the backend benefits from a good segmentation into business domains, exposes an API accordingly and becomes completely independent. Nevertheless, the frontend application is still very consistent. The problem of the monolith has therefore only been partially resolved. This problem is all the more visible on large applications with a scalability that goes rather well on the backend but is more problematic on the frontend (obligation to take over and redeploy the whole application on the frontend).
Technical problems appear:
- An evolution from a functionality point of view on the backend is equivalent to instantiating a new application (Microservice) but on the frontend, it is equivalent to adding a functionality among others. This means that we end up deploying an application that is more and more consequential (higher risk of crashing).
- Migration is an integral part of our work today, it is necessary to think about future uses and the possibility of obsolescence of current technologies. The more compact and tightly linked our code/architecture is, the more complex the evolution will be.
- The next issue follows directly from the previous one with the difficulty of finding developers in a few years when the technology will be obsolete.
As with the backend, we had to find architecture and a way to decouple our front-end application into several independent and reusable domains. This would guarantee better maintainability and integration into different applications.
The componentization allowed to partition functionalities that fulfilled one and the same function and this independently of the environment in which they were integrated. The development would be handled by a single development team responsible for the entire business chain (Microfrontend API). This brings us to another benefit, which is the ability to modularise an application according to the client. Indeed, if each business component is autonomous, independent, and can be integrated into any environment, it is quite possible to aggregate these components to make an application.
Finally, from the user’s point of view, the application will be an aggregation of shared components, each managing a particular business domain. The “container” will therefore be the guarantor of the orchestration of all these components (see Figure 3).
Having the ability to split our frontend into several Microfrontends is leading us more and more towards a division of development teams by business domain. This would allow the team to be in charge of business logic from A to Z (from Microservice to Microfrontend). Finally, front-end application integrators will no longer need to know the Microservice domain inside out. They will only have to integrate the component created by the Microservice team.
When the Microservice evolves, the adapted component will be developed at the same time by the same team (but probably different technical experts) and will thus be ready to be integrated quickly by all the client applications.
Advantages and disadvantages
Although the core of the problem was the difficulty of evolution and maintenance within a monolithic frontend, it was also noticeable that in development, the teams at the end of the chain had to know all the Microservices with their constraints, restrictions etc…
Now the teams are organized around business domains and take charge of the end-to-end development of a service. The advantage is that they don’t have to worry about the environment in which the whole thing will be integrated. They can concentrate solely on their business areas. As for the integration teams, their sole purpose is to integrate an application with a multitude of Microfrontends. They no longer have to worry about business issues. Finally, in a more general way, it makes a team more specialized and avoids it being a generalist.
One of the disadvantages that can be noted with this type of architecture is the need for broader technical knowledge. Indeed, each business team will be responsible for the development of both the Microfrontend and the Microservice.
Today’s web provides us with an enormous number of tools to create our applications. However, the choice of a technology or a framework is mainly based on the need, the delivery time, or even the budget. When we talk about Microfrontend, we distinguish two categories of solutions: applications that will share the same execution environment and those that do not.
The vast majority of Frontend applications that are developed today are applications that give a shared execution environment in order to guarantee the best result for the developer but also for the user.
Separate execution environment
Applications following this pattern have the possibility to guarantee a clear and precise separation from the rest of the application. Although it is beneficial on the one hand, it has disadvantages, especially when it comes to communication with the outside world. Nevertheless, from the user’s point of view, the Microfrontend will appear as a brick (more or less integrated) of the application.
This is probably the concept that is best known of all, Iframes have the ability to run one web application inside another. The user sees a single page but the technical separation is there, too much so.
This tool has, as previously mentioned, the capacity to make several pages/applications cohabit but brings a lot of disadvantages. The communication of data within an application is an important criterion for an interface to be as “user friendly” as possible. In the case of an iFrame, inter-frame communication is more than limited and the integration of several Iframes remains superficial from a user’s point of view. The last disadvantage that could be the major one is the management of CORS which becomes complex.
Finally, the iFrame is a tool that is rather old-fashioned nowadays and that answers old problems. This approach is used for a purely static integration of a page, completely sealed from its execution environment.
In the case of micro apps, the separation is less clear-cut than for iFrames, but remains present with the use of application routing to serve one Microfrontend or another. In this case, we are approaching a segmentation by business domain. Example:
- Micro frontends ” Product ” accessible at ” http://produits.monapp.fr/ “
- Micro frontends “Client” accessible at ” http://clients.monapp.fr/ “
- Micro frontends “Preferences” accessible at the following address: ” http://preferences.monapp.fr/ “
However, this practice implies the implementation of a hub allowing the user to be proposed and redirected to the right Microfrontend. This makes it possible to get as close as possible to microservices with segmentation by business domain but requires good management of identity transfer (authentication, preferences, etc.). Moreover, communication between the micro-frontends and their mutual knowledge remains complicated.
Although this solution comes closest to our vision of micro-frontends, it is far from satisfactory today. Not least from a user point of view, who will suffer from a somewhat confusing user experience.
Shared runtime environment
When our application offers a shared execution environment, all resources are available to all components of the application. We then speak of Microfrontend as components of varying size that are integrated into the same application. This segmentation by component is as visible on the user side as it is on the technical side.
These can be integrated into an application in the same way as the current blocks offered by HTML (div, input, etc.). With Web components, we have the ability to create HTML/CSS/JS bricks that fulfill a precise function and can be integrated into any application.
The web is nowadays a field that evolves very quickly, new frameworks and libraries are released every month and it is difficult to make a choice in this mass of tools. Web components are coming on the market to guarantee the durability of our development with JS/CSS encapsulation and W3C support. Communication between components is simplified by the “events” system. The idea here is not to detail the functioning of the web components but the harmony between the web components is governed by a “base app” which orchestrates everything.
Note that modern frameworks use Web Components technology. For the sharing of complex components, this is the most efficient solution, both at the development level and at the UX level. It is possible to integrate different modules transparently for the user and to manage workflows in order to guide him in his tasks.
As an indication, it is possible to make several SPAs from different frameworks cohabit using a tool such as “SingleSpa”.
Webpack 5 : Module Federation
Module Federation allows multiple web components (built using webpack) to work together. An application can dynamically execute code from another bundle or build. This is a revival for the world of micro-frontends.
Each webpack build can be a host, which is a container for loading other builds. It can also be seen as a remote, which is a micro-frontend to load. Each application can be a remote and a host, consumable, and consumer of any other federated module in the system.
Furthermore, Module Federation does not necessarily need to load the main entry point or an entire other application. It only needs to load the necessary code, which is a few kilobytes of code.
Thus each application has the possibility to work autonomously and independently because it is first and foremost a full-fledged application but it is also able to be packaged and integrated into any other application.
This approach makes it possible to reference parts of the program that are not yet known at the time of compilation. These can be self-compiled micro-frontends. Furthermore, one of the important points of Module Federation is the ability to share libraries between the different micro-frontends. This considerably reduces the size of our bundles and our entire application.
The shell of our application can be seen as the entry point or orchestrator of all the micro-frontends embedded in our application. It is ultimately the host and the micro-frontends the remotes. Module Federation allows (Figure 4) to share libraries between several micro-frontends. We can distinguish four Angular applications sharing the same stack of libraries, including Angular.
With this approach, we can see that it is easy to make several micro-frontends from different frameworks cohabit. Indeed, Webpack5 takes care of packaging our different remotes in Web components, which makes their use much simpler.
Namely, a remote can also be a host by hosting other micro-frontends (Figure 4).
In the figure below, we can see a routing mechanism allowing the various micro-frontends to be loaded. Here again, it is possible to load them dynamically with lazy loading in order to reduce the bundle as much as possible when the page is loaded.