Platform Freedom with Micro-frontends


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.



Hello and welcome everyone to the talk, Platform Freedom with Microfrontend. 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. So before getting into the topic, let me introduce myself. I'm Saravanan Balaji Srinivasan, working as a senior software engineer at Red Hat, as a full stack developer, mostly on the JavaScript technologies. I work in the areas where we build toolings for business automation products and serverless workflow specifications, etc. So yeah, that's about me. And let's get back to the topic. 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 of what we had and the experience and the learnings that we had while implementing certain use cases. So 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. I started with micro-frontends, and we already introduced a framework called the multiplying architecture. And we had certain learnings out of it. And finally, with the collaboration of micro-frontend strategies along with the micro-multiplying architecture, we were able to achieve our results. So this upcoming slides will show you how we were able to do it. So firstly, I would like to break my topic, platform freedom with micro-frontends. And I want to emphasize what I mean by the term platform freedom. So 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 the use case. This is because web technology has a stronger foundation. It has stronger standards, patterns, techniques, and the architectures of web technology are evolving very fast over the period of time. Also, it has a rich ecosystem. When we speak about the web technologies, we cannot ignore JavaScript. So now at this point, JavaScript is everywhere. Though it initially started with a scripting language for a small purpose, and now it has evolved over a period of time, and you can find JavaScript everywhere on your browser, on your laptop, on your mobile phones, on your PWA, or on your servers. Everywhere you can see JavaScript now. And also, after the introduction of TypeScript, I personally feel that just because of its behavior of static type checking, the perception of the developers on the web technology has totally changed. So people like me who started my career in the Java technology or as a Java developer, after turning to a JavaScript engineer and then for web technology and all that as a full stack engineer now, I started liking web technology after learning TypeScript because my code is totally type safe. And I will also emphasize that browser is everywhere now at this point. So you can have your browser on your laptop, you can have it on your mobile, and your servers, and PWA, et cetera. 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 the Chrome, it should run as a Chrome extension. 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 as a 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 tool links to run on VS Code GitHub browser and 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 a 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 as a service. You can deploy your function on a server and you can just pay for whatever, the number of hits that the function gets, right? And how much amount of time that the function is used. You can just pay for that instead of using the, I mean, 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, I mean, strategies for a use case, that takes me to the next slide again. I assume that most of you would have already tried something 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 were yet to learn it. So consider if you have a multiple, I mean, a bigger monolith frontend application. When I say monolith, it's a huge chunk of application, which is maintained by your 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 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. So 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 micro frontend. So that's how the micro frontend evolved. That's what Cam Jackson, who's a renowned architect. So a micro frontend architecture is an architectural style where independently deliverable frontend applications are composed into greater whole. Simply I can say that breaking up a monolith application into a smaller micro frontends and each micro frontend 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 micro frontends without depending on other micro frontends or other teams. So this example will give you an idea about how a micro frontend 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 we've built, 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 micro frontends. Here we can break this multiple UI into multiple micro frontends. Considering header, you can break as a smaller micro frontend, which can be maintained by a separate team. 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 the 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 of 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 are following for over the years. After the introduction of microservices, we have broken down or 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 micro frontend, even at this point, we have even the micro frontend is decoupled into smaller micro frontends. 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 micro frontend is BFF. Yes, you heard me right. It is not actually abbreviated as best friend forever, but it is. Actually, the abbreviation of BFF is backend for frontend, which acts as a best friend for micro frontend and as well as a microservice. So if you closely look at this diagram, the communication between the micro frontend and the microservice is not directly connected. So it is happening via BFF. So here this BFF acts as an intermediate between them. And also, it uses the loads or efforts that a micro frontend should do. So whatever request that comes from the micro frontend is just passed on to the service, whatever response that the service is giving, it transpires that service, I mean, the data and in such a manner that a micro frontend can understand and display it. So the maximum effort of the micro frontend is reduced here. And also, if we closely look at this diagram here, there won't be any communication between the micro frontends here. Every micro frontend is directly connected to the container, which is either we call this app shell. And the major decisions are taken only on the container itself. So there is no communication between the micro frontend here. So that's how our micro frontend works. So when we speak about micro frontend, there are two important strategies, which is integration strategies, how we integrate the micro frontends together on a single container application. So I can elaborate. I mean, we can just categorize on two different types. One is runtime integration, and the other one is build time integration. So when you say about runtime integration, each micro frontend is bundled and converted to a JavaScript file, and that is binded to the container application on a script tag. And the container will take the decision on what circumstance this particular bundle has to be rendered on the browser. So consider the scenario, a team A decides to develop a component C, and they develop the component and they deployed it in the path, the domain name slash the component in.js, which is a bundle again. So the user who navigates to the domain, I mean, the container domain, the container app loads the component C, and then it displays in the specific path. So that's the way here again, the container takes the control, I mean, entire control. This setup has its own downside as well, because the setting time is too high, and independent deployment is too challenging. 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, or the 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 dependency, I mean, any package component as a dependency, the entire bundle has to be rebuilt, the 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 concerns are autonomous, 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 be there present on a container application, which has been identifying that container, I mean, bug on the container application is obviously going to be a tedious task, and hard to run a complete experience. If someone wants to test some specific part of a feature, they have to rebuild the entire application once, and then they have to test a particular smaller feature. Again, that's very hard again. Debugging a problem in a smaller, multiple, and vast, I mean, 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're writing some CSS on a particular frame, I mean, 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 like 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 gets 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 the micro frontends are isolated, there should be at least some sort of data passing between the micro frontends 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 the micro frontend. So to overcome all these things, we decided to develop a framework solving all these problems, to address all these problems, and to address our requirements as well. So we started to think about an architecture. So the requirements were we wanted our tool application or the tooling to run on multiple distributions. As I mentioned, the distributions were the VS Code, GitHub, browser, and finally the web app. And with minimal code changes, my components has to run on all of the distributions. And also, there should be a bridge between the technology or text tags, whatever I choose. In case if I want to change my text tag in the 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 we mean by the software architecture? Again, there's a quote again from Ralph Johnson. Software is about the important stuff, whatever it is. 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 platforms 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. 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, acts as a mediator between the channel and the view, and passes messages between them. And the editor is, again, the DMN editor that we have in our previous example. So we wanted to deploy our tool on the online channel like this. You see here, 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 a GitHub extension like this, and VS Code extension like this. So here it 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. So the advantages of envelope are the context isolation. So it is totally isolated. Each micro frontend is kind of isolated from the other one, but still the data passing between them were possible. I mean, 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, so 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 built for any framework that is to do list. So here you can see a to do list package, which contains three folder structures. One is for API, embedded, and envelope. So this API just contains a methods that are required for the external world. So external world here, what I mean is that channels. So again, there is a file called channel API. So this contains a set of methods that channel exposes and consumed by the envelope. Again another file called envelope API, which again contains a method which are exposed by the envelope and are consumed by the channel. So these three 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. So I'll be showing that how it is defined. And there's another folder called embedded. So this embedded list is considered as the entry point for the React components. You see here the down the line, which calls the embedded to do list envelope. So this is coming from this envelope folder here, which is a TSX component, which exposes the view. So the view is here. So this view, I mean, the TS to do list envelope view contains the React code, I mean, the HTML and the CSS, all those things are here. So this is called inside the envelope, and then envelope is called inside the embedded, and 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 frontend. So you can use this micro frontend on different platforms. So as I mean, I wanted to run this application running on a VS code and also a web app. 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 activated. 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 by our root. So if you see here, there is an index.ts, which will initiate the component, I mean, the envelope, which we have it in the, I mean, exported from this channel embedded. So here this is a channel, I mean, VS code extension is a channel. 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. 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 micro frontend. So here we manage 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, 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 both the strategies are the duplication of library loading. Consider a scenario. If we have an application which has three micro frontends, and each micro frontend has its React as a dependency. So with this, actually, the React, I mean, three instances of React are created, which is definitely not a good thing. Consider if you have too many dependencies, and each of the dependencies are 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 it allows to load the micro frontends whenever it is needed, instead of just dumping the components or just dumping the micro frontend at once on your browser. Let's see how it does. So this is how we made the Webpack configuration on the root for a config file, where 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 does not create the instance, or not created individually, so the bundle size will be reduced. And while using this micro frontend in our React components, we used the React features that is lazy imports, lazy loading, and suspense to load the micro frontends then and there and whenever it is needed. So here if you see the inputs are lazy loaded, and here if you see the components are rendered, only if it is 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 or multiple distributions with a minimal set of code. And we also developed a bridge where the text tag could be replaced 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. We were able to implement the microservice architecture in the frontend world, able to finally take advantage of runtime integration. Each team could build or deploy its own modules. There is no duplication of the library on loading. And we could 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 rendered as a component, as a React component, simply, as simple as that. 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. So that's it. With that, we've just come to a conclusion. If you have any questions, please shoot it out.
31 min
24 Oct, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic