So here is a block diagram for a very simple e-Commerce site that is made up of three applications:
We have the applications at the top, Home, Search and Checkout, and they connect to some React components shared using NPM (AddToCart and Frame). And they also have a shared codebase for the redux store which holds the cart and some shared API wrappers in the logic NPM module. And all of this sits on top of two micro-services.
And this is the kind of thing we see a lot of nowadays; the "micro-site" architecture. The most common architecture before that was the "monolith", a single, usually Java application, where all the frontend code was in one application.
The advantage of the micro-site architecture is that each application can deploy independently. But there are some disadvantages:
- Code sharing is really difficult. Having shared code in NPM modules creates friction to sharing code since it has to be extracted from the original application, which means jumping from repo to repo, doing version bumping, etc.
- Single Page Applications (SPA) are really difficult to pull off with micro-sites, but being on a SPA is a huge performance win for the customer. There are great options like SingleSPA, but those have a learning curve.
- End-to-end testing is very difficult. How do you know when you release a new version of checkout it's going to be compatible with the other micro-sites and not break existing flows? That's what end-to-end testing is for, but staging the entire site using existing tools is very tough.
There are other issues as well, but these are the ones we address in this article and in the associated YouTube video:
The solution I'm proposing is to use the new Module Federation functionality in Webpack 5. Using Module Federation we can share code in-place between these applications and restore the ease of code sharing. We will bring back the SPA functionality we lost. And (in the video) we demonstrate using Cypress to perform end-to-end tests on the entire system. As well as bringing in the Module Federation Dashboard to show how all the parts work together in a way that we haven't had from NPM modules (or anything else).
Relocating the UI
First step is to move the AddToCart module from components.
This is the natural place for that code because it's functionality that fits within the purview of the Checkout team.
Our next stop on the train towards full site federation is to move the header (or Frame) component over into the Home application and then to expose it out to all the applications.
We will actually improve on that in a later step, but for now this is a huge win because the Home page team can update the Frame header and all the applications will see those changes immediately after Home page deploys. And that same thing applies to the AddToCart button from the Checkout project.
Moving the Business Logic
The next thing to move is the Redux store into the Checkout application (because it only stores the cart state).
Now all the applications connect to the store on Checkout and receive updates immediately when any new updates are pushed.
And then there is one more move to split the business logic code between both the Checkout and Search applications.
Checkout manages the Cart Logic because it relates to their role in the system. And the Search team manages the product related API wrappers which are also shared out to any other applications that need product data or search capabilities.
Now, while not strictly required, migrating all of the node modules, including the business logic and state management is interesting for a couple of reasons. First, because it's important to understand that Module Federation can shared any type of code. And second because it's interesting to think about when to use, and when not to use, module federation to share code.
Moving To A Spa
The final step is to move to a full SPA by exposing all the body content components for each of the applications And then linking those to the already shared Frame component using a SPA router such as react-router-dom
And that's all it takes. Basically it starts with migrating to Webpack 5 and then the rest is mostly skiing downhill as it's very easy to share code between applications using this technique.
Module Federation is one fix some of the issues we've seen with the Micro-Site architecture without losing the advantages of independent deployment.