How to build a micro-frontends architecture, with Angular and Webpack Module Federation
In 2020, “micro-apps” or “micro-frontends” became a big trending topic. Micro-frontends extends the microservice idea to frontend development and help to scale medium to large organizations. Many companies, such as Spotify to Ikea and Microsoft, are actively using it. However, the current state of client-side composition and the best practices to implement « MFEs » (Micro-FrontEnds) with most popular frameworks (React, Angular, Vue.js) is still in its infancy.
Before we dive into the technical details, let’s start by checking the technical architecture evolution of Agorapulse from 2011 to 2020, to get some context about why and how we plan to implement « MFEs » in 2021.
Splitting our initial « majestic monolith »
We initially launched Agorapulse v1.0 in 2011 with a good old « majestic monolith » architecture, running on AWS Cloud since day one.
With the release of AngularJS 1.0 in 2012, we quickly splitted our platform into two monoliths : an AngularJS-based single page application coupled to Grails-based server APIs and workers.
Then around 2014, we started to split our backend monolith into smaller « macro » services, to get more flexibility in deployments and scaling. We also started to experiment with serverless functions with the launch of Java support for AWS Lambda.
I summarized this initial backend journey in 2015 in this article: A startup journey on AWS: from bare metal monolith to serverless microservices.
On the frontend, not much really changed after the initial split. In 2016, we validated a mono-repo approach based on our Angular ecosystem stack for all our apps, web or mobile: sharing (ngrx-based) logic between Angular2 and Ionic2 apps and later on in 2018 with an upgraded POC with Angular8 and Ionic4.
Today, we are still using this mono-repo approach, which allows us to work with a single tech ecosystem and a single fullstack engineering team. At our current stage and team size, the benefits are huge in terms of development velocity, without the needs for native experts on iOS or Android and separate codebases.
But we are slowed down by the main web app which is still a big Angular monolith, that has become very fat over the years… It seriously impacts our velocity for frontend development, with long build/run/test times and the experience of our end users, with long initial load/run times.
End to End micro-architecture and « empowered » autonomous feature teams
Our product and engineering team has grown from 15 to 26 people in 2019 and is around 40 in 2020, still expanding. To support this growth and keep our agility, we started to move from “Tech Feature Teams” to “Empowered Product Teams”.
“ Product Teams are cross-functional (product, design and engineering); they are focused on and measured by outcomes (rather than output); and they are empowered to figure out the best way to solve the problems they’ve been asked to solve. ”
Following Conway’s law, our architecture ideally should mimic our Product Teams organization and get all the benefits from this approach:
- End-to-end full-stack ownership: from back end to front end (code and infrastructure)
- Reduced cognitive load for teams with clear business domain decoupling and boundaries
- Release cycle autonomy: from design, specs, to release and maintenance
To achieve this, we have to split up our big fat frontend monolith.
A micro-frontends architecture is the natural next step in the evolution of our platform architecture.
Adopting a micro-frontend approach will allow us to achieve a pure End-to-End Feature architecture. Each single feature can be run and developed locally within its own micro-app, which dramatically increases developer productivity by avoiding the build/rebuild times required by the whole fat monolith. It can then be tested and deployed independently.
Micro-frontends removes the last blocker allowing us to move from “single threaded” to “multi-threaded” development and delivery processes.
Moreover, micro-frontends offer an overall better end user experience with smaller optimized bundle sizes because of shared modules and dependencies, lazy-loaded on demand.
A micro-frontends approach is a win/win situation for the developer and the end-user.
Note: An additional benefit with micro-frontends is that they can be created using different technologies. That is not a use case for us because we aim to keep a unified tech stack around the Angular ecosystem.
Proof of Concept with Angular 11 and Webpack 5 Federated Modules
With the release of Angular 11 and the experimental support of Module Federation in Webpack 5, it was a good time to build a POC and validate the MFE concept on our stack:
The requirement was also to be able to mix lazy-loading of local and remote modules through routes.
If you’re not familiar with Module Federation, please check the official site. Then for Angular 11, follow Manfred Steyer’s fantastic articles series on this topic, which details the use of @angular-architects/module-federation package.
This advanced demo POC is based on two apps that can run separately:
- Counter-mfe is a standalone micro-app that encapsulates a single feature / domain, with its own ngrx store and actions (and supposed corresponding microservice backend). It exposes a remote CounterRouteModule to be lazy-loaded as a remote route.
- Shell is the main simple host app in charge of bootstrapping the main app and navigation, which will lazy load local modules (AuthRouteModule) or remote modules (CounterRouteModule).
Module Federation Under the Hood
All the magic is happening in the route loadChildren() syntax declaration and some Webpack 5 custom configuration.
export const APP_ROUTES: Routes = [
// Lazy loaded local module
loadChildren: () => import('./login/login-route.module').then(m => m.LoginRouteModule)
// Lazy loaded remote module
loadChildren: () => import('counter-remote/counter-route.module').then((m) => m.CounterRouteModule),
To benefit from the Webpack 5 Module Federation experimental support in Angular, Manfred Steyer provides a custom builder ngx-build-plus to extend the default Webpack behavior by providing a partial custom config (so that you can use ModuleFederationPlugin).
On the host side, here is the Webpack ModuleFederationPlugin configuration of the shell main app. It consumes counter-remote exposed modules.
On the remote side, here is the Webpack ModuleFederationPlugin configuration of the counter-mfe micro-app. It exposes a single counter-route.module.
To deep further, you can find the POC source code and run the apps by yourself: https://github.com/benorama/mfe-advanced-demo
What’s Next ?
Completing the migration of our main web app to micro-frontends is our objective for 2021.
This “End-to-End Feature” architecture will help us to achieve our product and engineering multi-threaded organization goal (fully autonomous and “Empowered Product Teams”). More importantly, it will enable the continuous delivery of business value to our customers, helping them to manage their social media profiles with even more delight and efficiency!
Note: we’re hiring! Are you kick-ass fullstack or front-end dev that want to work on Angular? You also master Java or Groovy? You must contact me to join our dream team in Paris!
If you liked this article, please hit the ❤ button to recommend it. This will make it easier for other Medium users to discover this.