Microservices frontend: module federation, an handsome promise
Over the last couple of years, the benefits of micro-service architectures have been well publicized. Starting from monolith architecture at the dawn of web, we relentlessly and progressively broke them down into smarter, smaller units of features called micro services. The whole backend world embraced this model.
Major breaking changes from monolith and benefits are:
- Product teams empowerment and enablement
- On demand delivery enablement
- Technical stack flexibility
- Better Maintainability
- Better testing, hence quality
- Technology agnosticism
- Better velocity
- Better knowledge of code and functional domain
In the end, we have an elegant decentralized backend stack. But how can we take care of our massive front end monolith ?
I’ll take here a real life scenario. Cdiscount scenario (Cdiscount is a french 4.5B$ e-commerce platform handling 1B yearly unique visitor).
Before we start, here are some utterly important considerations:
- Front-end Caching: Maybe speaking about caching and front-end sounds awkward to you (it certainly did to me initially). But in real life, there is simply no way to avoid it at a descent scale (both traffic and software). So, let’s be it, we will talk about caching at the front-end side …
- SSR: Server side rendering. Everyone who considers SEO and performance have heard about SSR. React / Angular / Vue can dynamically execute server side the first render and deliver DOM. I think this is the most common and popular use case for SSR. This post will only cover the SSR side. For SPA, this is another point of view, I will speak about it another post.
- MFE: short hand for microservice front-end. Any front-end technology that could deliver a service independently. (not only sever result output, could be source code). Could be also mentioned as remote in module federation context.
- Fragment: A fragment is a separated unit of logic. Really similar to MFE. This is a meta representation.
Let’s say, you’re a big company, with a lot of employees and teams. You’ve got one or multiple sites in React / Angular / Vuejs; with SSR.
And of course you have a nice micro service stack for the back-end with dedicated teams for each asset of your system. (Product / Price / Cart / Checkout and so …).
At the front end side, you are basically facing the following challenges:
- Releases crashes: Without Canary Release, one release can crash the all site. So Continuous Delivery is just too dangerous. This architecture issue deeply impact your change fail rate (cf Accelerate).
- Personalization and dedicated TTL: Caching at the top of the stack (the generated DOM) brings you a huge side effect. Indeed your cache is computed for each URL and several pages about the very same object (e.g. a product) ends up having different content due to different generation timestamp. One way to workaround those limitations could be the use of ESI (edge side includes). This is a hard choice, which also comes with its package of problems and coupling.
- Flexibility of release: deployment is just one big pipe. If a team wants to release only one feature (e.g. on the price component); you have to deliver the whole site.
- Change strategy: Changing one core feature (redux upgrade for instance) will impact all your site in one shot; you must migrate everything at once (if performance is a concern for you). Doing AB; doing POC are, in the same way, a pain.
- Flexibility of run: this is hardware/processor affinity. Dedicated CPU by MFE. To do that, we have to transpile code dynamically.
As pre-read material, I would recommend Martin Fowler post on micro service front-end. (like many times with Martin, this is spot on): https://martinfowler.com/articles/micro-frontends.html.
Several solutions claimed to resolve this problem the past few years:
The first approach for sharing code, is npm and package handling. This solution doesn’t resolve all the major problems because, at the end of the day, you are still stuck with a monolithic approach. No independence at core, you’ll have to modify all callers (but I wanted to mention it because this is the first natural way that comes to mind)
Client side only frameworks
Also, I am not interested in this solutions, because SSR and SEO/Perf is mandatory in a real world scenario. We want a universal full stack solution.
Micro SSR is the concept of building a single page with several different front end apps.
- A major actor on this type of architecture is mosaic (Zalendo).
Mosaic addresses this issue by using Fragments that are served by separate services and are composed together at runtime according to template definitions.
Project Mosaic-Frontend Microservices
Mosaic is a set of services, libraries together with a specification that defines how its components interact with each…
Other similar approaches:
Micro SSR in details
Each micro SSR or MFE is an application (in a front-end way; e.g. a React App). Each app is composed by two artifacts:
- SSR endpoint (Node js instance, isomorphic technique)
- a js bundle (webpack transpile: cf manifest.json)
Note about bundling: each app is, at core, a Webpack transpile/bundling ; and doesn’t have to know each other, each Webpack is self-contained.
At the top level, we have an orchestrator which aggregates all SSR outputs and bundles them together to deliver a page. Some people developed it in React, for SPA convenience; others in Nodejs core (cf mosaic: Tailor), but independently of the chosen language, the problems are about the same.
In this schema, ORCH aggregates all the DOMs served by SSR / caches.
CACHE, could be applicative caching or reverse proxy (varnish, …)
I have drawn several BFF, but it could be just one. In fact we want to have a vertical stack, so this is a coherent choice.
This approach has some strengths:
- Multi CPU on executions
- Flexibility of run / code / delivery
- Multi stage caching, Edge side caching
Here, a MFE is composed by only one artifact: the source code, in Webpack terms, a bundle. We could see that technique like dynamic npm packages (even if bundling is not that coupled) but without versionning, and so, all the pull requests associated.
The main difference with micro SSR is that we share code. And when sharing code, we still have a monolith (from server/CPU point of view), but code is dynamic. Releasing MFE will be dynamic on every depending host.
So, no multi threading here. (only one execution: the host execution)
R&D subjects: all envisioned solutions
Several points are R&D subjects:
- Sub MFE: a MFE which call another MFE. This is a real performance issue. To know if a MFE is contained in another, we have to wait the return of the first container. Complexity can be really tricky. In the micro SSR schema, I’ve mentioned a prefetch (prefetches subfragment), this could be a solution but a difficult one. Note : Mosaic and Tailor simply said no to this feature. Note 2 : cf Cdiscount’s CTO quote at the end ;)
- Sharing libraries: on the front-end side, some libraries need to be shared. Because of versioning, perf; let’s see the next point.
- Performance front-end: because performance matters on SEO, and for the users, everybody in the web community is focused on performance. Here, we have a real problem because each Webpack delivers all the libraries; no more common; and no more tree shaking / code splitting. All this, have to work with Webpack externals. Ouch.
- Maintenance and reliability: with this approach, there is a huge question on depending on custom libraries to make work all of this. (orchestrator / redux sharing / …)
- CPU / multi threading: with micro SSR, all MFEs are bundled and executed server side by one dedicated CPU, on bundling injection, one CPU is working, because we share code; like a dynamic npm package.
- Change strategy: bundles have to share versions of packages with externals; no solutions here when you upgrade a core library, if performance matters to you, you’ll have to upgrade on all MFE at the same time; in two words: delivers everything.
Passed all this pretty complex considerations on MFE and the solutions out there, here comes Module Federation.
First of all, module federation will be included in webpack 5.
Zack Jackson, the co-creator, describes it like this:
A scalable solution to sharing code between independent applications has never been convenient, and near impossible at scale. The closest we had was externals or DLLPlugin, forcing centralized dependency on a external file. It was a hassle to share code, the separate applications were not truly standalone and usually, a limited number of dependencies are shared. Moreover, sharing actual feature code or components between separately bundled applications is unfeasible, unproductive, and unprofitable.
This is in development, as we speak. And we already see a powerful and agnostic technology. Webpack 5 will be a fantastic breaking change in front end architecture.
The promise here, is to share code. AND sharing is done by Webpack.
- The first important thing in my opinion, is the keyword Webpack. Yes, all the Webpack jobs will know themselves and will be able to share between them. The promise in itself is a revolution. It can solve the performance and sharing problem naturally, in an elegant way.
- Second thing, we share SSR side, for now, code. Not DOM. Right now, Zack Jackson is working on sharing DOM, but by Webpack. Why? Because if Webpack can transpile a part of the react tree, we can distribute this job on several CPUS, and so, we can go down the multitread path and achieve performance ! cf https://www.youtube.com/watch?v=kOuoSBTCzl4
But, for me, the real hard work, on this, is the library sharing part. And module federation is about this. It can at runtime elect versions - have a dependency resolution - and this is a real big thing.
Automatic module sharing (i have this in v2 of webpack-externral-import but the rewrite (v3) for Webpack changed my approach. So ill be re-implementing this
Tree-shaking support - FederationPlugin - if sharing a module from host to remote likely shouldn’t tree shake the module. However ill be writing another extension and hook for SharedModule which will enable dynamic export hydration. The idea is that you can merge exports between two builds. Thus offering tree-shaken, shareable code. This required some additional work outside the scope of my proposal to Webpack.
All the boilerplate code we have been writing to make MFE work (redux states, reat custom hydratation, etc ..) are not needed anymore due to Webpack integration.
- Sub MFE: ✅ : bidirectionnal remotes is the module federation answer.
- Sharing libraries: ✅ : Webpack is about that.
- Performance front end: ✅ : resources are processed by Webpack. It handles all the infrastructure required to optimize the amount of shared code.
- Maintenance and reliability: ✅ : Webpack and its community are the response; no more dependencies to the core functions, but Webpack.
- Change strategy: ✅ : breaking changes can be applied incrementally ! Like Zack Jackson said to me on twitter. Indeed, with module federation you can switch versions without pain; in all other solutions it will cost size, so a performance issue.
- And more: the possibilities aren’t limited to MFE application cases; I’m sure it will highlight other architectures and other very nice tricks around Webpack 5. (AB testing, …)
Here: https://github.com/module-federation/module-federation-examples you’ll see some use cases and code exemples:
For now there are a few downsides:
- In development now; so we’ll need Webpack 5 release.
- Proprietary things ? Zack Jackson is thinking about a model; only plugins should be impacted (automatic vendors shaking …).
- An issue on prefetching resources; but Zack Jackson is telling us “no problem ! but no time to demonstrate” (https://github.com/module-federation/module-federation-examples/issues/55)
- Module federation is really young, it is really beta, and developers are going to find lots of use cases. It is clearly a thing to watch.
- For MFE, module federation will change the landscape of MFEs like we know them today. I am clearly watching it and driving our internal stack toward this solution. On boarding all our available experts on this exciting journey.
Module federation links to look at:
Getting Started With Federated Modules
If you've worked as a developer long enough, you've ran into the inevitable problem of sharing components between…
This repository is to showcase examples on Webpack 5's new Module Federation can be used. If you need support, consider…
Prefetch is for cowards. Fabien Poletti, CTO at Cdiscount