📜 ⬆️ ⬇️

Microntends: what are we talking about?

All these years you, the frontend developer, have been writing monoliths, although you understood that this is a bad habit. You divided your code into components, used require or import and defined npm packages in package.json or fetched git repositories in your project, but you still wrote a monolith.
It's time to change the position.

Why can your code be considered a monolith?


By their nature, all frontend applications are monolithic - except for applications that implement micro-frontends. The reason is that you are developing using the React library, and two teams are working. Both must use the same version of React and keep each other informed of updates, which means they will always resolve conflicts when the code is merzh. They are not completely independent of each other in the code base. Probably, they generally use one repository and one build system. Microservices can save from the solidity of the application! But how so? After all, they are for the backend! * incredible surprise *

What are microservices?


In simple terms, microservices are a development technique that allows developers to make independent deliveries of functionality (releases) for different parts of the platform, and at the same time releases do not break each other. Independent shipments allow them to collect isolated or loosely coupled services. There are several rules that make such architecture more stable. In short, they can be defined as follows: each service must be small and perform only one task. Consequently, the team working on it should also be small. How large the project and the team can be, explains James Lewis and Martin Fowler:

Developers interacting with microservices call different sizes. The largest of them meet the strategy of Amazon about the " team for two pizzas " - no more than 10-12 people. The reverse pole - a team of 5-6 people, where each supports one service.

Here is a diagram explaining the difference between a monolith and microservices:
')


From the diagram it is clear that each service in the microservice system is a separate application, except for the UI - it remains a single whole! When all services are supported by one team, the risk is great that as the company grows, the frontend team will no longer be able to keep up with the UI. This is the vulnerability of this architecture.



Architecture can bring organizational problems. Suppose the company has grown and adopted flexible development methodologies (I’m talking about Agile). They require small cross-functional teams. Of course, in our abstract example, managers will begin to separate the tasks of the frontend and the backend, and the cross-functional teams will not be truly cross-functional. And all efforts will be in vain: the team may look flexible, but in fact it will be very divided. Managing such a team is not for the faint of heart. At each scheduled meeting there will be a question: are there enough frontend tasks, are there enough backend tasks in the sprint? To solve these and many other problems a couple of years ago the idea of ​​microfronted arose, which quickly gained popularity.

Problem Solving: Microntends


The solution looks pretty obvious, because similar principles have been successfully used for a long time in the work on backend-services: divide the monolithic frontend into small UI-fragments. However, the UI is not quite similar to services - it is the interface between the end user and the product, it must be thoughtful and systemic. Moreover, in the era of one-page applications, entire applications run through the browser on the client side. These are no longer simple HTML files, these are complex components that can encompass different UI and business logic. Now, perhaps, it is necessary to define micron-readings.

The microntend principle: the presentation of a website or web application as a set of functions for which independent teams are responsible. Each team has its own mission, its own field of work, in which it specializes. The team is cross-functional and develops
the whole cycle - from the database to the user interface ( micro-fontend.org ).

My current experience shows that many companies may find it difficult to accept the architecture proposed above. To others - the load of the old code does not allow to switch to the new architecture. Therefore, a smoother, easier and reliable way of migration is needed. Having considered the architecture in detail, I will try to offer my own vision of the problem solution. Before delving into the details, let's get acquainted with the terminology.

General structure and some more terminology

Imagine that we divide the structure of a monolithic application vertically, according to business functions. We get several smaller applications with the same structure as the monolithic application. But if we add a special application on top of these small monolithic applications, users will interact with it. It, in turn, will integrate the UI of those small applications. Let's call this level a binder, because it takes the UI-elements of each microservice and connects them into a single interface - this is the most direct implementation of the microfrontend. * sincere admiration *



To make it clearer, in the following I will call every small monolithic application a microapplication , since these are not just microservices, but standalone applications — each of them has UI elements and each of them represents a full-fledged business function. As you know, today's frontend ecosystem is very diverse and can be quite complex. And such simple, obvious solutions may not be appropriate in the process of product implementation.

