Key Takeaways
- Microfrontends can help developers to focus on functional requirements and business needs.
- Microfrontends can accelerate time to market, increase reuse within your company, and simplify the development process.
- Possible trade-offs are extra complexity, integration costs, and negative impacts on UX and performance.
- Building a platform for microfrontends is quite similar to building one for microservices.
- Mashroom Server can be the integration component for such a platform.
This may be familiar to you: The business department hires a few developers or a contracted firm to quickly build a small new site to sell a product. And at some point, they hand it over to the IT department and that’s where the difficulties begin. Usually, the IT people get angry at the business people because they commissioned someone to create a new site without even consulting them first. And now they have to integrate it into their systems. Most probably it is written with some technology no one in the IT is used to and it certainly does not respect the domain model and the processes already in place.
But why did the business guys bypass the IT department in the first place? Maybe they had little confidence that they could get what they needed following the official path. Sometimes, such requests often lead to tedious discussions about whether or not a requirement is fitting into the overall architecture and the existing application landscape. Perhaps they simply knew the IT was completely overloaded and there was no chance to get something on such short notice.
A way to solve this problem could be to define minimal requirements for these small, microfrontend applications, without needing to include all the overhead of building and deploying stand-alone applications. To accomplish this, a microfrontend platform is needed to provide standardized deployment and integration with existing processes and sites.
This article describes how to build a microfrontend platform and what benefits and difficulties you can expect.
Reasons why
Of course, microfrontends are no golden hammer and you need good reasons to accept the extra complexity and integration costs. In addition to the reasons in the introduction, there could be others:
- Newly hired developers need to be productive almost instantly without any introduction phase - ideally using the technologies they prefer and are used to.
- Your business applications tend to live 5+ years and you want to mitigate the problem of relying on a single web framework that might not exist that long.
- Your company has invested in some commercial systems like CMS, CRM, or a Portal, but you want to prevent your internally developed applications from becoming useless when the system is going to be replaced. Also, you want to integrate existing applications there.
- You are going to develop a huge web application that simply cannot be maintained as a monolith (at least not for a long period of time).
- You need to add new features to a legacy application that is no longer maintained (or no developers are available anymore).
What is a microfrontend?
Because "microfrontend" is a current buzzword, let me clarify what I mean by the term, in the context of this article.
Some definitions that can be found online are:
- "An architectural style where independently deliverable frontend applications are composed into a greater whole." (Micro Frontends by Cam Jackson)
- "[...] to think about a website or web app as a composition of features that are owned by independent teams. […] A team is cross-functional and develops its features end-to-end, […]" (Micro Frontends - extending the microservice idea to frontend development by Michael Geers)
These are not new ideas. The concept of building sites from small web applications integrated via hyperlinks is (still) very common. There have also been a lot of concepts of rendering pages from smaller, independent building blocks in the past, such as Java Portlets.
Even if the term microfrontend nowadays is used to refer to modern JavaScript apps, there are multiple possible approaches. So, when I use it in this article I refer to an application that:
- is basically a JavaScript Rich Client (for example a SPA or a Web Component) that runs isolated within an arbitrary DOM node and is as small and performant as possible.
- does not install global libraries, fonts, or styles.
- does not assume anything about the site it is embedded in; especially it does not assume any existing paths, so all the base paths to assets and APIs must be configurable.
- has a well-defined interface consisting of the startup configuration and some runtime messages (events).
- should be instantiable (so it could exist multiple times on the same page with different configurations).
- ideally inherits the shared styles from the site and ships only styles absolutely necessary to define its layout.
These technical requirements lead to very specific properties:
- This kind of app almost runs everywhere. You only have to provide a small integration layer to be able to configure and launch it. You can even package it as a mobile app or integrate it into a fat client via Web View.
- It is flexible in use since the behavior can be made configurable and it (ideally) adapts to the style of the host site.
- No vendor lock-in whatsoever - at least when you use a mainstream framework to create the SPA or Web Component. You don’t have to stick with the framework - just use a different one for your next microfrontend or even rewrite the existing ones. (That should be easy since they are small.)
- Within the given constraints, developers can adapt the used frameworks and tools with every new microfrontend (so bad design decisions may not have a big impact).
- Since there should be no dependency on the site where your microfrontends are being hosted, they can be developed completely standalone, as any normal SPA would be.
What does this mean in terms of business requirements?
- You can define specific microfrontends that fulfill a specific business need for example Customer Search, Customer Details, and Customer Products which then can be reused in different business contexts (eg. new business, maintenance processes, customer service processes, etc.).
- You can combine several of those microfrontends for user-specific dashboards, process flows so that different business views and business processes can be supported.
Microfrontend integration
The integration of microfrontends is possible on different levels:
- At build time, which results in a runtime monolith and allows no independent deployments.
- On the server-side with some template engine, (for example with Mosaic), which allows independent deployments but no loading (and unloading) at runtime.
- At runtime on the client-side, which is the most powerful method and the one I’m going to discuss here.
From a purely technical point of view, client-side integration isn’t really complicated. Assuming that your bundled microfrontend exposes some global launch function, it could be as simple as loading the bundle and calling that function:
But then quickly some questions arise:
- Where does the customerId come from? Maybe it should be passed at runtime from some other microfrontend (e.g. a Search). But how?
- The showDeleteButton probably depends on some security roles, but where do they come from and how can we map it to such fine-grained permissions?
- How can we access the backend API which might not be reachable from the client?
- Where do we store the microfrontend instance configuration?
To tackle these and similar problems, we’ve started an integration platform for microfrontends and open-sourced it some time ago: Mashroom Server.
To integrate an existing microfrontend into Mashroom you simply have to add some metadata to package.json and a bootstrap method with a specific signature. The server can register local NPM packages or microfrontends running on remote servers that expose /package.json.
For the example above, you just need to add this to package.json:
And a global bootstrap method:
As you can see, the bootstrap method takes a bit more than just a simple config object. The portalAppSetup also contains user information and a permissions object. The clientServices is used to inject handlers for common services such as the message bus, which can be used to exchange messages via publish/subscribe. The use of those services is fully optional.
Here’s an example page with multiple microfrontends written with different frameworks:
Building a Platform with Kubernetes and Mashroom Server
First, we need to establish what a deployment unit (a Docker image) would consist of:
- The resources of the microfrontend (JS, CSS, assets)
- An HTTP server to deliver the resources
- Typically a REST API to simplify the communication with the actual backends (a backend for frontend)
- Maybe an HTML page or template that allows it to call the microfrontend standalone
Deploying and operating such units on Kubernetes is not very different from deploying and operating microservices and the same best practices can be applied. So, I’m not going to discuss this here in detail. In fact, if you already have a platform for microservices, you could just use it for microfrontends as well.
The missing building block is the component that allows it to integrate the separately deployed microfrontend into pages or cockpits. This component requires all the features you would expect from any Portal:
- Authentication and role-based authorization
- A way to define sites and pages
- Support for theming
But it also requires a set of features very specific to the microfrontend approach:
- A microfrontend registry and the ability to scan the Kubernetes cluster for new deployments.
- A way to compose pages from microfrontends (either statically or dynamically).
- Automatic proxying of API calls so the microfrontends can reach their backends behind the Portal (in most cases not directly reachable from outside of the cluster).
- A way to make its static assets (e.g. images) accessible to the microfrontend.
- Ability to establish communication between the microfrontends on a page (or even beyond).
That’s where Mashroom Server comes into play. It supports all those requirements and has a very flexible plugin architecture and a lot of existing plugins for authentication, persistence, messaging, and monitoring.
So, the platform could look like this:
The Microfrontend Proxy allows Legacy Systems to fetch the microfrontend resources and connect to APIs on the platform. The common services can of course also be used by the microfrontends.
We provide a GitHub repository with scripts to set up such a platform on Google Kubernetes Engine within minutes. It should be simple to adapt it to other Kubernetes platforms.
Lessons Learned
Building Mashroom Server and using it in projects has provided insights into technical and non-technical aspects that need to be considered when adopting a microfrontend architecture.
Organizational Structure
Similar to building microservices, the key is to allow the DevOps teams to build and operate their microfrontends as independently as possible. Remember, their goal is to ship something of value to the business people as quickly as possible. To allow them to work efficiently and quickly with regard to the platform and integration complexity, there should be a Platform Team to support them. The Platform Team is responsible for the platform and sufficient guidelines of everything to keep in mind so new microfrontends integrate smoothly. Ideally, the DevOps teams can plan, develop, and ship a new microfrontend without even bothering the Platform Team. Furthermore, they should be able to access logs and monitor for operational problems by themselves. Otherwise, the approach will not scale and the Platform Team will be overloaded.
Here a simplified diagram of the organizational structure:
What is missing from the picture is Governance. Especially if you’re using this approach at large scale you need some oversight to prevent uncontrolled growth and to maximize the reuse of existing microfrontends. If you already have a lot of microservices or a WebService catalog, you would do something very similar in that regard.
User Experience
It turned out that User Interface Design and User Experience are the most critical parts when building systems with microfrontends. This is because a completely heterogenous UI with different colors, fonts, and even different behavior is difficult to accept by business people and users alike.
So, I recommend the following:
- Make sure the developers understand that a microfrontend should never contain any style, except what is unique to the microfrontend.
- At the same time, provide them with CSS (a theme) they can apply during development so they can see how it will look when integrated.
- Define all common UI components in pure CSS (or SASS, or LESS, or whatever you prefer). Then the teams can still use the JavaScript framework of their choice. They just have to use the predefined CSS classes.
- Make sure you have an expressive UI guide that covers not only the single UI elements but also the most important interaction patterns.
As you will see, style and UX introduces coupling between the teams, but there is no way around this. Because of the very nature of CSS, you could argue the coupling is actually quite weak.
Talking with Business People
When you talk to business people about this approach and its pros and cons, you should highlight that microfrontends allow them to focus the discussion on required business functionality. Fewer non-functional requirements need to be discussed before you can start with the implementation.
Other advantages you could mention are:
- The approach makes it very likely that the IT department can find developers to start the implementation quickly since they don’t need specific framework skills or knowledge about existing systems (given your backend APIs are well documented).
- After some time you will have a catalog of existing building blocks that are going to accelerate the development of new UIs. For example, if you have a microfrontend that shows some basic customer data, you don’t have to implement that part anymore and you can save some time and costs.
- Themable and configurable, microfrontends can be reused for different sales channels and subsidiaries, even if the behavior and "look and feel" needs to be quite different.
Microfrontend Granularity
The question you will hear a lot is "How do you cut the microfrontends?" Here you will see some differences to microservices, where you would try to identify domain-model boundaries and prevent overlapping responsibilities. In the microfrontend world, on the other hand, reusability and scalability are the main concerns and some redundancy is no big deal.
Another major difference is who is going to decide: Since microservices live hidden in the back and cover general-purpose business processes, the decisions are made by Enterprise Architects or Software Architects. Microfrontends, on the contrary, live in the user space where UI Designers and UX Experts are responsible.
Since UI Designers usually work with reusable building blocks, they might already have identified the microfrontends without even knowing it. They might even have names for them that they use to communicate with the business people. So, the microfrontends should appear almost naturally from an existing design. This is another reason why the microfrontend approach requires a strong focus on UI/UX.
Make sure the UI Designers are aware of the existing microfrontends so they regard them as a fixed building block and don’t introduce any changes. This will also lead to more standardized UIs throughout the company.
If you have difficulties deciding how to decompose an existing design, these questions might help:
- Which building block could be reused in other applications?
- Which building block would even make sense as a standalone application?
- If you mention the name of the building block to business people, could they picture it?
As soon as you have established a microfrontend platform you will also see the back to front approach, where teams start so implement views for their microservices without even knowing if and where they will be used. This is also a valid approach, but you can expect that those views need some refurbishment as soon as they are actually integrated somewhere.
Quickstart Templates
As I established before, even if there need to be rules in place for deployment, UI/UX, and integration, there is a high level of freedom when it comes to the development of a new microfrontend. So, most teams will be grateful for some structure and a starting point.
It makes sense to provide a couple of microfrontend template projects to speed up things. They should not only cover the actual source code, but also the server that is going to deliver the resources (it could also be a Backend-For-Frontend), the Dockerfile, and all the necessary build and deployment pipelines. Basically, everything that is necessary to ship the microfrontend to production as fast as possible.
This also reduces the necessary amount of written guidelines. And it allows you to reduce the number of different frameworks used in microfrontends by providing just one template project based on some common mainstream UI framework. Since most teams will just use it (again, they don’t have to) this will lead to a quite homogenous code base, which usually mitigates the feared risk to have every existing framework used (IT managers will appreciate this).
Security
Security has of course several levels and aspects. The main aspect we will cover here is authentication and authorization. For other aspects like XSS, SQL injection, DOS attacks, and such, a microfrontend platform is no different from other web applications and has to be secured.
So, security is crucial, but also complex and big companies tend to have different authentication and authorization strategies and multiple identity providers. Ideally, the microfrontends don’t have to bother with them (and neither should the developers).
It makes sense that the web application that hosts the microfrontends does the authentication. And also some basic authorization such as checking if the user has permission to view a page or a microfrontend on that page.
But what about fine-grained permissions within a microfrontend? A good approach here is to define just abstract permissions for your microfrontend. Basically just keys like deleteCustomer or assignDiscount. The host application is then responsible for passing a permission object along with the startup configuration like this:
And in your Microfrontend you can hide then the delete button if permission.deleteCustomer is false.
This approach pushes the responsibility for determining the correct permissions to the host application. Mashroom supports this out-of-the-box: If you add a mapping between permission key and user roles to the plugin definition in package.json the permissions are determined and passed automatically. Such a definition could look like this:
What’s really interesting here: If your microfrontend doesn’t deal with access tokens or roles, it can even be integrated into legacy systems with some weird, outdated role concept. The integration layer just has to determine the permissions somehow from them.
But of course, disabling buttons based on some JSON property is not very safe. The user could easily manipulate the DOM and trigger a delete request anyway. So, there need to be checks on the backend side as well. Basically you have two possibilities here:
- The host application adds an access token when forwarding the request to the actual backend. So, the backend can check the permission based on its own rules.
- Every microfrontend has its own BFF (Backend-For-Frontend) and that just repeats the checks on the server-side based on the same permission keys.
The first approach is very difficult to implement in a heterogeneous environment. It could be difficult to generate access tokens if the host application is some legacy system or if there are multiple identity providers.
We usually use the second approach and access the actual backend system via service accounts (which is the only way in many companies). Mashroom can automatically forward the calculated permissions via HTTP header to the BFFs, so the developers can simply check requests from the microfrontend against the permission keys in the header.
The approach looks like this (the Portal can be replaced here with any other host application):
Microfrontend Communication
Interestingly, when I talk with developers about microfrontends one of the first questions always concerns communication. How can they talk with each other?
But in my experience, they don’t talk much. We saw the same with Java Portlet projects many years ago. Even if they talk, they shouldn’t say much, because passing big data structures means tight coupling and you want to avoid that.
So, I recommend the following:
- Use a lightweight publish/subscribe mechanism to communicate (Mashroom has a built-in frontend message bus).
- Only pass tiny JSON messages, for example, some state information or keys. Withstand the urge to use the communication to share state, as some customer data, because typically you will not have a unified model throughout the company. Only pass the customer ID, even if that means that the same data has to be loaded multiple times on a single page.
Shared Libraries
The question of shared libraries will arise on two levels:
- You could consider creating a library with common UI components or common BFF functionality and share it among the development teams.
- It might make sense to share libraries at runtime in the browser, for example, the UI framework libraries, so not every single microfrontend has to ship it by itself.
Be careful with the first one. It makes sense from a software engineering perspective, but it introduces coupling and dependencies between the different teams. You will see teams waiting for each other and you risk the Platform Team becoming a bottleneck. If you develop common libraries, make the usage optional, (the developer should see them merely as an offer), because you won’t lose the ability to start with a clean slate.
But what absolutely makes sense is to create a reference implementation for the UI components you have defined in CSS. The developers can use it directly or as a template if they use a different JavaScript framework. Also, such a library can act as a showcase, which is very useful for new team members.
About the second one: Nowadays most SPA libraries have a quite small footprint (even Angular thanks to AOT compiling). So, loading the runtime multiple times, (for a few microfrontends on a page), is not really a big issue. But it doesn’t feel like a good thing to do. If you need a lot of additional libraries or cannot avoid huge UI component libraries, you will certainly see a negative performance impact. In that case, you can consider using webpack’s Web DLL mechanism or the Mashroom DLL Plugin. But that has only effect if all (or at least most) of the development teams agree on the same framework versions or on kind of a dependency pack - which introduces, again, some coupling.
Performance
We see few performance issues that arise solely because of the microfrontend approach, but the problems can multiply. So, make sure everyone tries hard to make small and performant SPAs and keep low the number of assets that need to be loaded.
You could additionally:
- Make sure everyone thinks twice before using some fat UI component library (that’s where a component reference implementation comes in handy).
- Agree on an icon font (if the company does not provide one anyway).
- Keep using Web DLLs as an option (see above).
- Since the microfrontend approach can lead to a different level of over-fetching where the same data is loaded multiple times, it could be a good idea to add some caching mechanisms, either on the client-side or the server-side.
Open Issues
Routing as you would typically use it in SPAs is a bit of a problem. How should the microfrontends on the page "share" the browser URL? The simplest approach here is to just disallow any URL manipulation. This works if losing the browser navigation is no big deal (which doesn’t seem to be for most users used to enterprise applications). Mashroom also offers a service to encode state into the URL, but that causes a coupling to the host application which is not ideal. So, it might be necessary to define a new pattern here in how microfrontends can share the URL space.
Also, it would be useful to adapt the service registry pattern for microfrontends. Mashroom has a built-in registry but it would be nicer to use some external service like Eureka. Adding metadata to every microfrontend that describes its capabilities would allow use cases far beyond statically arranged microfrontends:
- Instead of placing a specific microfrontend, you could just describe the required capabilities and the host application could dynamically look up the best candidate.
- You could create completely dynamic cockpits that grow as the user wants to see more data. The system just looks up microfrontends that are capable of displaying or editing some data type and loads it.
- You could analyze a customer email (or basically any task) and present the user with a tailored page that contains everything to fulfill the task.
- You could use metadata to select different microfrontends for different user groups on the same page. (Could be used for Canary releases or A/B testing.)
Summary
To be clear, building a microfrontend platform is a huge endeavor. Not only from a technical but also from an organizational point of view, because you have to include the business people, UI/UX experts and basically everyone in the IT department. Everyone involved needs to understand the basic concept and its constraints and benefits. So, it is not just an architectural decision but a transformation process that requires convincing a lot of people.
Additionally, the microfrontend approach itself, (even at minimal scale without a full-blown platform), comes with some trade-offs: You will have to put some extra effort into the integration, to get a seamless user experience and to prevent performance problems. You can also expect that you will have to solve some problems you never had with your monolithic applications (e.g. routing).
On the other hand, it solves a lot of problems, especially the IT departments of big companies struggle with: The inability to react quickly to new business ideas and requirements. High costs because the same UI functionality needs to be implemented multiple times with different technologies. Complex systems that require a long training period for developers until they can get productive. And the constant fear of making the wrong framework decision that ultimately blocks innovation.
Most importantly, the microfrontend approach can help you to hide the non-functional complexity of your IT landscape and allow you to focus on business needs.
About the Author
Jürgen Kofler is a Software Developer and Web enthusiast. He is passionate about tackling complexity and bringing the fun back into Entersprise development. In the last years he focused on strategies to compose sites from smaller, independent building blocks, be it Portlets, Widgets or Microfrontends. Currently he’s working on a large scale Microfrontend Platform in Vienna.