Last year my team undertook a project to build a one-stop portal site to integrate downstream systems to our portal page. We did this by making a monolithic system with React & Redux for frontend and Kotlin for backend, but being forced to share the same frontend, backend, practices, etc. resulted in cross-team collaboration problems and a loss of team autonomy.
It was through the introduction of micro frontends architecture that we were able to take things to the next level.
Micro Frontends
In terms of business model, micro frontends architecture brings domain-driven design to the frontend. The front end can be split into independent microapps, each with its own downstream services which communicate with the corresponding backend-for-frontend. This enables autonomous teams to focus on their own business changes with lower impact on other teams
Moreover, a delivery team can focus on creating and maintaining frontend modules with clear boundaries that minimize coupling between domains. This clear business boundary and extremely low coupling relationship cater to the requirements of both product managers and business analysts. The business modules are sufficiently independent, which decreases their instability and unpredictability.
In terms of the development model, the team can make decisions independently since the repos, pipelines, artifacts and environments are completely split up, as the diagram below shows. By doing this, the team can move closer to the ideal delivery conditions faster: an independent codebase, familiar tech stacks, lightweight regulations and efficient delivery processes.
When it comes to service asset management, independent environments have their own artifacts, thus every microapp’s environment is isolated from others. By doing so, the regression test for the whole monolithic environment is broken down to microapps. Therefore, every team only needs to maintain their own environments and take charge of their own incidents. They also have autonomy for their own on-call rotations.
Here is an example of a micro frontends system and its key components. Note that the recommendation feature is isolated in the system so that the custodian team can maintain it with full autonomy.
Another concept worth mentioning is the import map component at the left-bottom corner of the diagram. The import map is essentially a mechanism for dynamically loading JavaScript files and it can be seen as an index of the latest address when the system receives a request from a user. With the help of an import map, our portal site is able to involve more teams and thus enrich its functionalities.
Benefits
Micro frontends have been a game-changer for cross-team collaborative projects for the following reasons:
Improves the stability and predictability of the project delivery
Allows teams autonomy throughout the project lifecycle
Minimizes unnecessary custodianship overlaps across teams
Additionally, the Import map is designed as a mechanism for dynamically loading JS. It is better at attracting individuals, teams and even third parties to become contributors. This can speed up product delivery and take our products to the next level.
Lessons learned
Micro frontends can’t solve every problem. They can even increase performance problems, have higher DevOps requirements and make management more complex.
Performance
Introducing micro frontends requires you to load the framework before loading the page, then load the target component and microapp. This will undoubtedly increase the loading burden. Taking largest-contentful-paint as an example, it is increased by about 0.2~1.2s without any performance optimization.
In order to reduce the loading time, we needed to increase the loading speed of each JS file through artifact minimization, tree shaking and caching at Cloudfront and browser level. We also needed to improve the parallel loading ability of JS as much as possible to ensure functionality.
DevOps
Micro frontends architecture sets a higher requirement on the team's CD (continuous deployment) capabilities. It needs better automation and “Infrastructure as Code” practice to make sure the microapp refers to the latest version in the pipeline. On top of that, highly automated practices require higher test coverage so that there is enough confidence during the continuous integration and continuous delivery.
Management complexity
Although microapp architecture solves unnecessary coupling issues, it also increases management complexity for the system, for example by causing CSS/JS conflicts between apps, or in terms of consistency across apps. However, we’ve always believed that a smooth user experience shouldn’t be sacrificed in the pursuit of a high degree of autonomy. Therefore, we need to set up conventional design guidelines, such as a unified naming rule, a matching color palette and a similar pattern of error handling or user experience.
Ultimately, despite some added complexity, we found that micro frontends architecture remains the best way to promote cross-team collaboration without loss of team autonomy.
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.