Problems to solve


When the idea of ​​this article was born, I started a topic on Reddit to discuss it. Thanks to the community members and their responses, I can provide a list of problems that need to be addressed.

Problem number 1: achieve consistent and consistent behavior from the UI, when we have several completely autonomous micro applications

There is no panacea, but there is an idea to create a common UI library, which would also be an independent micro application. In this case, all other micro-applications will have to depend on this UI library. And it kills their independence.

Another option is to create common CSS variables at the root level. The advantage of this solution is that we get a global custom theme for all applications.

Or we can make SASS variables and impurities common to all commands. Among the drawbacks of this approach will be the repeated implementation of UI-elements and the need to constantly check the design of similar elements in all micro applications.

Problem number 2: make sure that one team does not rewrite the CSS of another team

First, you can limit the scope of CSS using selectors, formed by the name of the micro application. By placing this constraint on the link level, you can reduce the overall development time, but at the same time the link level responsibility will increase.

Secondly, you can force each micro app to become a custom web component . The advantage of this approach is that the browser deals with the limitation. However, everything has a price: with Shadow DOM it is almost impossible to render on the server side. In addition, custom elements are not 100% supported by browsers - especially if you need IE support.

Problem number 3: make global information common to different micro-applications

This problem is one of the most common, but is solved quite easily. HTML5 has a fairly powerful functionality, almost unknown to most front-end developers.
One of these functions is custom events that allow you to make information common to micro applications.

You may also be helped by the implementation of a pub-sub or T39. If you need a more subtle global state handler, you can implement a small generic Redux - this results in a more reactive architecture.

Problem number 4: if all micro-applications are autonomous, how to perform routing on the client side?

Solving the problem depends on the implementation. All major modern frameworks have powerful client-side routing mechanisms using the browser history state. The problem is which application is responsible for the current route and when.

My pragmatic approach is to create a common client router that is only responsible for the top-level routes, and the rest is given to the corresponding micro applications. Suppose we have a route definition /content/:id. The common router will decide the c /content , and the solved route will be transferred to ContentMicroApp. ContentMicroApp is a standalone server that will be called only with /:id .

Problem number 5: Do we really need SSR (server-side rendering), is it possible using microntend?

Server side rendering is not an easy task. If you want to associate micro-applications with iframes, forget about server-side rendering. Similarly, web components for binding are not stronger than iframes. However, if each of the micro applications is capable of rendering content on the server side, then the link layer will be responsible only for combining the HTML fragments on the server side.

Problem number 6: “Integration with the existing environment is necessary as air! How to make it?

To integrate with existing systems, I want to describe my vision, which I call “ gradual introduction ”.

First of all, we need to implement the connecting layer so that it has the functionality of a transparent proxy server. After that, we can define the existing system as a micro application ( LegacyMicroApp ), by declaring a special route to it. All traffic arriving at the connecting level will transparently be proxied to the existing system, since we do not yet have other micro applications.

The next stage is a gradual introduction. We will take a small piece of LegacyMicroApp , removing the main navigation, replacing it with an addiction. This dependency is a micro application implemented using a new brilliant technology, NavigationMicroApp.

Now LegacyMicroApp will intercept all routes through the NavigationMicroApp dependency and process it internally.

Then in the same way we redo the footer.

So we will continue to bite off LegacyMicroApp bit by bit until nothing remains of it.

Problem number 7: orchestrate the client side so that you do not have to reload the page

The tie layer solves problems on the client side, but not on the server side. On the client side, we can’t download single parts by loading a single HTML. Therefore, we need a mechanism that loads fragments asynchronously. The problem is that these fragments may have dependencies, and these dependencies need to be able to be resolved on the client side. This means that the microntend solution should offer a mechanism for downloading micro applications and dependency injection.

The problems listed above can be combined into the following topics:

