Introduction
One of the most recent books I read was Building Micro-frontends (written by Luca Mezzalira and published by O'Reilly), which although had a couple of downsides such as repetitiveness and clarity, it still managed to deliver a complete framework of decisions, which if its followed correctly, it can help architects and senior developers to take the right decisions whenever they come across with a microfrontends architecture. These rules set the foundations of four main pillars, which we will discuss in a moment, but before that, let's briefly explain when we will need to use this framework.
When you should use it?
Web is the easiest way to communicate with customers, which often utilised from a business as a medium to validate its own idea. A software is the communication pipe between customers and business and as the business grows, the software grows as well. But, there is a catch. The business wants to validate its idea as quickly as possible, but the lack of time doesn't allow the business for an extensive design of the software, which means that the software will potentially become inefficient and unscalable at some point. For that reason, most start-ups choose a three-tier architecture for their initial implementation (Minimum Viable Product - MVP), and they revise it to a more scalable solution when the right time comes. In terms of backend development, the application and data layer change to microservices, and the presentational layer is splitted into microfrontends.
With that being said, when the business needs to move away from its monolith, it needs to take some architectural decisions which set the boundaries of how the architecture will evolve. At that point, we want to use the framework suggested by the author, to guide us through this journey, as well as to warn us when we find ourselves deviating too much. This framework is applicable only on a microfrontends architecture and if its followed wisely, it can provide clarity and flexibility within our infrastructure.
Since we clear this one out, let's move to the beef now.
Framework
The microfrontends decision framework depends on four main pillars, which are:
- Bounded context and splitting
- Composition
- Routing
- Communication
1) Bounded Context and Splitting
The first decision we need to take is how we split the software. We can either split the software in vertical or horizontal slices, with both approaches sharing a common thing. Slices shouldn't overlap between each other and they must operate independently. This is also known as bounded context. Different techniques have evolved to help us to determine a bounded context of an application, but the most known approach is Domain Driven Design (DDD). The core idea of DDD is that you split your software into teams based on the domain they operate.
For instance, if we assume that our business has an e-commerce platform and we follow DDD, its common to split the software into teams responsible for the landing, authentication, products and checkout pages. Each team owns the pages related to their domain and their ultimate goal is to cover the whole spectrum of the business proposition.
The above example is a common case of a vertical split. From my experience, a vertical split is the easiest approach which we can follow, mainly because there is a one-to-one relationship between a microfrontend and a page. A single page renders a single microfrontend, which unfortunately is not the case for a horizontal split, which allows multiple microfrontends to coexist in the same page. For example you may have a team responsible to render the products of a page, which also needs to share the same page with the recommendation teams, who handles the rendering of suggestions related to particular products.
2) Composition
We managed to split our software into smaller pieces, but we also need to ensure that we render those pieces consistently and provide the same presentational layer as before. To achieve that we need to have a parent application that orchestrates the rendering of the microfrontends and this can be done with three ways:
Client-Side Composition
We need to develop an app, also known as application shell, that handles the rendering state of microfrontends, and its preferred to be technology-agnostic. With that way we avoid introducing coupling between the shell and the framework libraries we use to develop the microfrontends.
In terms of business logic, we also need to keep the application shell agnostic, however, centralised logic between microfrontends such as analytics and monitoring can exist in that layer. Here are a few libraries that support client-side composition:
Edge-Side Composition
Edge-side composition assumes that the assembly of the view occurs in the edge layer, which varies between different providers. Most providers have support for Edge-Side-Include(ESI), which is a mark-up language that can instruct edge how it should place the microfrontends within a view.
Server-Side Composition
This option assumes that a backend-server is responsible to assemble the view that is eventually rendered on the client-side. This approach is extremely flexible because it allows you to easily run experiments and gain good SEO(Search Engine Optimization), however, you may encounter scalability issues. Before you choose this option, make sure that autoscaling rules are in place and the application is NFT tested. The most known technique used to compose a view is Server-Side-Include (SSI), which is also a markup language that you can use to set placeholders of where elements should be. Traditional servers such as nginx and httpd have built-in support for it. Below you can find a few libraries that you can use to achieve the same goal:
3) Routing
We also need to determine a way to route customers to each microfrontend, which should be managed by the same tool we choose to handle composition. For example, if we choose client-side composition, then the application shell should be responsible for handling the navigation. For that reason, I would put Composition and Routing pillars in the same bucket, however, the author presented those options separately, so I kept them like that.
In addition, each microfrontend can also have its own localised form of router, which navigates users between the pages owned by a single microfrontend. However, when a customer needs to navigate between pages of different microfrontends, then the global router should handle that scenario.
4) Communication
One of the core principles of a microfrontends architecture is independence, which means that each microfrontend needs to be independently deployable, as well as to avoid sharing state between other microfrontends. This is totally valid, especially if you compare it with microservices, which suggest having a single data store for each microservice. However, there are a few cases where microfrontends need to communicate between each other, and at that point we need to determine a mechanism for them to communicate. The options available to us depend on how we decide to split our software, so if its a vertical split we can utilise:
- web storage (session/local storage)
- querystring
- path parameters
On a horizontal split, we can use:
- Subscribe to events and publish custom events using the naitive CustomEvent API.
- EventEmitter
Since a vertical split loads a single microfrontend at a time, we just need to store our information somewhere, which is then picked by the microfrontend that follows. But on a horizontal split, where multiple microfrontend are rendered, the situation is different and state needs to update immediately. In that scenario, the recommended approach is event-driven communication. This will ensure that the microfrontends remain decoupled and can still share information between them.
Summary
While microfrontends architecture provides a lot of benefits in terms of scalability and independence, its still a complicated architecture that can introduce a lot of issues if you don't set the right foundations. The microfrontends decision framework will help you to mitigate those issues, and encourage you to set the right boundaries on how your architecture should operate. These rules are there to keep the scope of your software as clean as possible and if you find yourself deviating from that scope, you may need to think twice about what you choose.
The four main pillars you should consider are Bounded Context and Splitting, Composition, Routing and Communication, which determine different approaches you can utilise based on different business context. Before you move into microfrontends, take a step back, analyse the context of your business, and pick those options that have a better fit in your organisation.