Applications created using Micro-Frontends look like regular applications to users, but each Micro-Frontends has its own codebase, repository, and pipeline.
Join the DZone community and get the full member experience.Join For Free
Microservices have been around for a long time. Despite being a more complex solution than the more traditional Monolith, in Apiumhub, we believe that microservices are a good solution to many extremely common problems in software development, such as difficult and slow deploy process, large codebase that often lacks cohesiveness, difficulty for different teams to work simultaneously in somewhat related features due to conflicts, etc.
Nevertheless, microservices are used almost exclusively on the backend. And this makes a lot of sense, since it is easier to provide a seamless experience, with a consolidation layer, such as the API the server-side provides. It kind of hides everything that happens underneath. On the frontend, since it is always in front of the end user’s eyes, this is not so easy to achieve. Especially given how SPAs work and what is expected from them, and also given the popularity of global shared state patterns and libraries, such as Redux.
Now, this is exactly what Micro-Frontends are needed for. To replicate the benefits that microservices bring to the server-side, but this time directly in front of the final user in his browser. The main benefits we want to archive using Micro-Frontends, are:
- More cohesive codebase
Since we break the frontend into smaller pieces, we separate code into smaller pieces of functionality (in many cases by page or part of the routing), resulting in a smaller, cohesive codebase, which is easier to understand and develop.
- Simplify maintenance
Since each micro-frontend takes full responsibility of a particular part of the application, with little to no dependencies with the rest, it shortens both development and testing time.
- Allows to scale development teams
More teams can work simultaneously on the project, as the collisions between them drop significantly, and teams can be more autonomous and decoupled from the rest.
- Simplify updates
In a monolith, when in need to upgrade a certain dependency, or change a common component, it cannot be done unless the rest of the app will also support the change. Because of that, to introduce a breaking change is a costly task, that leads to a lot of development, and even more testing time to ensure everything works as expected. But if using Micro Frontends, we can introduce any changes we need in our module, without colliding with the rest of the application.
- Independent deploy
Each micro-frontend should have its own pipeline that would build, test, and deploy the module all the way to production. Hence we can achieve continuous delivery where our updates arrive to production in question of minutes.
Applications created using Micro Frontends look like regular applications to the end user, while each of the Micro Frontends have their own codebase, repository and pipeline.
To make it all work, we rely on the Container application, that will detect and understand which view is expected to be shown to the user, how it must be composed and built. And this can be achieve using one of the following approaches:
Integrate Micro-Frontends at Build-Time
In this approach, all micro applications are put together in the Container application during the build process, and the result of this operation is then deployed to production. In this case, the Container application acts as a Monolith, and the micro applications are just dependencies that are used within.
The main problem that arises with this method, is that whenever a new version of a particular micro frontend is pushed, it is necessary to go to the main project (Container), bump the version of the micro service, and push that change. That meaning, the possibility of independent deployment of each micro frontend is lost.
Integrate Micro-Frontends at Runtime
In this case, the Container Application will not auto-contain all the micro-frontends, and they won’t be downloaded by the user’s browser whenever the application’s entry point is requested, but only when the specific route or view is reached in the main application.
This can be achieved in two different ways:
We can use Nginx as a web server to differentiate what path (location) A user is accessing, and return one HTML or another, as a response. For reference:
HTML template that can be used to achieve to ‘include’ the right micro frontend:
Welcome to the Container Application
Nginx Sample Configuration
Nevertheless, this solution has its limitations, especially in terms of actual use of a simple page application, where we want to rely on shared state, or when the micro-frontend to be presented is not defined by the route but by more complex conditions.
Dynamically Load the Micro-Frontends in the Browser
In this situation, the Container application will load the needed micro frontend whenever is needed, by directly appending a <script> tag to the DOM with the src pointing to the requested application.
Here is a great example of how to achieve such behavior using a custom hook in a regular React application that allows you to load the right bundle, and uses a custom event for communication across the services. But any other communication via Publisher/Subscriber suits perfectly, like pubsub-js library.
Published at DZone with permission of Roman Predein . See the original article here.
Opinions expressed by DZone contributors are their own.