A coarse-grained view of micro-frontends and their trade-offs
In software engineering, one of the most important technical decisions you can make is the method of partitioning a project’s architecture. The consequences of this decision cascade, so you must get this right! Unfortunately, there is no silver bullet solution as the correct decision depends on your context. As always, we defer to the First Law of Software Architecture:
Everything in software architecture is a trade-off.
Using micro-frontends is an increasingly popular method of partitioning frontend applications. In this post, I will define them and describe their trade-offs. For those who have more time, I will explore the why, starting with exploring different partitioning styles. I then share a coarse-grained overview of micro-frontends. But first, for those who are in a rush:
TL;DR Micro-frontends, inspired by microservices, break previously monolithic codebases into independently deployable parts that are composed of a greater whole. Deploying frontends independently improves desired characteristics such as scalability, agility, and maintainability. These benefits mirror those of micro-services but also have similar accompanying downsides: operational and governance complexities. Examples include complexities with code reuse, standardised UI, managing interactions between micro-frontends, and deployment configuration.
Before delving into micro-frontends, let us first explore other partitioning methods
One approach is the Big Ball of Mud partitioning (or lack thereof):
“A Big Ball of Mud is haphazardly structured, sprawling, sloppy, duct-tape, spaghetti-code jungle showing unmistakable signs of unregulated growth, and repeated, expedient repair.” — Brian Foote and Joseph Yoder
This approach is excellent for scrappy spikes and prototypes as it lends itself to characteristics of simplicity and (initial) speed. It is not ideal for longer term use, as the structure eventually reduces the characteristics of agility and resilience. Side point: an underrated skill is identifying the characteristics that matter to your particular context. Occasionally, code snobbery or business pressure leads teams to select incorrect structures or technologies that do not align with the characteristics that are needed to make the project a success.
Most applications fall within the spectrum of Technical and Domain partitioned applications.
Technical partitioned applications are sliced into technical layers: persistence layers are separated from presentation layers, and so on. This simple structure allows for the optimisation and specialisation of technical layers. However, it means that business logic is smeared across layers, potentially resulting in lowered agility and resilience. Because of Conway’s Law, an organisation with this partitioning structure usually has a separate database, backend, and frontend teams. In a frontend context, a technically partitioned application could be structured:
┣ 📂 state
┃ ┣ 📜 store.js
┃ ┣ 📜 reducers.js
┃ ┗ 📜 actions.js
┣ 📂 components
┃ ┣ 📜 header.js
┃ ┣ 📜 button.js
┃ ┗ 📜 footer.js
┗ 📂 api
┣ 📜 queries.js
┗ 📜 mutations.js
Domain partitioned applications are sliced into business domains. Think of Spotify model inspired cross-functional teams that own their product end-to-end. Or Amazon’s “You build it. You run it” approach. This has the benefits of increased agility, maintainability, and scalability. In a frontend context, applications with this partitioning are structured:
┣ 📂 checkout
┃ ┣ 📜 state.js
┃ ┣ 📜 screen.js
┃ ┗ 📜 api.js
┗ 📂 catalog
┣ 📜 state.js
┣ 📜 screen.js
┗ 📜 api.js
Your decision to choose a particular partitioning method has consequences on the deployment possibilities. Technically partitioned architectures can only be monolithic, while domain partitioned architectures can be both monolithic or distributed. Monoliths are controversial in software engineering, but always be careful of dogma and have a trade-off mindset. Shopify, as an example, uses a modular monolith, which is perfectly suitable for its particular context.
If you are partial to fancy terminology, we can use the term “quantum”:
An architectural quantum is an independently deployable component with high functional cohesion
Monoliths have a quantum of one (everything is deployed together), and it is large. Modular monoliths also have a quantum of one. Micro-services have multiple quanta (many pieces can be deployed independently) and are also each smaller.
The reason for micro-service popularity? They are a more evolutionary architecture, as they have a higher quantity of smaller quanta. The trade-off, however, is in the resultant complexity. Deployment, code sharing, observability, and transactions are all areas that become more complex (although tools like Temporal are making life easier), so it is always a trade-off.
To go slightly off-piste, the trade-offs between decentralisation and centralisation touch on a wider theme of “competing methods of efficient data processing.” Free market economies process information in a decentralised fashion. Coherence emerges through parallel and independent actions of many individuals with limited and local knowledge. According to Yuval Noah Harari, this efficiency is why the Soviet Union’s inefficient centralised economy lagged behind the United States. Cryptocurrencies are another foray into the world of decentralisation that wrestles control away from central authorities which, naturally, have caused some alarm.
Micro-frontends are an emerging decentralised architecture style inspired by microservices. The idea is to break monolithic frontend codebases into smaller parts owned by autonomous teams. Unlike modular monolithic frontends, these applications are then independently deployed to production, where they are composed into a larger whole.
As a tangible example, see the diagram above. Team A and Team B work on completely separate domains that are then deployed independently and composed together with either a Vertical Split or Horizontal split partitioning style. In a Vertical split, teams own a business domain (see Domain-driven design), and in a Horizontal split, teams own a page slice.
This architecture can be used with various backend architectures, although they are particularly well suited to microservices. If used with microservices, cross-functional teams can fully own business domains.
- Tooling: teams can choose the best tool for their particular use case, refactor with less fear of creating bugs for other teams and rewrite applications easily. This helps move them move toward the working model of you build it, you run it, and it allows for incremental upgrades.
- Deployment: teams can have “À la carte deployments” — each application is deployed to its own cadence. Unrelated changes do not need to be bundled together, and deployment risks are decreased, leading to increased agility.
- Governance: governing code quality standards and consistency across multiple teams is more difficult because of the reduced overall observability. The ability to mix a range of competing technologies, tools, or frameworks on a single page can also lead to micro frontend anarchy.
- Operational: Much more administrative work is required to manage micro-frontend tooling, pipelines, repositories, and servers. Collaboration and managing state is also more complex
Micro-frontends are a fantastic way to manage complex frontend codebases. It is a partitioning style used by autonomous teams that improve characteristics of scalability and evolvability. They do not have the characteristic of simplicity and require considerable governance and DevOps investment. If your micro-frontends need to be deployed in a particular order, save yourself the trouble and stick to a modular monolithic frontend.
If the characteristics that your business requires align with those provided by micro-frontends, and if you have the operational expertise, it is worth exploring the method further. Migrating towards micro-frontends is also not a “big-bang” endeavour, so you can always spin up a lighthouse team to carve off a small domain as a first experimental step.
This article was deliberately coarse-grained to examine the principles of micro-frontends. If you wish to have a more fine-grained view, I recommend looking at the following resources: