Dispelling myths around micro frontends. Originally published at Bits and Pieces.
Microfrontends are a new trend that goes back for many years. Equipped with new ways and solved challenges they are now slowly entering the mainstream. Unfortunately, a lot of misconceptions are quite apparent making it difficult for many people to grasp what microfrontends are about.
In short, microfrontends are about getting some of the benefits of microservices into the frontend. There’s more to it, and one should not forget that microservices are no silver bullet, either.
Tip: To share React/Angular/Vue components between Micro Frontends or any other project, use tools like Bit. Bit lets you “harvest” components from any codebase and share them to a collection in bit.dev. It makes your components available for your team, to use and develop in any repo. Use it to optimize collaboration, speed-up development and keep a consistent UI.
Nevertheless, while some of the reasons for choosing microfrontends can be summarized, too, in this post I want to list the most common misconceptions I’ve heard in the past months. Let’s start with an obvious one.
Keep in mind that isomorphic rendering comes with its own challenges.
Consider one of the "older" patterns: Using
<frameset>. I hear you laughing? Well, back in the days this already allowed some split that people try to do today (more on that below). One page (maybe rendered by another service?) was responsible for the menu, while another page was responsible for the header.
<frameset cols="25%,*,25%"> <frame src="menu.html"> <frame src="content.html"> <frame src="sidebar.html"> </frameset>
Today we use the more flexible (and still actively supported)
<iframe> elements. They provide some nice capability - most importantly they shield the different microfrontends from each other. Communication is still possible via
<iframe> or similar to get microfrontends working.
Microfrontends can be as simple as server-side includes. With advanced techniques such as edge-side includes, this becomes even more powerful. If we want to exclude scenarios realizing a microfrontend in microfrontend functionality then even simple links work just fine. In the end, a microfrontend solution can also be as simple as tiny, separated server-side renderers. Each renderer may be as small as a single page.
The following diagram illustrates more advanced stitching happening in a reverse proxy.
In nearly every tutorial about microfrontends, the different parts are not only developed by different teams but also using different technologies. This is bogus.
Yes, using different technologies should be possible with a proper microfrontend approach, however, it should not be the goal. We also don't do microservices just to have a real patchwork (or should we say "mess") of technologies in our backend. If we use multiple technologies then only because we get a specific advantage.
Our goal should always be a certain unification. The best approach is to consider a green field: What would we do then? If the answer is "use a single framework" we are on the right track.
Now there are multiple reasons why multiple frameworks may become apparent in your application in the long run. It may be due to legacy. It may be convenience. It may be a proof of concept. Whatever the reasons are: Being able to play with this scenario is still nice, but it should never be the desired state in the first place.
No matter how efficient your microfrontend framework is - using multiple frameworks will always come at a cost that is not negligible. Not only will the initial rendering take longer, but the memory consumption will also go in the wrong direction. Convenience models (e.g., a pattern library for a certain framework) cannot be used. Further duplication will be necessary. In the end, the number of bugs, inconsistent behavior, and perceived responsiveness of the app will suffer.
In general, this does not make much sense. I have yet to see a microservice backend where the data handling is in one service and the API is in another. Usually, a service consists of multiple layers. While some technical things like logging are certainly brought to a common service, sometimes techniques like a side-car are used. Furthermore, common programming techniques within a service are also expected.
For microfrontends, this is the same. Why should one microfrontend only do the menu? Isn't a menu there for every microfrontend to be populated accordingly? The split should be done by business needs, not by a technical decision. If you've read a bit about domain-driven design you know that it's all about defining these domains - and that this definition has nothing to do with any technical demands.
Consider the following split:
These are technical components. That has nothing to do with microfrontends. In a real microfrontends application the screen may rather look as follows:
Granted the stitching is much more complicated here, but this is what a sound microfrontends application should provide for you!
Nope. You should share what makes sense to be shared. You should definitely not share everything (see the next point). But to get consistent you'll need to share at least a set of principles. Now if that is via a shared library, a shared URL, or just a document that is used when building or designing the application does not matter.
For microservices this "share nothing" architecture looks like the following diagram.
In the browser this would lead to the use of
<iframe> as there is currently no other way to prevent leakage of resources. With Shadow DOM CSS may be isolated, but the script level is still capable of touching everything.
Even if we would want to follow the share nothing architecture we would be in trouble. The duplicated resources just to keep simple components alive would cripple the perceived performance.
Granted, the deeper the sharing is (e.g., using a shared library attached to the DOM via an app shell) the problems can arise. However, on the other hand, the looser the sharing is (e.g., just a document specifying the basic design elements) the more inconsistencies will arise.
Absolutely not. If this is the idea then a monolith makes more sense. Performance-wise this may already be a problem. What can we lazy load? Can we remove something? But the real problem is dependency management. Nothing can be updated because it could break something.
The beauty of shared parts is the consistency guarantee.
Now if we share everything we introduce complexity to gain consistency. But this consistency is not maintainable either, as the complexity will introduce bugs on every corner.
The origin of this issue lies in the "dependency hell". The diagram below illustrates it nicely.
In short, if everything depends on everything we have a dependency problem. Just updating a single box has an impact on the whole system. Consistent? Truly. Simple? Absolutely not.
Why should they? True, so far we touched mostly the web, but the concepts and ideas can be brought to any kind of application (mobile app, client app, ..., even a CLI tool). The way I see it microfrontends are just a fancy new word for "plugin architecture". Now how the plugin interface is designed and what is required to run the application using the plugins is a different story.
The following diagram shows a quite generic plugin architecture. Credit goes to Omar Elgabry.
There is no notion of where this is running. It could run on a phone. It could run on Windows. It could run on a server.
Again, why? If the solution is super complex then I would certainly look for a simpler one. Some problems require complex solutions, but usually, a good solution is a simple one.
Depending on the scenario it may not even require a distributed team. Having distributed teams is one of the reasons why microfrontends make sense in the first place, but they are not the only reason. Another good reason is the granularity of features.
If you look at microfrontends from the business perspective then you'll see that having the ability to turn on and off specific features can be meaningful. For different markets, different microfrontends can be used. Going already back to a simple privilege level this makes sense. There is no need to write code to turn certain things on or off depending on a certain condition. All this is left to a common layer and can just be activated or deactivated depending on (potentially dynamic) conditions.
This way code that can (or should) not be used will also not be delivered. While this should not be the protection layer, it certainly is a convenience (and performance) layer. Users are not confused since all they see is what they can do. They don't see the functionality. That functionality is not even delivered, so no bytes wasted on unusable code.
I fear this is partially true, but in general, should not be and (spoiler!) does not have to be. With any kind of implementation (or underlying architecture for the sake of argument) the development experience can be crippled. The only way to fight this is to be developer-first. The first rule in implementation should be: Make it possible to debug and develop. Embrace standard tooling.
Some microfrontend frameworks don't embrace this at all. Some require online connections, dedicated environments, multiple services, ... This should not be the norm. It is definitely not the norm.
While it's true that decoupled modular backends may be a good basis for also decoupling the frontend, in general, this is not the case. It is totally viable to have a monolithic backend that demands a modular frontend, e.g., to allow simplified personalization potentially combined with authorization, permissions, and a market place.
In the same sense, indeed, a microservice backend does not justify applying a similar pattern to the frontend. Many microservice backends are operated by single-purpose applications that do not grow in features, but rather just change in appearance.
A couple of times I've already read that to create a microfrontends solution one needs to leverage mono repo, preferably using a tool like Lerna. I am not convinced of that. Sure, a mono repo has some advantages, but they also come with clear drawbacks.
While there are microfrontend frameworks that require a joint CI/CD build most don't. A requirement for a joint CI/CD builds usually leads to a mono repo, as it's just much simpler to set up correctly in the first place. But to me - this is the monolith re-packaged. If you have a joint build in a mono repo then you can scratch two very important factors that made microfrontends interesting in the first place:
- Independent deployment
- Independent development
In any case, if you see a microfrontend solution that requires a mono repo: Run. A well-crafted monolith is potentially better without having all the issues of distributed systems awaiting in the long run.
Microfrontends are still not for everyone. I do not believe that microfrontends are the future, but I'm also positive that they play an important role in the future.
Where do you see microfrontends shine? Any comment or insight appreciated!