Good frontend development is hard. Scaling frontend development is even harder because you need to ensure that multiple teams can work on a big and complex project simultaneously and without any blockers. Today you often hear about micro frontends which are one of the more controversial Web topics. What are they? Are they worth all the fuzz? Should you implement them? As someone who worked at integrating this in Infobip’s Web Interface, I want to use our example to show you our way of thinking: how did we know that we have problems, how did we decide to approach the implementation of micro frontend architecture and why did we decide to go with it, and which problems we ran into. We will also look at alternate available possibilities useful for anyone.
3. ARE YOU USING MICRO
FRONTENDS ALREADY?
Possibly ... yes, we are!
HOW IS THAT POSSIBLE?
Deploying only parts of the
application or page separately, ...
10. MICRO FRONTENDS
No unified approach!
Micro Frontend 1
Micro Frontend 2
Micro Frontend 3
SOURCE CONTROL BUILD & TEST DEPLOY Front-end
integration
Team Jaran
Team Pero
Team Rendo
11. CORE IDEAS
Technology Agnostic Isolate Team Code
Naming Convention
Resilience
Favor Native Browser
Features
e.g. infobip- prefix, namespaces, etc.
Separate application builds!!!
Application stability
12. Independency is the key!
N projects (15 < N < 45)
WEB INTERFACE:
Every project has navigation
MICRO FRONTENDS
15. Any Infobip
Web Interface
application
Type definitions for
navigation
Navigation code
bundle
Backend API
endpoint
Retrieve & Prepare
navigation infoNavigation data
ready
Resolves & renders
navigation in the browser
Navigation info
19. WHY THIS APPROACH?
Own implementation
Bundle optimizations
Single version of dependencies
https://reactjs.org/docs/error-decoder.html/?invariant=321
Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons: 1. You might have mismatching versions of React and
the renderer (such as React DOM) 2. You might be breaking the
Rules of Hooks 3. You might have more than one copy of React
in the same app See https://fb.me/react-invalid-hook-call for tips
about how to debug and fix this problem.
28. OUR CHOICE ... WHY?
Already N projects
Gradual
implementation
Requirements Time to implement
Looking into the best
option
29. SHARED TOPICS?
Team Jaran
Team Pero
Team Rendo
Web Performance is key!
Establishing a common
Design System
Knowledge sharing
Team Duje
30. Feature development
No monoliths
Be able to keep changing
Independence
Useful for medium-large projectsRedundancy
Consistency
Heterogeneity
Requires more front-end code
and local development setup
Not good for smaller
development teams
Good sides
Bad sides
The Good, the Bad and the Ugly …
31. ARE MICRO FRONTENDS
WORTH ALL THE FUZZ?
In Infobip’s use case ... yes!
Analyze your projects and
decide what to do!
Remember: there is no perfect solution!
Hi everyone, thank you for joining. Today I will introduce you into the world of micro frontends and how we implemented them, so let’s start.
Hi. My name is Ante, 26 years old Software Engineer working in Infobip for 4 years. I am focused on technical side of Web and recently I am focusing on React, Webpack, Node.JS and optimization of application and project infrastructure and build processes. You will find me holding a talk on local meetups and I always hope to pass my knowledges and experiences through presentations, meetups and conferences to the other developers. (00:50) In my free time I love exploring and hobby photography, so you will often find me with my camera.
(00:50) Let’s start with an important question. Are we already using micro frontends? It is likely that we are! Usually when working on projects, we think about project optimization and optimizing developer experience by avoiding frequent re-deploys. We usually do that by separating parts of the application, so that we can deploy them independently. Well, by doing that we changed application archutecture and actually implemented some sort of microfrontends without being aware of it! Let’s check how this is possible.
The idea of creating independent modules from parts of the application is definitely not anything new as micro frontends are more an arhitectural approach than a specific technique, but the name is new and it came from micro services. Micro Frontends are obviously more friendly and bulky term under which this concept became more popular and present.
To understand this better, I will take an example of navigation in Infobip’s Web Interface. This is our main Web product that is a collection of N different projects. Those projects can be separate applications, even parts of the application. We will focus on the general picture, so let’s start by checking the implementation we will explain.
If we look at this screenshot, we can see a web application that contains navigation. But, as this is a big product with N projects, this is not the only application and we have navigation shown in N different projects. So, this is component that is common for absolutely every project.
(03:00) Let’s check the structure. We have projects split up into multiple separate areas, so that it is easier to support them by multiple teams. As these are Node.JS projects, the foundation are dependencies like react, lodash, moment, webpack, and more. Some projects also can have additional dependencies or some sort of store or state management if necessary. Or common parts necessary and usually present in all applications. And of couse, then we have components. Now, we saw the navigation in the previous slide which contained the screenshot and we know that it is global. What should we do with more present components like that? Extract them into a separate library!
What problem do we have? Our Web applications are monolithic meaning that they contain everything. Even though we separated some parts to libraries, it is not enough because we are building and including components and everything into application code bundles. And that is not good because code bundles are a static code what means that we have to do the re-deploy whenever we have changes. But how could we solve this? We can optimize our applications by scaling and decomposing parts into independent modules which can then be deployed separately from the application. Let’s dive in more to check what this means.
(04:51) We can define micro frontends and micro services as architectural approach that allows us to split up the application into independent multiple areas. With micro services we get agility and independence because teams can take ownership for their services, easier deployment because one micro service is not dependent on the others, technological freedom because we can choose what to use, and resilience because service independence increases an application’s resistance to failure. This is related to the micro services architecture, but the main difference is that micro frontends include user interface. By doing this we avoid monolithic UI where change in one part can have unexpected effects on the other side, so let’s dive into micro frontends and learn more.
(05:57) There is no unified approach for implementing micro frontends, but we have a general goal. By separating monolithic UI into the smaller independent modules we optimize them for feature development by having a separate front-end modules each maintained by the team that owns its stack. In our example, let’s say that we have Team Jaran, Team Pero and Team Rendo. As we see here, each micro frontend is independent: it has own build and deploy after which the new feature is shipped directly to the customer without depending on anyone else. To better explain and understand how we can approach implementing micro frontends, let’s start by exploring the core ideas.
To start with, we should be technology agnostic – each team should be able to choose and upgrade their stack without coordinating with other teams. Your application modules should be independent what means that code between applications shouldn’t be shared. Establish some sort of naming convention, especially for parts which can’t be isolated yet. Try to rely on using native browser events for communication instead of Custom APIs, but if you need API, keep it as simple as possible. And lastly, your modules should be resilient and work even if something else fails.
It is important to say that independency is the key. A lot depends on how applications are set up because there are multiple approaches for implementing this. To easier understand the implementation, let’s set up some conditions. All projects are in React. We have N projects and each one of them uses the same navigation code that we will decompose. Let’s assume we are using Amazon Web Services and CDN.
In Infobip Web Interface it makes sense for us to extract navigation and additionally error page because they are common and should work consistently everywhere. Additionally, we are also extracting the translation system which we are improving and unifying at one place, so with its global usage it makes sense to extract it. We are working on expanding this, so we are just getting started.
(09:00) Let’s check general picture. Our navigation module contains build and deploy stages by using Jenkins. We are keeping them separate because we want to choose by ourselves which micro frontend to deploy and when. When we run application build, we determine the navigation version. This is important because we need to have the version ready for deploy. Then with that version, we make type definitions and Webpack-generated code bundle for CDN. Then when we want to deploy, we start the Jenkins deploy job by choosing the desired version and environment. That job will save the version information to Redis.
In Web application we use Redis to retrieve and prepare any neccessary information about CDN links and version by using our application back-end. Then as soon as we have everything ready, we pass all of it to any Web Interface application. With that information it has everything necessary to dynamically fetch and render the navigation in current client’s browser without any issues. (10:21) Then as a final result, application is loaded with navigation visible and there is no change for user in comparison to earlier.
(10:27) In application we resolve navigation dynamically right before we need it. We do this asynchronously with what we ensure that the navigation is loaded before the application. Performance and UI are not affected as fetching the bundle doesn’t take more than 80 milliseconds. When we load the bundle, it automatically makes navigation available with window scope and Webpack for using. As we share some dependencies inside every application, our navigation code bundle uses them as Webpack extenals. This means that everything under window is resolved as EcmaScript import. We will explore why we did this, but let’s first talk about how we develop new features for modules like navigation.
For micro frontend modules we always have one local development and one production build. Production is easy because our module is loaded into applications and we can immediately see the result and if everything works. But, local development is challenging because we need to ensure that we can test everything like in production. (11:49) Everything should be inside your repository and you should avoid using something like monorepo as it is considered an anti-pattern and doesn’t allow you to take advantage of organization benefits of independent teams and applications.
Our modules in Infobip usually consist of 2 parts: exposed modules and demo page for local development. Demo page includes exposed modules and is done to mimic the functionalities and behaviour from production. Apart from demo page, we also need web application server like webpack-dev-server. And sometimes you will need to make API requests and then it is good to ensure that you are making the calls same locally and in production. You can use HTTP proxy server which can then take care of that. Also, additionally today’s Web development is quite powerful and you have a lot of possibilities. (12:41) It is important to remember that your module shouldn’t know or care about the development environment of other teams, so definitely try to avoid dependency like that.
Why did we do this approach? Like most developers, we decided to go with own implementation based on our needs. The second reason is optimization of navigation bundle. This helped us significantly reduce the bundle size by 60 percent. The third reason is ensuring the single version of dependencies, especially for React libraries. Let’s check the React error message quote below. Just read what is bolded in red colour. We learned this lesson the hard way when we had issue with one shared component using React Hooks and after a little bit of investigation, we realized that having multiple copies from React even from the same folder in node_modules still causes issues.
(13:44) There are two more main reasons. The first is less complexity. By breaking up the application into smaller parts, we can deploy those parts independently. And then that means that it is easier to maintain the projects because they are noticeably smaller and you are not depending on anything else to update your micro service module.
We can also use some existing solution instead. We have 3 categories: client-side frameworks which allow for client-side micro frontends while also offering server-side rendering; server-side frameworks which are usually libraries or frameworks for Node.JS Express, but can also come in a form of services rolled out into your infrastructure; and helper libraries that either provide some infrastructure for sharing dependencies, routing events, or just bringing together different micro frontends and their lifecycles. Helper libraries are our desired direction and the implemention worth mentioning here is Webpack’s Module Federation.
But first, let’s check all solutions. We see interesting trend here: client-side and server-side frameworks are less popular, while helper libraries like Single SPA and Postal are more popular. They are still far from popular libraries, but this shows that developers are getting interested for sharing dependencies and bringing different microfrontends together. This is expected as micro frontends are starting to gain more traction.
Let’s first talk about downsides of our approach. There is a lot of manual work as we have to cover everything by ourselves like loading foreign modules, script tags, on-demand loading, and more. The problem is also resolving shared dependencies because we need to be careful about for example resolving React versions problem we mentioned. And any own solution requires a lot of trial and error to be sure that everything is working properly and they can take time to properly implement and test. But, can we improve this?
(16:33) There is always room for improvements, but you have to be aware of something: no solution is perfect and you can find good and bad sides anywhere. The most important things are developer and customer experiences, so it is good to think about build and Web performance and especially automation, so that you have less manual work. You can even consider a different solution, so let’s check Module Federation.
If we take Module Federation and compare it with our solution, we can see that automation and less manual work are problems because we can’t achieve that at the moment. We can get good build performance by optimizing bundles, Web performance by optimizing what we load and how, and shared dependencies to some extent by using window global scope and externals, but let’s dive in into Module Federation because automation makes a difference here.
With Module Federation idea is same – we want application parts as a separate build. We will call those separate builds Containers. Containers can be referenced either directly by Application or by other containers and in such relationship the container is the remote and the consumer of the container is the host. The remote can then expose the modules to the host which can then use them. We call those modules remote modules. And this means that we have separate builds what brings us independence and much better build performance.
But what actually happens? Normally we have exports as EcmaScript Modules. Here we specify which modules to expose in remote, so that the hosts can consume them. That is done asynchronously what allows the build to put each exposed module in a separate file with all necessary dependencies. Thanks to async, we load only what is really necessary what ensures good Web performance because we make only necessary requests with the number of total downloads low. Another thing we saw with navigation is sharing dependencies. Here we have shared modules. Any container and application can put shared modules into the share scope together with version information. And they can consume shared modules available in the share scope together with the version requirement check. We will always get from share scope the highest available version of a shared module within the version requirement. And shared modules are also provided in async way meaning that providing them has no download cost and we download only what we really use.
(20:00) We chose the externals solution because we already have N projects and we wanted to start gradually. We wanted more instant results due to incoming navigation updates that gave us a good starting point we can build. Client-side and server-side frameworks would take us too long, so we knew that we would go for our own solution or a helper library. But this doesn’t mean that we are not looking into the other options. We are waiting for Webpack 5 to be out of beta and after that we are planning to test Module Federation and possibly integrate it, so the future is bright. (20:47) But remember, migrating to micro frontend architecture is complex and definitely not trivial, so this is the big reason why we are doing it slice-by-slice and improving it along the way.
(20:49) As Micro Frontends are all about working in teams on independent modules, there are always some shared topics which are essential for covering and addressing. This is important to ensure a good end-result and avoid redundant work. Let’s take our already mentioned tems Jaran, Pero and Rendo and assume we have more teams like Team Duje. Each team shouldn’t be the one to reinvent the wheel and it is always a good idea to share knowledge, ideas or implementations with the other teams through either presenting your solution or some Community of Practice. It is also good to have a technical Web team which can then improve some things teams usually don’t have time for. And when we are developing, we need to ensure a consistent look-and-feel for customer and we can do that by having a common Design System. And lastly, Web Performance is very important because you have faster response time and less code shipped to the browser which results in a better load time and it is better to unify improvements across all modules.
(22:08) Let’s summarize micro frontends. They are useful for optimizing feature development because they improve development speed. There are also no monoliths anymore. You are able to keep changing and improving your projects because you can do local decision-making and eliminate legacy code. And there is independence as we have self-contained modules, no shared code, and lighter projects we can run with less computing resources. But, because each micro frontend has own stack, you have to maintain everything separately what can lead to redundancy, but this may be avoidable with Module Federation. And if you need to get some info or data from the other teams, you may have to depend on them and keep everything consistent. And even though micro frontends are technology-agnostic, there is a problem of heterogeneity which is one of the more controversial things. Just because you can doesn’t mean that you should pick a different technology stack. We defined that we will always use technologies like Node.JS or React and avoided this problem. And we already said that setting up local development what can be a challenge. And lastly, while micro frontends are awesome for medium and large projects, they may be an overkill for smaller teams and smaller amount of developers that don’t have an issue with communication.
(24:01) So, your takeaway from today should be that micro frontends are worth all the fuzz! They helped us a lot with optimizing everything in Infobip, especially with N projects and we are planning to continue to use and improve it. For your projects, definitely take time to analyze them and then decide about what you will do. And remember – there is no perfect solution, you can find good and bad sides for absolutely everything. (24:35) Pick up the solution that best fits your needs and first try implementing it on one smaller parts and if all works well, then expand it to more global level.
Before wrapping up, I just want to share link where I have prepared the little reference list where you can check frameworks and some things I have mentioned. I used some of those links to prepare this presentation, so hopefully it will be useful for you.