Can React and TypeScript applications run on different platforms (app shells) with minimal source code changes ? Yes !!! with the Micro-frontend strategy in Multiplying architecture. I’ll be presenting a new dimension of Micro-frontend, that paved a way to decouple the components of a larger monolithic React application using a new framework called Multiplying architecture. The framework is highly flexible and scalable for code development, also aiding the business and community.
Platform Freedom with Micro-frontends
AI Generated Video Summary
This talk explores Microfrontend strategies and the benefits of using the multiplying architecture to deploy applications on multiple platforms. It discusses the concepts of platform freedom, microfrontend implementation, and Backend for Frontend (BFF). The talk also highlights the challenges of debugging and styling in larger microfrontend applications and introduces the multiplying architecture as a solution. It explains the core elements of the multiplying architecture and how it enables communication between different tech stacks. The talk concludes by showcasing the use of embedded lists, federated modules, and Webpack configuration to achieve efficient code sharing and deployment across multiple distributions.
1. Platform Freedom with Microfrontends
This talk will take you through the Microfrontend strategies, the problems that are solved by the Microfrontend architecture, a new framework that was built on top of the Microfrontend strategies and how this multiplying of the new framework helped us to deploy our applications on multiple distributed systems. I wanted to share the experience what we had and the experience and the learnings that we had while implementing certain use cases. We started with micro-friendlings, and we then introduced a framework called the multiplying architecture. And we had certain learnings out of it. And finally, with the collaboration of micro-friendly strategies, along with the micro-multiplying architecture. We were able to achieve our results. Firstly, I would like to break my topic platform freedom with micro-friendliness. And I want to emphasize what I mean by the term platform freedom. We all know that web technology is completely dominating the software development industry at this point. And web technology has become a default choice for most of the developers and the companies who wish to develop an application or a product for their use case.
Hello, and welcome everyone to the talk, Platform Freedom with Microfrontends. This talk will take you through the Microfrontend strategies, the problems that are solved by the Microfrontend architecture, a new framework that was built on top of the Microfrontend strategies and how this multiplying of the new framework helped us to deploy our applications on multiple distributed systems.
I would like to start with the Red Hat story. I wanted to take this talk in such a way that I wanted to share the experience what we had and the experience and the learnings that we had while implementing certain use cases. Sometime ago, we had a use case where we wanted to deploy certain tools that we built to run on various platforms, and we wanted to just reuse the components that we recreated on the React JS. So that was the story idea behind it. We started with micro-friendlings, and we then introduced a framework called the multiplying architecture. And we had certain learnings out of it. And finally, with the collaboration of micro-friendly strategies, along with the micro-multiplying architecture. We were able to achieve our results. So this upcoming slide will show you how we were able to do it.
2. Platform and Microfrontend Strategies
The goal is to run applications on multiple platforms such as VS Code, browsers, and GitHub. The web technology has evolved rapidly, starting with evolutionary architecture, followed by microservices and serverless. Micro frontend strategies are used to break down monolithic applications into smaller, independently deliverable frontend applications. Each micro frontend can be owned and maintained by an individual and autonomous team.
Now, the browser is not just a window to access your internet. So, you can show your graphical user interface anywhere on your browser and that can run on any platform. So, the term what I mean by the platform in my topic is, the application must run on platforms. So, in our use case, the platforms that we wanted to achieve is that, to run our applications on the VS code IDE as an extension. And it should run on the browser as an extension. Maybe in case of Chrome, it should run as a Chrome extension. And in case of Firefox, it has to run as a Firefox extension. And the same set of code, same set of components has to run on the web app and also on the GitHub as a GitHub extension. So, this was the end goal that we wanted to achieve. For which, I mean, this is what I mean, I want my two links to run on VS Code, GitHub browser as a web app and also on the...
So, while implementing, when we thought about this implementation, we were analyzing the architecture that we have on the web technology. The web technology has, I mean, as I mentioned in the previous slide, it is growing very fast. I mean, starting with the evolutionary architecture, which supports incremental guided change as a first principle across multiple dimensions. Then comes path breaker, which we call it as microservices, which allowed the backend to be broken into a smaller decoupled microservices, which can run independently. And then comes the buzzing word at this point, which is serverless, where backend is a service. You can deploy your function on a server, and you can just pay for the number of hits that a function gets and how much amount of time that the function is used. You can just pay for that instead of owning the entire server. And finally, we have the term micro frontend, which will, again, I mean, from where we thought about implementing micro frontend strategies for a use case. That takes me to the next slide again.
I assumed that most of you would have already tried something out on the micro frontend strategies or maybe you would have heard the term, but still, I would like to, I mean, touch base with the strategies of micro frontend to the people who are yet to learn it. So consider if you have a bigger monolith frontend application, when I say monolith, it's a huge chunk of application which is maintained by a vast team and which has a huge chunk of code. When you have this kind of a scenario, you're obviously bound to a lot of errors, production issues, bugs, which can't be addressed that easily. So to address this problem, developers across the world, they started thinking of a solution to break this monolith application into smaller pieces. This happened some five, six years back. At that point of time, microservice was evolving a lot at the backend technologies, so the frontend developers wanted to have this similar set of strategies to be on the frontend as well. So that's when they introduced the term called microfrontend. That's how the microfrontend evolved. As for Kam Jackson, who's a renowned architect, and microfrontend architecture is an architectural style where independently deliverable frontend applications are composed into a greater whole. Simply, I can say that breaking off a monolith application into smaller microfrontends, and each microfrontend can be owned by an individual and autonomous team. They can have their own deployments, they can have their own release cycles, they can maintain their own microfrontends without depending on the other other microfrontends or other teams.
3. Microfrontend Implementation and BFF
This example demonstrates how a microfrontend can be implemented by breaking down a UI into smaller components, each maintained by a separate team. The microfrontends are contained within a single container application, which makes the rendering decisions. We also discussed the concepts of monolith, microservices, and micro frontend, and how the frontend can be decoupled into smaller microfrontends. Another concept that evolved with microfrontend is BFF (Backend for Frontend).
So this example will give you an idea about what a microfrontend will look like. So as I mentioned in my previous slides, I work in a team where we build tooling for business automation products. So this DMN is one such tool which is a graphical representation of the business decisions and business rules. So here you can see some sort of a graph which displays the set of rules and decisions for business.
So we broke this UI into multiple microfrontends. Here, we can break this UI into multiple microfrontends. Considering header, you can break as a small microfrontend, which can be maintained by a separate team. Just in this case, the header is a smaller one. Just imagine if you have an e-commerce site or a bigger application where your header itself is a huge chunk of React components. So just imagine you can have your search bar, your logo, your profile login and logout, and multiple drop-downs on the header. So this all consists of a huge chunk of components resides on a header. So this definitely qualifies as a separate micro frontend. And this could be maintained by a separate team. And also here, coming back to this editor, at the center, we have the editor, which is mentioned as a component, my micro frontend C, which can be maintained by a separate team. And again, on the left, we have a navigator kind of a thing, which allows you to navigate through the nodes of the diagram that we have here, again, which could be maintained by a separate micro frontend. So all these micro frontends are contained inside one single container application. And that container application takes the major decisions in order to render the micro frontends.
So this slide will give you an idea about the three terminologies that we discussed so far, monolith, microservices, and micro frontend. So in case of monolith web application, you have your frontend, you have your backend, and the backend is connected to the data source. So whenever there is a request from the frontend, it reaches the backend, and the backend connects with the data source and gets the data from the data source and just passed on to the frontend. So it's just a straightforward approach which we have been following over the years. After the introduction of microservices, we have decoupled the backend totally into smaller microservices. And between the frontend and the microservices, there is a layer called API Gateway Layer, which will take the decisions based on the request coming from the frontend. And it passes the request to the specific microservice which can handle that particular request. And also each microservices will have its own data source. And combining this microservice along with the microfrontend, even at this point, the frontend is decoupled into smaller microfrontends, and we have the API Layer in between. And then we have the microservices again. And then each microservices has its own data source. So this slide will take you through another exciting architecture or a concept which evolved along with the microfrontend is BFF. Yes, you heard me right.
4. Understanding Microfrontend Architecture
It is not actually abbreviated as best friend forever, but it is. Actually, the abbreviation of BFF is back-end for front-end, which acts as a best friend for microfrontend, as well as microservice. So if you closely look at this diagram, the communication between the microfrontend and the microservice is not directly connected. So it is happening via BFF. So here this BFF acts as an intermediate between and also it uses the loads or efforts that a microfrontend should do. So whatever request that comes from the microfrontend is just passed on to the service. Whatever response the service is giving, it transpiles the data in such a manner that a microfrontend can understand it and display it. So the maximum effort of the microfrontend is reduced here.
Also, if we closely look at this diagram here, there won't be any communication between the microfrontends here. Every microfrontend is directly connected to the container, which is called an app shell, and the major decisions are taken only on the container itself. So there is no communication between the microfrontend here. So that's how a microfrontend works.
And coming to the other strategy that we have, which is build time integration. So here with this strategy, the NPM registry came as our boon where we can convert our, I mean, the component that we are developing, we can convert that as a package and we can just deploy to our NPM registry, the teams who want to use that particular component, the other micro-frontend team or other autonomous team who wants to use that particular component, they just can install that particular component as a dependency into their application, and they can just use it. The downside of this build time is, again, once you're using any package component as a dependency, the entire bundle has to be rebuilt, their entire application has to be rebuilt. So this bundling time and the rebuild time will be too high.
So there were other concerns over our micro-frontend strategy is that one such concern is working on an autonomous team. So while we were working on an autonomous team, each team just takes care about their own micro-frontend, they don't even care about other teams. But still, there could be a possibility that error could present on a container application, which I define that container, I mean, a bug on the container application is obviously going to be a tedious task, and hard to run a complete experience. If someone wants to specific, I mean, test some specific part of a feature, they have to rebuild the entire application once and then they have to test the particular smaller feature. Again, that's very hard.
5. Multiplying Architecture and Channel Abstraction
Debugging problems in larger micro-frontend applications can be challenging. CSS styling concerns can be addressed using CSS-in-JS strategies or iframes, although iframes have drawbacks and are considered outdated technology. To overcome the limitations of micro-frontends, we developed the multiplying architecture. This architecture allows applications to run on multiple distributions with minimal code changes and provides a bridge between different tech stacks. The core elements of the multiplying architecture are channels, envelopes, views, and editors.
Again, debugging a problem in a small, a multiple- I mean, in a bigger application, again, it is going to be, even though it is a micro-frontend, it is going to be a difficult one. Apart from these autonomous teams, there are other concerns related to CSS styling. If you are writing some CSS on a particular frame, particular micro-frontend, that can be overridden on other micro-frontend as well. That may affect your overall view of your application. And, also, maybe to overcome this, actually, you can use either a CSS in JS strategy which you have the style components or something like that, some libraries like that. And, you can use iFrames. Though, I mean, this iFrame will completely isolate your micro-frontend to the outside world so that you can just, even if there is a CSS styling getting overridden, it will not affect your component. So, this is how iFrame can be created here. So, you can embed your iFrame in your script tag and the source for your iFrame can be passed on like this. So, there are some certain drawbacks with the iFrames as well. So, these iFrames are nothing new, actually. So, these are age-old technology. I strongly believe that at this point, at this evolution of technologies, nobody will still prefer to use an iFrame for their application. So, even though micro-frontends are isolated, there should be at least some sort of data passing between the micro-frontend should be enabled. Using iFrames, this data passing is kind of again a problem. To overcome this, so far we have seen all the concerns that are associated with micro-frontend.
So, to overcome all these things, we decided to develop a framework to address all these problems and to address the requirements as well. So, we started to think about an architecture. So, the requirements where, we wanted our application or the tooling to run on multiple distributions. As I mentioned, the distributions were the VS Code, GitHub, browser, and the web app. And with minimal code changes, my components has to run on all other distributions. And also, there should be a bridge between the technology or tech stacks, whatever I choose. In case if I want to change my tech stack in near future, that still should be possible with minimal effort. So, to address all these things, we introduced the multiplying architecture. So, before getting into the multiplying architecture, what do we mean by the software architecture? Again, there's a quote from Ralph Johnson, architecture is about the important stuff, whatever it is, right? I'll explain to you what it exactly means. So, the main idea behind this multiplying architecture is the abstraction. We wanted to have our React components to be abstracted and wrapped and that wrapper should be included on the platform where we want to run the particular components. So, the core concepts or the core elements of the multiplying architecture are these. The first one is channel, envelope, view, and editor. The channel, what I mean here is the platforms that we were discussing so far.
6. Multiplying Architecture and Channel Communication
The view is the React components, and the envelope acts as a mediator between the channel and the view. The editor is wrapped inside the envelope, which is then wrapped inside the channel. The advantages of the envelope include context isolation, autonomous team implementation, independent release cycles, and complete type safety. The multiplying architecture can be seen in practice with the provided code examples, such as the TodoList package, which includes API, embedded, and envelope folders. The API, channel, and envelope files are responsible for communication between the channel and the API.
It could be either VS Code, GitHub, or browser extension, or the web app. The view is obviously the React components that we have, and the envelope, which acts between the channel and the view or acts as a mediator between the channel and the view and passes messages between them. And the editor is again the dm and editor that we have in our previous example as we have.
So, we wanted to deploy our tool on the online channel like this. You see here the online channel. So, the editor is wrapped inside the envelope, and the envelope is wrapped inside the channel. We wanted our tool to run on a browser extension like this, on GitHub extension like this, and VSCode extension like this. So, here comes again. So, this channel, I mean, again, if you see here, a simple implementation of what we were doing with multiplying architecture is, again, the channel is wrapped inside, I mean iframe is wrapped inside a channel, and this iframe contains editor, I mean, the editor, the components, whatever, I mean, my editor contains the React components, and the envelope acts as a mediator between the channel and the React components.
The advantages of envelope are the context isolation. So, it is totally isolated, each micro-frontend is isolated from the other one, but still the data passing between them is possible. Implementation of autonomous team. Each team could work individually on their specific micro-frontend. Independent release cycle, they can have independent release cycles and they can have independent CI-CD pipelines as well. The main advantage of this multiplying architecture is it is completely type safe. We are using TypeScript here, so it is totally type safe. Let's see multiplying architecture in practice.
For which I'll take you through the code base here. I'll be sharing this code base URL where we have this set of examples that we implemented for the multiplying architecture. Whatever packages that you see in this entire library are based out of multiplying architecture. You can take any example out of it, but for a simpler example maybe we just take the regular use case or regular example that we build for any framework that is TodoList. Here you can see a TodoList package which contains three folder structures. One is for API, embedded and envelope. API just contains a methods that are required for the external world. External world here what I mean is that channels. There is a file called channel API. This contains a set of methods that channel exposes and consumed by the envelope. Again another file called envelope API which contains a method which are exposed by the envelope and consumed by the channel. So these 3 files are responsible for the communication between the channel and the API. Whatever method that you want to implement you can just have this abstracted here and you can define this method in your channel.
7. Embedded List and Federated Modules
I will be showing how the embedded list serves as the entry point for React components. The TS to-do list envelope view contains the react code, html, and css. This embedded acts as an entry point for your react components. We managed to achieve the same set of code working on two different platforms. The duplication of library loading is a common issue in build time and run time integration. Federated modules, introduced by Webpack, allow for sharing libraries and dependencies between micro frontends and loading them when needed.
I will be showing how it is defined. There is another folder called embedded. This embedded list is considered as the entry point for the React components. You see here down the line which calls the embedded to-do list envelope. So this is coming from this envelope folder here so which is a TSX component which exposes the view. So the view is here. The TS to-do list envelope view contains the react code, the html and the css, all those things here. This is called inside the envelope and the envelope is called inside the embedded. This embedded acts as an entry point for your react components. So this is the flow for this to-do list view and it is a micro front end so you can use this micro front end on different platforms.
So as I mean I wanted to I wanted to run this application running on a VS code and also the web app right so here we created a separate VS code extension with all the requirements for an extension. Because VS code has its own extension and set of rules that needs to be followed that has to be active and first of all the extension has to be activated so before implementing the VS code extension you must be aware of all these things. So here the subscriptions are made and then the component is rendered wild. So if you see here there is an index.ts which will call initiate the component I mean the envelope which we have it and I mean exported from this channel embedded. So here this is a channel I mean VS code extension is a channel again in case of an example I mean a web app we have again a source folder which have a page to-do list. To-do list view page now if you come down you will see the embedded to-do list is called here. This embedded to-do list is imported from the to-do list view which is another microfinder. So here we try we managed to achieve a same set of code working on two different platforms.
Coming back to my slides. So even after implementing the envelope channel view and multiplying architecture blah blah all those stuff we were still having some sort of something missing on our architecture design. We had a problem with this build time and run time integration issues. The common issues that we face across these both the strategies are the duplication of library loading. Consider a scenario if we have an application which has multiple three micro frontends and each micro frontend has a React as a dependency. So with this actually the React were created. Three instances of React were created which is definitely not a good thing. Consider if you have too many dependencies and each of the dependencies were individually creating its own instance for each of the micro frontend and the size of your application will be too high. To reduce that to overcome this issue, federated modules came to a rescue for us. So you might have heard this term, federated modules. It is introduced by Webpack in its fifth version. It has to share the libraries and dependencies between the micro frontends and also it allows to load the micro frontends whenever it is needed.
8. Webpack Configuration and Multiplying Architecture
Instead of dumping the micro frontend at once, we made the Webpack configuration to load specific micro frontends based on the route. The shared libraries and dependencies between micro frontends reduce bundle size. React features like lazy imports, lazy loading, and suspense were used to load micro frontends when needed. Using micro frontend strategies, BFF, federated modules, and multiplying architecture, we deployed our application across multiple distributions with minimal code. The multiplying architecture eliminated iframes and allowed easy breaking or coupling of the monolith application.
Instead of just dumping the components, just dumping the micro frontend at once on your browser. Let's see how it does. This is how we made the Webpack configuration on the root for config file. We imported the module federation, which has remotes and each remote route can be linked to its own micro frontend. Whenever there is a marketing route hits, only then its corresponding micro frontend will be loaded. Until then it will not, until that point it will not load the marketing micro frontend. It will just keep on there. Similarly, it happens to the auth and dashboard. If you see here, the libraries, the dependencies are shared between these three micro frontends, so it is not created, the instance are not created individually, so the bundle size will be reduced.
While using this micro frontend in our React components, we used other React features that is lazy imports, lazy loading and suspense to load the micro frontend whenever it is needed. Here if you see the imports are lazy loaded, and here if you see the components are rendered only if the particular route gets hit. So, after doing all these things, I mean, using some parts of micro frontend strategies, like BFF, federated modules, and on top of it using our multiplying architecture, we were able to achieve our goal of deploying our application over multiple distributions with the minimal set of code. And we also developed a bridge where the text tag could be replaced in the near future with a minimal set of code change. So, these were the achievements that we were able to gain out of the multiplying architecture.
The first thing is microservice architecture. I mean, we were able to implement the microservice in the front end world. Able to finally take advantage of runtime integration. Each team could build or deploy its own modules. There's no duplication of the library on loading. And able to deploy multiple pieces of your application to different servers without iframes. So, that's the main advantage that we have. So, with this multiplying architecture, we completely eliminated the iframes. We replaced it with the div tag, so that it is not totally isolated. The micro-frontend just ended as a React component. In case if you want to break your monolith application, you can easily break it. In case if you don't want to break it and you want to couple it again, you can easily do it with minimal effort. With that, we've just come to a conclusion.