Micro frontends have been generating a lot of buzz lately. It's a topic I also explore, along with various alternatives, in my Angular Architecture Workshops. While this pattern can be quite appealing, I believe a crucial aspect is frequently overlooked: breaking your domain into self-contained pieces. This, however, is the hardest part and should be tackled before even thinking about technical solutions like micro frontends.

The rationale for slicing your domain

One of the key lessons from decades of building maintainable software is to keep coupling as low as possible. The logic is straightforward: when everything depends on everything else, making changes to individual components becomes a nightmare.

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 1

So, it makes sense to partition your system into smaller units that have minimal knowledge of each other. For example, when designing an e-procurement system, you might split it into the following sub-domains:

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 2

Following the principles of domain-driven design, you would model each sub-domain independently to avoid shared references to common concepts across different boundaries.

Naturally, this is easier said than done. Finding the right boundaries for your sub-domains requires deep domain knowledge. To acquire this, you need to engage with domain experts. Techniques such as event storming can be invaluable here.

Essentially, you need to identify the use cases and map them to sub-domains. If you find that many use cases cross your proposed boundaries, it's a strong signal to reconsider your decomposition.

After the split: what comes next?

Even if you've identified suitable sub-domains, remember that your domain boundaries should be re-evaluated periodically. New use cases might necessitate shifting concepts and responsibilities between them.

Assuming you've found the "right" sub-domains, the next step is deciding how to build them. One straightforward option is a integrated solution—a deployment monolith—using a monorepo:

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 3

I won't delve into all the pros and cons of each architectural style here, but I'd like to point out that this setup promotes a consistent user interface and results in optimized bundles since everything is compiled together.

On the flip side, a team owning one sub-domain must coordinate with other teams about the overall architecture, the chosen framework, and a shared dependency update policy. Interestingly, this could also be viewed as a benefit.

There's also the temptation to reuse code from other domains, which can increase coupling and, eventually, lead to breaking changes. To counter this, you can use tools like Nrwl's Nx. For instance, Nx lets you define import restrictions between different parts of your monorepo, enforcing your intended architecture and keeping modules loosely coupled. I have covered implementing strategic DDD with Angular and Nx in a separate post.

Monoliths, micro frontends, or a middle ground?

To achieve even greater decoupling, you could break your system into several smaller, independent applications. If your use cases don't cross sub-domain boundaries, this can empower teams to be more autonomous and deploy their parts independently.

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 4

Additionally, working with multiple smaller systems can reduce overall complexity.

If you desire even more isolation between sub-domains and their respective teams, you might assign each sub-domain its own (mono) repository:

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 5

This is where you land in what people commonly call micro frontends. It allows teams to be as autonomous as possible: they can pick their own design patterns, tech stacks, and decide when to upgrade their frameworks. This also means they can select the "best tool" for the specific challenges of their sub-domain.

However, this autonomy comes with trade-offs: you now need to manage shared libraries via npm, handle versioning, and deal with potential version conflicts.

You'll also need a strategy to integrate these separate applications into a single coherent system for your users. A simple way to do this is with hyperlinks:

It’s about cutting your domain, not (first and foremost) about micro frontends! — figure 6

A more frequently discussed method is a shell that dynamically loads different single-page applications. As outlined in a previous article, each integration method brings its own set of advantages and disadvantages.

The real crux: your sub-domains

As demonstrated, choosing an implementation approach for micro frontends—or even debating their merits—shouldn't be your starting point. First and foremost, you must identify the right sub-domains. Only then can you focus on the viable technical options to build them.

For example, if it's impossible to prevent use cases from overlapping your sub-domain boundaries, or if users frequently hop between them, hyperlinks might not be the most effective integration strategy.

Furthermore, you need to be clear about your architectural goals to evaluate the candidate technologies, a topic I've previously discussed here. Does your project require a mix of technologies? Do you need separate deployments?

Finally, here's the good news: once you have a solid understanding of your sub-domains and your architectural objectives, choosing between the available implementation options becomes much more manageable. With that context, you can quickly dismiss the ones that don't fit. At the same time, weighing the pros and cons of the remaining candidates is far simpler.