Customer side


Server side


Flexible and powerful but simple architecture


For this, it was worth tasting the beginning of the article! The basic elements and requirements of the microntend architecture have finally begun to emerge;)

Guided by the identified requirements and concerns, I began to develop a solution called microfe . * anticipation of feedback *
Here I will outline the architecture of the project, describing in brief its main components.

The easiest way to start is on the client side, which has three separate main structures: AppsManager, Loader, Router , as well as one additional, MicroAppStore .



AppsManager
AppsManager is the core of micro client-side orchestration micro applications. The main task of AppsManager is to create a dependency tree. Once all dependencies are resolved, AppsManager runs the micro app.

Loader
Another important part of client side orchestration is Loader. He is responsible for downloading applications for the client side.

Router
To perform client side routing, I implemented the Router in microfe. Unlike conventional client-side routers, the microfe router has limited functionality. It does not process pages, but micro-applications. Suppose we have the URL /content/detail/13 and ContentMicroApp. In this case, the microfe router will process the URL to /content/* and invoke the ContentMicroApp /detail/13 .

MicroAppStore
To solve client interactions between micro-applications, I implemented MicroAppStore into microfe. It has similar functionality as the Redux library, but with one nuance: it is more flexible in terms of asynchronous data changes and reducer announcements.

***


The server side is perhaps a bit more complicated to implement, but it has a simpler structure. It consists of two main parts - StitchingServer and MicroAppServer.

MicroAppServer




The minimum possible functionality of MicroAppServer can be expressed as: init and serve.
When MicroAppServer is loaded, the first thing that it should do is call SticthingServer and register endpoint with the announced micro application. It determines the dependencies, types, and URLs of the MicroAppServer schema. I think it’s unnecessary to talk about serve - there is nothing interesting here.

Stitchingserver




StitchingServer allows you to register an endpoint with MicroAppServers. When MicroAppServer registers with StichingServer, StichingServer records the MicroAppServer ad.

Later, StitchingServer uses the ad to allow MicroAppServices from the required URL.

Having resolved MicroAppServer and all its dependencies, the corresponding public URL appears in the names of all the corresponding paths in CSS, JS and HTML. An additional step is to add the unique MicroAppServer prefix to the CSS selectors to prevent a conflict between micro applications on the client side.

Then the main task of StitchingServer enters the scene: the layout of all the parts received and the return of the entire HTML page.

A few words about other implementations


Even before the term microfrontend appeared in 2016, many large companies tried to solve similar problems - for example, Facebook with its BigPipe .
Now the idea is gaining momentum. Companies of various sizes are interested in this topic, investing time and money in it. For example, Zalando provided the open source code for its solution, Project Mosaic . I can say that microfe and Project Mosaic follow similar approaches, but with some fundamental differences. If microfe resorts to fully decentralized routing to make each microapplication more independent, Project Mosaic prefers centralized routing and pattern definition for each route. By the way, Project Mosaic allows you to easily conduct AB testing and dynamic template generation on the fly.

There are other approaches, in particular, the use of ifram as a link layer - obviously, not on the server side, but on the client side. This is a very simple solution that does not require a special server structure and the involvement of DevOps. It can be implemented by the front-end team independently, which means it creates less organizational problems for the company and costs less.

There is also a single-spa framework. The project relies on naming conventions for each application to authorize and download micro applications. Easily catch the idea and follow the patterns. So the framework can be useful for dating and experimenting with the system in your local environment. The minus of the project is that you have to build each microapplication in a strictly defined way - otherwise, the framework may not accept it.

Conclusion (and links)


I think that over time, the microntend theme will be considered in more detail. If it starts to attract the attention of more and more companies, then this concept will become a development method in large teams by default. For each front-end developer, it will be useful in the near future to get acquainted with this architecture and gain valuable experience working with it.

micro fe app registry server
micro front end infrastructure

Source: https://habr.com/ru/post/459540/


All Articles