This will be the first article on a series dedicated to the micro-frontends topic. This one will focus onĀ analysingĀ andĀ designingĀ an architecture suitable for an enterprise application at scale while keeping the advantages that a Single Page Application (SPA)Ā provides to our end users.
By the end, youāll have a workingĀ platform with a scalableĀ micro-frontendsĀ approach.
Why Micro-Frontends?
First of all, you might be wondering why do you even need this, right? Well, there are several answers to this question, and Iām definitely not an advocate of using this pattern/architecture ājust becauseā. Iāll provide a concrete example where this makes sense:
Youāre in a big enterprise project, itās a big monolithic application encompassing several different āproductsā inside one single project/product. The company most likely chose this initial model to start fast and get to market soon. Now, they are established and new engineers come to the team.
The team grows a lot, and dedicated teams for each sub-product starts to feel like a good and realistic possibility. Maybe each sub-product can have its own product manager, to extract the most out of each product and to enable the teams to focus on smaller parts of the platform.
Up until now, the teams have been working horizontally, meaning that they are working focused on the technology itself (front-end, back-end), instead of focused on the Product.
The company decides that vertical teams will be more productive on the long term and also more scalable as new engineers get into the company. These vertical teams will hold all the knowledge inside a specific product and will be composed by the 3 related horizontal layers (front-end, back-end, infrastructure), which will also allow growing each product individually.
With this approach, the company can also get more independent teams and deployments, by not being dependent on a single gigantic release to get a new version out to the clients.
Given these changes, how will the front-end reflect them?
Solutions
After a lot of investigation, I concluded that micro-frontends would be the solution to the scenario presented above. But micro-frontends has many faces, many technologies possible, many opinion articles (just like this one) and itās not easy to know where and what to bet on.
Iāll be presenting anĀ architectureĀ that isĀ suitable for scaleĀ but that still leverages theĀ SPA feelĀ and performance that we love to rely on to deliver a great experience to our customers.
The imaginary business that we are going to use for these articles is a store of clothing items (dresses, shirts, pants, etc). This business is a case of the scenario presented above, working in a horizontal structure on a monolithic application. Now, to increase team independence, they want to create 3 vertical teams for the different sub-products of their platform: Items,Ā Checkout,Ā and theĀ Blog.
Architecture
To achieve the objective of the clothing company presented in the previous section, we need to design an architecture that enables all the teams to be independent while connecting all these individual products into a coherent platform.
The following technologies will allow us to achieve what we need:
- ReactWebpack 5 with Module Federation
- Yarn Workspaces with lerna
The following architectural diagram represents an overview of the final solution. This is what weāll achieve at the end of this series of articles on micro-frontendsĀ (MFEs).
All of the components presented in the architecture areĀ MFEs, and as such, each isĀ independentlyĀ developed,Ā testedĀ andĀ deployed,Ā but all of them tie together to deliver a consistent experience to the customer.
App Shell
This MFE will be theĀ single entry pointĀ into the platform.
It will be responsible for rendering the first page to our user and also for invoking the respective micro-frontends as the user navigates through our platform.
UsingĀ client-side navigation, we will be able to asynchronously load the necessary micro-frontendsĀ atĀ run-time, keeping the solution memory and performance efficient.
Itās pretty common to find this App Shell approach in micro-frontend projects, but some of the approaches are not exactly like this. They follow more of aĀ micro-services approachĀ where each MFE can be the entry point for the user, and then that MFE will import the āsetupā code from the App Shell (e.g. Base Layout with the Header and Footer). Although this approach is perfectly valid, it will most definitely not feel like an SPA to the end user while switching between different entry points for the platform, since it will force a refresh each time the user navigates to a domain from a different MFE.
Shared
Inevitably, there are components that will be shared between our different MFEs, and for that reason the Shared MFE will be their home.
Regarding this MFE, in my opinion, there is not a team specifically responsible for this, instead, there should be a ācaretakerā of the MFE that would review any pull request created regarding shared components. This person would be responsible for keeping order in Shared and also for keeping a critical thinking on the following question: āWill this change affect other MFEs?ā.
Items / Checkout / Blog
These MFEs are the actual products with dedicated teams working on them. Theyāre allĀ independentĀ in development and deployment. None of them is directly dependent on the other. Naturally, since the platform works as a whole, we need to cover the possibility of some MFE being broken and handle that gracefully. However, this approach will not let any MFE break the whole platform, as long as we handle the remote imports properly.
Repository Structure
Weāre going to use aĀ monorepoĀ for this solution.
The option to use aĀ monorepoĀ is arguable, and for all the cons it may have, theĀ prosĀ Iām after by using it are:
Easily shareable configurations (linters, tasks, setup jobs, code styles)Better / easier overview of the whole systemEasier to make a gradual migration from an existing monolith
Architecture (in detail)
Letās see in more detail how these components interact with each other and how exactly we enable the features mentioned in the previous section.
The key to making this whole architecture work isĀ Module Federation.
Webpack 5 Module Federation aims to solve the sharing of modules in a distributed system, by shipping those critical shared pieces as macro or as micro as you would like. It does this by pulling them out of the build pipeline and out of your apps.
The excerpt is from the officialĀ Module Federation page,Ā a new feature shipped officially onĀ Webpack 5. If you want to learnĀ moreĀ you can read the information on the website and also check out theirĀ videosĀ section. There is also a wholeĀ GitHub projectĀ with examples using this new technology.
Very briefly, Module Federation provides us the chance toĀ remotely importĀ javascriptĀ code (components, functions, variables) atĀ run-time, while efficiently managing dependencies between different remotes.
Basically, any application can be classified as aĀ hostĀ (in case itās the entry point) or aĀ remoteĀ (in case itās imported by a host). An application can be both a host and a remote at the same time, in which case itās calledĀ bi-directional.
Next, letās analyse some common micro-frontends pitfalls and see how we can get around them using the presented architecture.
Common pitfalls
Dependency Duplication
Using a micro-frontend approach may lead to importing dependencies multiple times. When the components are remotely imported from another MFE, they need certain dependencies to work. Chances are that the MFE importing that piece of code already has some (or all) of those dependencies.
Without highly customised caching for dependencies, the imported components will most likely bring along their own dependencies, causing the host to have multiple instances of the same libraries (e.g. having different versions of React in scope may lead to unexpected problems).
With the presented architecture weāll take advantage ofĀ Module Federation to manage these dependencies. When a remote module is imported at run-time, Webpack will actually check if it already has the dependencies required in theĀ host. If they are there, then the only thing imported from the remote is the actual code.
Dependency Mismatches
In micro-frontends, sometimes different versions of the same library are loaded in the same scope. To avoid this, weāll be using a combination of lernaĀ to manage our monorepo andĀ yarn workspacesĀ to manage the dependencies of our different MFEs.
By using yarn workspaces weāll make sure that the versions of critical libraries are locked for all the micro-frontends, preventing this whole class of problems. This approach reduces independence, but it allows a more robust overall solution.
UI / UX
In a micro-frontends solution, it often happens that theĀ UIĀ andĀ UXĀ diverge between different MFEs. Itās a natural process when different independent teams are each developing their products. At the same time, we need to keep in mind that the different products form the platform as a whole and that platform should provide a consistent experience to the user.
This should happen both in the UI components and in the flows / user experience provided.
In the solution presented, by using a Shared MFE, we are keeping these same visual components and reusable behaviours in the same package. This will guarantee that the UI experience is unique and consistent.
At the same time, weāre giving this Shared MFE the independence it needs to release frequently. This will help fix small problems in the common components without needing to redeploy any other product MFE (like Items and Blog).
Local Development
Local development can be taxing in a micro-frontends solution, sometimes needing to run the whole solution in order to test what you are developing in a small part of that platform.
The way weāre going to solve this is by providing the chance for all the different MFEs to be run inĀ stand-aloneĀ mode. This will be done by providing 2 separate entry points to our MFE, one for the integration with the whole MFE solution and another for the stand-alone entry point.
Conclusion
The architecture presented in this article was achieved after a lot of investigation on the topic and you can check a lot of material in the ReferencesĀ section below.
In the next articles, weāll be deep-diving on how to implement this architecture by following along a github repository with the example project for our imaginary business use case.
Weāll also talk about common challenges that occur when usingĀ Module FederationĀ and what can we do to solve them.
Stay tuned š
If you find this article interesting, please share it, because you know ā Sharing is caring!
If you enjoy working at a large scale in projects with global impact and if you like a real challenge, feel free to reach out to us atĀ xgeeks! We are growing our team and you might be the next one to join this group of talented people š
Check out our social media channels if you want to get a sneak peek of life at xgeeks! See you soon!
References
- https://martinfowler.com/articles/micro-frontends.html
- https://dev.to/marais/webpack-5-and-module-federation-4j1i
- https://blog.nrwl.io/monorepos-and-react-microfrontends-a-perfect-match-d49dca64489a
- https://dev.to/brandonvilla21/micro-frontends-module-federation-with-webpack-5-426
- https://www.nicolasdelfino.com/blog/micro-frontends-module-federation-webpack
- https://indepth.dev/webpack-5-module-federation-a-game-changer-in-javascript-architecture
- https://blog.bitsrc.io/revolutionizing-micro-frontends-with-webpack-5-module-federation-and-bit-99ff81ceb0
- https://medium.com/dazn-tech/orchestrating-micro-frontends-a5d2674cbf33
- https://medium.com/hacking-talent/two-years-of-micro-frontends-a-retrospective-522526f76df4
- https://single-spa.js.org/docs/recommended-setup/
- https://medium.com/swlh/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669
- https://module-federation.github.io/videos
Also read behind a paywall at https://medium.com/xgeeks/micro-frontends-at-scale-part-1-a8ab67bfb773