The Micro-Frontends future

Photo by Randy Tarampi on Unsplash

Between the end of 2021 and the early weeks of 2022, I spent some time looking at where the micro-frontends journey is arrived so far.
I analysed the different challenges where teams are struggling with, the anti-patterns that causes coupling in the long term, and what are the recurrent patterns used for solving them.

We discovered that micro-frontends enabled teams to work independently and contribute to medium-large size applications, iteratively evolving our applications and reducing the blast radio of potential issues.

However, the analysis couldn’t stop on what we have achieved till now.

I had to look forward, a step into the future.

I have to understand what are the missing pieces of this fascinating puzzle and try to picture what would make this architecture approach even better.

In this post, I want to share a bunch of ideas and trends that might spark some interesting conversations in the micro-frontends community.
The topics covered are taking into account the client-side, server-side and edge-side implementations of this architecture.

Finally, I am going to share what will be my focus for 2022 to the micro-frontends ecosystem.

More upfront design

One of the main challenges for micro-frontend architectures is answering the question: how “micro” is a micro-frontend?
This is a question many organizations are facing, and in the reality, there isn’t only one answer, we need to understand the context, the organization structure and its size, and the communication flow between team.
After several engagements with multiple teams working on distributed architectures, I’ve seen many times “distributed components” more than a micro-frontends implementation.
With distributed components, the domain knowledge was shared across the container and the “micro-frontend” or even between the container and multiple “micro-frontends”.
We are still struggling to find the right boundaries and there is sometimes a lack of understanding of how we should interpret micro-frontends when we implement them.
I think this understanding is a necessary step towards better maturity, mastering the application business subdomains is not an easy task and requires a deep knowledge of the application we are building.

However, I think there is a potential solution for mitigate this challenge.

Investing more time upfront on the whiteboard revising with multiple parts of the organization how split our business domains without compromising the user experience is a must.
When we walk out from these meetings, we should be have enough ground covered to kick off the project with confidence and review our decisions on a regular cadence to make sure our initial assumptions are still valid for reaching our goals.
Remember that we cannot capture everything upfront, a business and an organization today is likely to change in 6 or 12 months so we should revisit our micro-frontends boundaries on a regular basis.

Also, never forget the link between organization structure and software architecture, it’s important to be aware of it and take it into account in our design decisions.

Micro-frontends communication

When we have multiple micro-frontends in the same view, at some point they need to communicate with each others.

In the mental model I created for designing micro-frontends, it’s encouraged to communicate between micro-frontends using a publish-subscribe pattern for enforcing the boundaries between micro-frontends, avoid or at least reduce the design-time coupling that leads to more autonomous teams.

For implementing technically this pattern there are several options such as Custom Events, an event emitter library, or even reactive streams.

An important requirement came up in the last few months that I didn’t put too much emphasis at the beginning, probably because I gave it for granted, but it’s definitely something to be aware of.
Like for event-driven architectures on the backend, having a clear schema for an event will help to avoid mistakes during the integration phase. Moreover, a schema provides a clear understanding of what’s going on inside a specific application also for tech people who are not working on the codebase directly.

I discovered in one of the many Slack channels I follow, this event-bus library that definitely helps achieve a more structured communication between loosely coupled elements (micro-frontends but not only): https://www.npmjs.com/package/@trutoo/event-bus

@trutoo/event-bus for your micro-frontends communication

Considering micro-frontends is a distributed architecture, there is the need to have a more formal API or events management.
API or events are the way how teams interact, not only in micro-frontends.
It’s essential to understand that these practices are not helping only a developer to avoid mistakes when an event is sent but also facilitate the discussion between teams and provide clarity of intent.
I hope in the future there will be even more effort in simplifying the developer’s experience when we have large applications that are massively using loosely coupled communications strategies.
How nice would be having an event registry to consult every time we are developing new interactions between micro-frontends?

Finally, if you didn’t have a chance to see what PayPal is doing on micro-frontends communication, I highly encourage you to watch this great video!

https://lucamezzalira.wordpress.com/media/8ab1607c97e5b8f5865c7a8b340318faPayPal presentation during the Micro-Frontends Conference organized by hasgeek

Server-side rendering (SSR)

Server-side rendering architectures are the ones who are innovating more in the past few months, think about Next.js or the investment made by the team behind React 18 with server components.
We have some interesting solutions also on micro-frontends like module federation for Next.js, Piral, TailorX, ILC and many others.

For SSR micro-frontends applications there are quite a few topics we should start to look at more in depth.
These are the gaps I’ve individuated so far:

  • micro-frontends discovery: like the service discovery pattern for microservices but applied to the frontend. Using this pattern we could compose micro-frontends dynamically without any static reference to an endpoint in a system.
    Imagine a micro-frontends infrastructure self-registering to a discovery service and a UI composer retrieving micro-frontends from the discovery service instead of being point-to-point with the micro-frontends itself 🤯
  • reference architectures on the cloud: there is a lack of guidance on how to build SSR micro-frontends architectures using popular cloud providers. This is a friction point that can be fixed relatively quickly and I want to help as much as I can.
  • leverage the serverless paradigm with micro-frontends: I believe serverless can provide a great speed of development delegating the infrastructure management to a cloud provider. At the same time, we have to shift our mindset in understanding which services we should leverage for specific workloads like micro-frontends. 
    For instance, I see the value to use a service like AWS Step Functions for simplifying the creation of micro-frontends considering it provides great integration with the entire AWS ecosystem. This allows us to embrace a low-code model that in the long run will simplify the maintenance.
    This is one of the many patterns we can use on the cloud, but exploring these patterns with micro-frontends can be extremely fascinating (at least for me 😅).
  • A framework-agnostic React server components approach: having a mechanism for atomically reloading a portion of a view using SSR when data changes on the backend and seamlessly integrating with client micro-frontends. This will allow a hybrid architecture mixing up CSR and SSR using the right approach for every micro-frontend. Probably we can create such a mechanism today, but having a sleek implementation like in React 18 would be the final goal.

As you can see there are many opportunities in front of us, some more tangible like the reference architecture one, some more longer-term like the agnostic React server components approach.
Of this list, my focus will be on covering the reference architecture as well as the investigation of using the serverless paradigm for micro-frontends. I’ve already started working on the prototype for the reference architecture and I have some interesting prototypes on the serverless side as well. Stay tuned for further updates 😉

Partial hydration

Performance is key for every frontend application, including micro-frontends ones.
It’s been a while since I heard about the concept of “islands architecture, however, I believe in the end this architecture might fall under the micro-frontends umbrella due to its principles and characteristics.
The interesting technique that islands architecture introduces is the possibility to enhance the performance of our server-side rendering applications by leveraging partial hydration.

In a nutshell, instead of hydrating the entire page, only the “islands” visible to the users will be hydrated immediately and the others will be hydrated if/when the user will visualise them.

Partial hydration is not a new technique, it’s available since 2019 (if I remember well), but I didn’t see any reference to this technique in micro-frontends applications. Considering the nature of micro-frontends and how partial hydration works I believe this technique should gain more popularity for optimizing further our SSR micro-frontend applications.

In this post, Addy Osmani provides useful resources for understanding better the concept:

https://lucamezzalira.wordpress.com/media/05183d9218e2178a406d66ba817ac427Partial Hydration resources

Finally, if you are interested in this topic, I encourage you to have a read this post where there is a list of UI frameworks that might use partial hydration.
I’m currently experimenting with Preact in a micro-frontends proof of concept, hopefully, I’ll be able to share more insights soon.

Micro-frontends and edge computing

When we talk about micro-frontends at the edge, we often think about Edge-Side Includes (ESI) markup language.
This time I am pointing to the compute capabilities that many CDNs are providing like AWS Lambda at the edge or Cloudflare workers.

The edge technologies are advancing fast and therefore part of applications can be moved towards the edge improving the latency and the scalability of our solutions.
However, in many web applications, we cannot consider only the computational effort to generate an HTML page using multiple micro-frontends but we need to account also the complexity of the entire application.

Computation is often the “easiest” problem to solve nowadays, less so when it comes to data gravity (database, multi-region data replication, writes vs reads with global infrastructure, data replication latency…), or authentication that usually is centralised and well secured in a specific region of your cloud infrastructure or even a data centre on-prem.

It’s true, SSR micro-frontends applications can benefit from edge computing but they require access to a multitude of other resources (data, authentication, caches…) that are not fully available on the edge yet.
We cannot really think to use the full power of the edge unless we have a workload very well encapsulated that doesn’t require any of these external dependencies.

I believe we will end up having a larger adoption of edge technologies in the future, but at the same time I think we have to understand better where edge technologies can be used with a real impact on our workloads and not just because is “cool” (hype-driven development anyone?) working with edge nodes.

In my opinion, edge computing will have great relevance for micro-frontends in the near future, especially for improving the applications’ performance, but it’s not as simple as it seems right now.

Deployment

In microservices, there are a set of consolidated practices for de-risking the deployment of new microservices versions like feature flags, blue-green deployment and canary releases.
In the past 12 months I didn’t see any effort for implementing similar practices with micro-frontends, a part from feature flags that look a well-known pattern for many teams.
I believe a deployment strategy that creates confidence in the development team is a must-have.
In a distributed system, where often continuous deployment is a reality, we have to create a safety net for developers who can iterate fast-moving their code from their laptop to a production environment without risking introducing bugs experienced by all the users in one go.
For SSR micro-frontends we can easily reuse existing tools and practices for releasing our infrastructure leveraging one of these mechanisms, although, those strategies are often not embraced for client-side rendering micro-frontends applications.

There are several ways we can implement them, client-side, server-side or even at the edge.
My recommendation is to implement one of these strategies as soon as possible because they can create a safer environment for your teams and the consequences might surprise you… in positive 😁

Routing

Strictly linked to the deployment strategy, client-side rendering micro-frontends applications are lacking a solid routing strategy.
All the implementations are using the same routing libraries we use to implement monolithic architectures.
Instead, I believe we can do better than this!

When we mix a routing library in conjunction with the deployment strategy described before, we can have a very smart routing that takes into account newer micro-frontends versions, different environments, or even different user roles.
We can also have tools that gradually increase the traffic towards versions and performs rollbacks in the same way.
For instance, when we develop containers or serverless workloads in AWS, we can easily set up the deployment strategy we prefer with a few lines of configuration:

Canary Release of an AWS Lambda Function using AWS SAM

The routing in the application shell can be orchestrated easily via an external JSON that provides the different possibilities available without the need of integrating this information into the application logic.
Finally, when this static JSON is combined with deployment logic I believe the combination can bring a lot of value reducing the risk of new versions and allowing dynamic configurations based on any logic your business would like to implement.

The routing and deployment are definitely areas of interest for me. I’ll invest time during the next months to remove the undifferentiated heavy lifting and allow teams to better control their deployments and routing. I hope I’ll be able to share what I’m working on sooner rather than later because the working group is very excited about these two topics 🚀

Micro-frontends management

I didn’t explore (yet) this area, but I have a list of tools to try for understanding the PROs and CONs with micro-frontends.

My focus will be mainly on monorepo because I believe with poly-repo we don’t need extra tools for managing code like we have when there are multiple independent projects sitting in the same repository.
Currently, these tools caught my attention:

I believe all of them have some features that might help to structure a monorepo strategy improving the developers’ experience.

It’s a stretch goal for this year, not sure I’ll be able to invest enough time in reviewing every tool but I’ll definitely keep an eye on this space because I believe there are more unexplored opportunities to improve the developer experience even further.
Any suggestion on tools to try is more than welcome, especially if you can provide a brief review of them when you share your experience 😁

Summary

As you can see, there is still a lot of ground to cover in the micro-frontends ecosystem but we made great step forwards in the past years.
This for me it’s a super exciting opportunity to shape many areas of improvement for a “young” architecture that is raising success across enterprise organizations globally.
I’m sure there is more to discover, and I hope this fast adoption will bring new insights into what does and doesn’t work in distributed UIs architectures.
There are other topics on my radar like WebAssembly, better security on the client-side, streamlining even further the developers’ experience and more, but the topics listed in this post should provide food for thought for all the community to improve this novel way to scale our applications and organizations for the next few months.

Context.

In the last year, I have been reflecting a lot on the decisions making process.
Not specifically how I make a decision but how people are making decisions in general.

The question that often bubbles in my mind is WHY, why I made that decision, why my colleague took that decision instead of the other one, why that person is approaching that problem from that angle.
Sometimes I really struggle to understand certain decisions made by people and this is not necessarily a bad thing but it made me wonder.

I started giving an answer to the following questions:
Why don’t I understand that decision?
Why my decision would differ so much from another person?

LACK OF CONTEXT.

Context, yes… context.

The definition of “context” provided by Google is

“the circumstances that form the setting for an event, statement, or idea, and in terms of which it can be fully understood.”

I think the keywords for understanding the meaning of context are circumstances and fully understood.

Let me share a story with you.

A few months ago I was in a meeting where some colleagues were proposing an idea for solving a governance challenge that from my point of view wasn’t fitting the purpose.
I listened, elaborate in my head and I asked a few questions but I didn’t share my opinion at all.
I spent the following hours rethinking that conversation, every single word was echoing in my mind trying to find a sense of the solution proposed during that meeting.

After a day or two, I replayed again in my head the entire conversation several times and I tried to explain how I would have solved that problem from my perspective for seeing overlaps between the two solutions.
During this mental exercise, what I realized was that from my colleagues perspective, in their own mental model the solution was absolute fine, my point of view was different from theirs, not necessarily better or worse.
I realised I was analysing the problem adding different dimensions and information that allowed me to find an angle not explored by the others due to the lack of context on how the architecture, the implementation and the governance would have worked all together.
The circumstances weren’t fully understood, more than that, the circumstances weren’t shared, we weren’t thinking about the same solution because we had a different context.

Context is everything.
Think about it.

In software architecture, a successful architecture may be replicated in another organization with a completely different outcome (positive or negative).
Have you ever asked yourself why?
Because the context is different, the players are different, the interactions, communications, environment, the governance are all different.

In an Architecture Decision Record (ADR) we usually capture a decision made along with its context and consequences. Often these decisions are revised in the following months because the business evolves and therefore the architecture has to evolve alongside the business.
What we decided six months may be obsolete right now.
This doesn’t mean we took the wrong decision at that time, but with the ADR we are trying to capture WHY we took that decision in a specific moment in time, drawing metaphorically what was the context of the architecture, the forces and the solutions evaluated for being able to reassess them on a later stage and bring everyone inside the organization up to speed on the what reasoning process we applied behind a specific decision.

I don’t think we can change the fact that context can dramatically modify the outcome of specific actions for better or worse.
There is although one thing we can definitely do, learn more about the context we work for providing the right solutions and take the correct direction. More we know, better we can think about the possible consequences.
Moreover, we need to invest time creating a common context in our company, despite we work in a large or small organization, often the context where an architect or a developer operate is completely different from the one of another colleague in the marketing team.
It doesn’t matter if we work in the same organization, the reality is the context for people in a different department, even different teams, may differ substantially.

A thing I learnt in the past few years is not stopping in front of a decision made but challenge or be challenged in a genuine way.
When I design a new architecture, I usually socialize with developers, with the principal engineers and the other architects.
What I’m looking for is feedback and validation of an idea, but more often than not I gather the point of view of different people, how they would solve the same problem from their perspective, with their experience and expertise.

It shouldn’t be a problem changing your mind after gathering more context, people may judge you because of that, the reality is they have just collaborated on enriching your knowledge, augmenting your context and making the solution you are working on better than before.

Therefore engage with your peers in a meaningful way, especially the ones outside your teams, engage with product people, senior management, stakeholders, architects, other developers, other departments even.
Don’t be afraid to leave your bubble but explore outside it. I know it sounds easier than what it is, but remember…

More context we are able to gather, better decisions we are going to make!

Don’t be afraid to ask WHY, to understand the value behind a decision, to understand the train of thoughts a person holds, because all of these actions provide you with more context enabling you, in the long run, to make better decisions or understand better the point of view of another person.

Tech people often research online, study books, watch videos, listen to podcasts, dream about the implementations made by other organizations, and I’m one of them.

However, the next step after a research is contextualizing the learnings in our own environment.

Contextualizing… context again.

Replicating 1 to 1 what we have learnt, may or may not provide the same outcome inside our organization.

Funny enough a few days ago, I was listening to a podcast episode on Domain-Driven Design (DDD), the speaker was sharing his experience of implementing DDD in two different companies.
The first time was a complete failure, they made several mistakes due to lack of experience in applying DDD breaking the services in very small units and ending up with a “distributed monolith”. The company closed a few years later.
In the second company although, the same person gained experience, reviewed his mistakes and applied with his new colleagues a similar approach avoiding the pitfalls he learnt from the previous experience.
It was a complete success, he learnt from his mistakes, he capitalized his experience and, with a different context, DDD was successfully implemented.

There are plenty of stories like this one out there of achievements and defeats, two sides of the same coin.

Every time we write a line of code, every time we design a new architecture, every time we introduce a piece of infrastructure, we should be able to answer the simple question WHY we are doing this.
I learnt by experience this answer is not always easy to provide.
Maybe we want to try a new approach or tool, maybe we thought that specific approach would make more sense over another, and there isn’t any problem on this but let’s spell it out, explain why we do things in a certain way and if we don’t have a specific reason, try to find it because it will help yourself and the people you are working with to follow your idea better and rationalize the decision in your heads.
Don’t be afraid of sharing your reasoning, the worst-case scenario is that you learn a bit more about the context you operate.
Having an idea turned down is not a defeat, instead, it’s a great win because it means next time you will be able to add a new dimension on your ideas that would make them more contextual for the environment you work with.

Context means also that there isn’t always right or wrong, black and white, right and left. There isn’t the absolute truth!
Often I see developers stack with their position because an “influential person said that” or “John Doe tweeted about it, so if he said that…”.
However, it may be applicable only to certain contexts. We should challenge ourselves and our beliefs from time to time, looking for different opinions and different point of views to have a better picture of how people may approach the same challenge differently from us. Embrace a different perspective and don’t push it back up front.

Next time you are in a discussion with a colleague or a friend, try to move a step forward finding the reason behind a particular decision or behaviour.
And then try again, and again.
Interacting with people, asking questions, sharing your thoughts or reasonings, will expose yourself and maybe sometimes put you outside your comfort zone, however, it will add to your personal growth an enormous value and more food for thought to reason about.

Context is not immutable, many people when they join a new company have a lack of context but that doesn’t mean they will not have it unless they are not looking for it.
I truly believe human beings are curious by nature, our passions drive our curiosity pushing the boundaries when we really and deeply love what we are doing.
In these cases, these people are going to look for creating quickly the context for making what they love valuable again.

I personally started to see the world with different eyes, I am trying to understand more the context where a specific architecture, implementation, decision or feature would be applied more than solutionize upfront.

I don’t believe there is always a solution that fits them all. And “the standard” is not applicable in every context.

I decided to believe in accepting failures for improving an empiric world called software development, in learning from failures and successes, in human interactions, in growing without limits, in understanding better the context before providing a solution, in a wrong decision made for lack of context, in listening and providing context, in challenging and being challenged, in starting with the WHY more than the HOW, in looking for the context no matter where I’ll find it.

It took me a while to gather my train of thoughts and decide to share them, it may be valuable for some of you or not.
Hopefully, you will find some interesting angle in these few words.

Thank you.

Building Micro-Frontends… the book

In the past 5 years, I had the opportunity to work in an OTT platform called DAZN that streams live and on-demand sports events across the globe.

During this journey, we had to overcome several challenges considering we were (and still are) pioneers of live video streaming.
After the first release, I realized in order to scale the organization we had to rethink our platform to allow us scaling not only from a technology point of view but also from a people perspective.

For achieving this, we had to review several approaches and common practices for scaling our company and onboard more and more people working on the same project.

Our answer was embracing a new frontend architecture called Micro-Frontends that in combination with Microservices help us to scale our teams, creating independent artefacts that are mapped with our business domains.
Micro-Frontends bring a new approach for scaling frontend projects, decentralizing the decision making and stopping the one-size-fits-all approaches very well known by the frontend community.

I decided to gather my experience building our Micro-Frontends architecture and share it into the pages of a book.

Today I’m really happy to announce the early release of Building Micro-Frontends, a book published by O’Reilly and already available on Safari Books Online.

Building Micro-Frontends by Luca Mezzalira

For more information, I suggest visiting the book website where you can also subscribe to the updates. I’ll keep you informed on the next chapters, new content released online, events where I’ll talk about Micro-Frontends and more.

The early release allows you to share your feedback with me, shaping the content inside the book and having a voice during the writing process, don’t miss this opportunity, WE can do an awesome job TOGETHER.

Micro-frontends decisions framework

REV 20 DEC

In the past year (2019) I spent a lot of time talking, writing, podcasting and just chatting with people about micro-frontends.
I’m truly passionate about the topic, mainly because it’s still an unexplored land where challenges arise every single day.

During all my talks, workshops and webinars I was building step by step a mental model that allowed me to simplify my thoughts for making them approachable by people listening to me or reading my contents.
I’m sure there is still a lot of work to do but from the feedback I received so far, I was able to explain in a reasonable way what I had in my mind.

Recently I had the pleasure to do a 2h workshop in Bergen (Norway) about this topic and during my presentation (literally on the stage) I realized that I was very close to simplify even further the decisions process for embracing a micro-frontends architecture.
In JavaScriptland we are used to choosing a framework and getting along with that for the entire lifecycle of a project, sometimes we change it in favor of another one but many times we stick with it for many months/years till the end of life for a specific project.
Every time we use a JavaScript framework, someone else made architectural decisions that we live with (or trying to) and we focus on what matters the most from a business point of view: the features of an application.
This doesn’t mean we have an easy challenge in front of us at all, we still have an essential part of this “game” taking the right design decisions for delivering the project, but first, we need to focus on having a solid base that allows us to build on top of it, aka the architecture.

Martin Fowler defines software architecture as:

“Software architecture is those decisions which are both important and hard to change”

Based on this definition, focusing on the key things, even better, the most important decisions for defining a micro-frontends architecture I came up with 4 pillars we have to decide upfront when we start architecting a micro-frontends project.
Those pillars can be summarized in:

1. Definition

2. Composition

3. Route

4. Communication

Each of them plays a fundamental role in the good outcome of a micro-frontends project.
After taking those 4 decisions there will be many others to take along the journey but with those cornerstones, we should be able to cascade vast majority of the other decisions without any need of evaluating again our architecture.
Bear in mind that those decisions could change over the lifecycle of a project but changing them will require a considerable development effort in order to achieve a coherent project’s architecture.

Defining Micro-Frontends

The first decision to take is how we identify a micro-frontend, is it a part of a view (horizontal split) or the entire view (vertical split)?
Splitting horizontally would mean identifying micro-frontends inside the same view, therefore multiple teams are taking care of the page composition coordinating themselves for the final result presented to a user.

Splitting vertically, instead, would assign a specific view, or group of views, to a team allowing that team mastering a specific area of the application.

In general, deciding to approach a micro-frontends project with a vertical slicing will simplify many decisions to take further down the line, because it’s closer the way a frontend developer is used to work so many practices can be easily applied to this approach.
Also, the coordination between teams should be easier considering each team is owning a vertical slice of the application and not multiple parts spread across the application.

There are additional challenges when we decide to go with the horizontal split, anyway I described more in-depth how to identify micro-frontends in another post available on this website.

Micro-Frontends Composition

The second decision is understanding where we want to compose micro-frontends, here we have 3 options to choose from:

  • client-side composition
  • edge-side composition
  • server-side composition

Client-side means implementing techniques like an App Shell loading single page applications, for instance, check out Single-SPA project, with iframes like Luigi framework or via a library using client-side transclusion technique.
Implementing over the edge would mean using CDNs capabilities like Edge-side includes (not always available in all the CDN providers or not fully implemented) or via computation happening on/near the edge (bear in mind there are restrictions in how much logic we could run with a solution like lambda@edge for instance).
Finally, on the server-side, there are plenty of frameworks like Ara FrameworkOpen ComponentsPiral or Tailor.js and many others.
When we decide to go with a vertical split we should aim for a client-side composition using Single-SPA or similar mechanisms like we do in DAZN.
Instead, when we decide to identify our micro-frontends as a single part of a view we have all the options available (client-side, edge-side and server-side).

Micro-Frontends Routing

After defining our micro-frontends composition strategy we need to decide how we want to route our views.
This time the decision is not mutually exclusive, we can decide to route client-side and adding logic on the edge or having the routing logic on the client or server only.
In this case, my suggestion is to be coherent with the composition part if you have decided to go with a client-side composition, use the app shell for mapping the routing logic, if you have chosen the server-side composition, use the webserver for managing the routing logic.
In the case we decided to go with the edge-side composition we will rely on the URL associated with a page.

In the following diagram, you can easily discover the different routing possibilities associated with a composition technique.

I wrote an article about this topic a few months ago that I recommend reading if you are interested in this topic

Communicating between Micro-Frontends

The final decision is finding a good way of communicating between micro-frontends bearing in mind that are independent units that should be completely decoupled between each other.
When we decide to have multiple micro-frontends on the same page we need to decide how (and if) a micro-frontend should notify when an event or a user interaction happens for coordinating changes in other micro-frontends.
In this case, we could use custom events or an event emitter library (there are many available) so our micro-frontends just emit/dispatch an event and who is listening reacts to that specific event.
However, either we identify multiple micro-frontends per page or we consider a micro-frontend an entire view, we need to decide how a view would exchange data with another view.
We can decide to use a web-storage solution (local and/or session storage) where every time a new view is loaded looks inside the web-storage to find the information needed or a URL routing where the application request to the backend the user state as displayed in the following diagram.

As we have seen, there are some decisions to make when we want to approach a micro-frontends architecture, those will shape the other challenges will be faced alongside a micro-frontends project like how to create a seamless user experience, how to define our CI/CD pipelines, how to optimize our micro-frontends reducing our JavaScript bundles and so on.
However, those 4 decisions made upfront will provide a strong guideline for facing all the other challenges and restrict the number of options to choose from.

Last but not least…

During my last keynote at JS-Poland I introduced this decisions framework for the first time, you can find the slides here (hopefully the recording soon!) with some additional details.

I’d really like to hear your feedback about this decisions framework.
If you feel it may help in your current or future projects if you think something is missing, if you think you have a better way to summarize a complex topic like this one, please leave a comment, I’m eager to read your point of view.

Identifying micro-frontends in our applications

I’ve always spent a lot of time reading, attending conferences, researching different topics and those learnings really helped me shape my career, one of them is definitely Domain Driven Design (DDD).

Let’s take a step back first, why am I talking about DDD?

One thing that always puzzled me in this industry is the lack of learnings from different technology communities, for instance, we can find quite a lot food for thoughts when we examine the principles behind microservices more than focusing on the bare implementation.
On the backend side, there are often practices, methodologies, more in general ideas, that are totally applicable on the frontend too, but often we don’t think how to do it.
Often just taking a step back, understanding why someone implemented a pattern over another, allows us to open up a world of opportunities that we would never think about because “it’s not the standard way to do things”.
Contaminations from different industries or technologies allow us to see the world from a different perspective, creating new possibilities not explored enough (or at all sometimes), giving us the possibility to apply concepts and mental model to our day to day work.

DDD key concepts

Ok, now we can explain why DDD is mentioned in a post where I talk about micro-frontends.
DDD brings on the table some of the key concepts for defining a micro-frontends because it helps our organisation to align the business with the tech side, unifying de facto 2 main areas of our companies: product and tech.
DDD starts with the idea of identifying parts of our application that represents a subdomain of the final application.
Usually, an application is focused on a core domain, for instance, Netflix core domain is streaming movies anywhere at any time, considering the domain is usually a complex proposition, DDD suggests to split the domain into multiple subdomains allowing a company to understand how to structure the company as well as the project.
Some examples of subdomains could be the authentication, customer support inventory management and so on.

Subdomains are divided into 3 categories:

Core Subdomains: those are the main reason why an application should exist, core subdomains should be treated as a premium citizen in our organizations because they are the ones that deliver values above anything else
Supporting Subdomains: subdomains related to the core ones but not key differentiators, those subdomains could support the core subdomains but at the same time are not essential for delivering the real value to our users
Generic Subdomains: those are needed subdomains used for completing the platform and often the companies decide to go with off the shelf software because not strictly related to their domain, for instance, authentication or payments management, more in general anything that is not related to our code business

Inside each subdomain, tech and product teams should identify a ubiquitous language, or rather a way where business meets tech using the same language for identifying functionalities, objects but more, in general, the domain model.

Think about it, how often we speak with a product owner that defines part of an application in a completely different way from the techies!

Ubiquitous language is not a static language, should evolve with the business and the applications running alongside it.
In this way, we would be able to define a domain-model similar to what we discuss day to day with the domain experts, constantly up-to-date.
Let’s take Netflix for instance, I think we are all familiar with this famous streaming platform, a subdomain of Netflix might be the catalogue, inside it we can identify multiple areas with specific functionalities, those could be directly connected to backend APIs related to a subset of the entire application.
In Netflix case, they are using Backend For Frontend pattern (BFF) nevertheless the principles remain the same.

Netflix web platform

In this screenshot, we can identify some components, those might be linked to some microservices like the personalisation service, the catalogue per country, the most popular contents and so on.
Despite the technical integration that could be via backend for frontend, GraphQL, Server Side Rendering and so on, the most important thing to understand is that those areas are all linked to the same subdomain.

Therefore those microservices, as well as the frontend, should be encapsulated in a unique subdomain with its own ubiquitous language.

Following just an example to understand what a subdomain should contain:

An example of subdomain based on the Netflix platform

This is a fundamental step for identifying how to “slice” our application, understanding that the frontend is part of the subdomain allows us to think holistically about our web application.
If we then extend the concept to the infrastructure too, we finally see all the components for developing a subdomain in the hands of a team that can own Frontend, Backend and Infrastructure end to end without too many external dependencies.

Up to now, DDD was applied to the backend layer but not very often to the frontend as well, extending these concepts to the frontend allow us to easily identify our micro-frontends.
I’d like to highlight another important concept, a subdomain cannot (and shouldn’t) be recognised as a component in a page, it’s true that in each UI we can find links or graphical elements related to different subdomains but at the same time we need to understand that they are not standalone identifying a subdomain and they need to have teams owning a subdomain end to end as we are going to see in the next few paragraphs.

Identifying a micro-frontends bounded context

Following the DDD principles, identifying a micro-frontend becomes quite trivial.
Usually, there are 2 main scenarios to deal with on a day to day based: greenfield projects, usually very exciting for any developer but also more complicated because we don’t have real information about our user base and how they would consume our content; legacy projects, where we have a tons of information (if we have diligently tracked our users behaviours using Google Analytics or similar tools) and therefore it’s easier to rationalise a logical identification of bounded context across the entire platform following our users’ behaviours

Having data to consult is one of the best situations we can aim for, understanding the users’ behaviours allow us to easily identify the subdomains of our applications.
Let’s assume we see a huge amount of traffic consulting the landing page, then 70% of those users are moving to the authentication journey (sign in, sign up, payment…), from here only 40% of the traffic subscribes to a service or use their credentials for accessing the service.

Users’ behaviours example in an application

Those are good indications about our users’ behaviours in our platform, DDD would suggest starting from the domain model of our application identifying the subdomains and their related bounded context and having behavioural data supports us on how to “slice” the frontend applications,

Users’ behaviours are invaluable for identifying our micro-frontends.

In the example discussed before, if we think about the technical implementation, allowing 100% of our users downloading only the code related to the landing page will allow them to have a faster experience because they won’t download the entire application immediately and the 30% of users who won’t move forward to the authentication area will have just enough code downloaded for understanding our service.
Obviously, mobile devices with slow connections can only benefit from this approach for multiple reasons: less KB to download, less memory used, less Javascript to parse and execute and a faster first interaction of the page.

Greenfield projects are a bit more complicated to manage, identifying micro-frontends upfront without knowing how our users interact with the platform could result in bad experiences but nevertheless, we have to find a way for structuring our micro-frontends architecture.
In this case, working closely with the product team or the subject experts could make a huge difference.
In my experience, any startup or medium-large organization have always a team or a person that has a clear idea of how the platform should behave, that knows inside out the core domain of an organization.
This person or team is key for understanding how the user should behave and therefore how to identify the domain model of our application as well as our micro-frontends.

It’s essential to understand that a subdomains evolves with the business, never assume that is immutable!

In DAZN we have decided to split our Single Page Application into multiple subdomains based on the data retrieved in the past years, ending up with 5 different micro-frontends with a few components developed by external teams and embedded as dependencies in one micro-frontend.
We identified the following micro-frontends:

. Landing page
. Authentication
. Catalogue
. Playback
. Sports data
. User account
. Help
. Chat

For instance, Playback and Sports Data are components living inside the catalogue micro-frontends, the complexity of those 2 subdomains lead us to assign a team dedicated to each of those subdomains.
Those components are published in an NPM private repository and are treated as an external dependency for the catalogue micro-frontend.
All the others are SPAs or single pages loaded by our client side orchestrator.

The power of local decisions

Working with subdomains allow us to assign a team to a specific area of our application, now stop for a moment and think how powerful could be this concept…
One of the key thing that I’ve always envy to startups is how fast they are capable to move and how quickly they take decisions on architecture, design or UX even.
When they need to take a decision, it’s a matter of minutes or hours but not weeks like in large organizations where we need to have a quorum of people agreeing on the solution.
If we think even further, a startup can react very quickly because they can take local decisions, in their case a local decision is a company decision, but if we extend this concept to medium-large organizations, dividing an application by subdomains allow us to have “multiple startups” inside an organization, therefore, empowering a team to take local decisions will allow to speed up the delivery, reduce the frustration and brings on the table interesting concepts like independent builds and deployments, less external dependencies, less frustration and more innovation.
The outcome of using DDD for identifying a ubiquitous language and subdomains would be creating a cross-functional team composed by frontend developers, backend developers, manual QAs and dev-in-test working closely to their product team/subject expert and being able to take a wide range of local decisions, from product decisions to infrastructure decisions, being responsible of the subdomain end to end.

Obviously, this team cannot (and shouldn’t!) be compared to a remote island where any decision is taken locally, these teams have to collaborate with the rest of the organization using services like architects, cloud experts and other functions inside the organization following the boundaries created by the heads of the technical department.

Organization example where each team represent a subdomain

In the past years, I read a lot about DDD and I found an interesting box inside Domain Driven Design Distilled book that caught my attention and I think is worth to share in this post to enforce the concepts explained in this paragraph:

Bounded Contexts, Teams, and Source Code Repositories

There should be one team assigned to work on one Bounded Context. There should also be a separate source code repository for each Bounded Context. It is possible that one team could work on multiple Bounded Contexts, but multiple teams should not work on a single Bounded Context. […]

It is especially important to be clear that one team works on a single Bounded Context. This completely eliminates the chances of any unwelcome surprises that arise when another team makes a change to your source code. Your team owns the source code and the database and defines the official interfaces through which your Bounded Context must be used. It’s a benefit of using DDD.

from Domain Driven Design Distilled — chapter 2

5 suggestions for dividing your frontend monolith

Last but not least, I think would be helpful having some takeaways of this post based on my experience and what I saw so far:

  1. Gather data: if you have a legacy project you can use Google Analytics or similar services for understanding how your users are interacting with your application, you will find a clear idea how your user base is interacting with your application.
    For greenfield projects, engage with your product team or customer, add GA or similar tools in your web application and via data validate the initial assumptions. Remember, bounded context and subdomains evolve with your business, are not defined once and set in stone!
  2. Talk with the domain experts: invest time with your product team or the domain experts in your company, understand their point of view, their roadmap, how they think to evolve the project, those are vital information for identifying the micro-frontends
  3. Review the teams organization: don’t fall in the trap of defining once the teams and don’t change them anymore, teams, like your business, should be fluid, if you see that following DDD there are some teams crossing multiple subdomains, make the bold decision reviewing the internal organization, the entire business will benefit from it!
  4. A micro-frontend could be a single page or a SPA or SSR: as long you are following DDD for identifying your subdomains, a micro-frontend may end up to be represented by a single page like in the case of a landing page, or a more complex solution based on a Single Page Application architecture or a Server Side Rendering one.
    Components risk being not representative of a subdomain because tightly linked to the container where they are nested, therefore the overlap of multiple contexts could cause more issues than benefits.
  5. Invest the right amount of time at the beginning of your project: designing an architecture upfront is not the best way for starting a project, usually an architecture should work iteratively, therefore we should start designing “just enough” and slowly but steady we enhance the design based on additional information we found engaging with the product team, developers and users.
    When you are identifying the different subdomains of your application invest enough time because this decision could impact how to structure the tech teams as well as how much communication overhead your company is going to spend due to dependencies between teams

Orchestrating micro-frontends

How can we orchestrate our micro-frontends architecture?

Following the previous posts on micro-frontends (1 and 2), it’s time to talk about how to orchestrate micro-frontends.

First of all and foremost, there are 2 schools of thoughts about how a micro-frontend should look like, as explained in the previous article where I was explained different implementations of micro-frontends, there are implementations where a micro-frontend correspond to an area of the user interface, others where the micro-frontend is a SPA or a single page.

When we consider the micro-frontends implementation based on different logical areas of the application (like a header, a footer, a payment form and so on) we would face different challenges like:
Which team would assemble the aggregated view?
How can we avoid external dependencies in every team?
Which team is accountable for an issue in the aggregated view?
How do we ensure that a specific area of the application is not tightly coupled with the parent container?
How can we be sure there aren’t conflicts between dependencies?
Are we assembling at runtime or compile time?
If we decide to create the page at runtime time, is our application servers layer scalable?
Is the content cachable and for how long?
How do we ensure the development flow is not impacted by distributed teams?

And many other questions (technical and organisational) that could make our life way more complicated than how it could be.
Interestingly enough, this approach didn’t provide the expected benefits for Spotify working at scale and they reverted back to a more “classic” architecture based on SPA.

For the benefits of this post, let’s define our micro-frontends as SPA or single pages with a generation made at compile time in order to avoid any possible surprise happening at the composition layer.

Anyway, there are some challenges to face also with this approach, probably the main one is understanding how we want to orchestrate our micro-frontends and it is the focus of this post.
The orchestrator layer could be either on the client-side, server-side or edge-side; the solution depends on how “smart” the orchestrator layer should look like for our applications.

Server-side or edge-side orchestrator

A server-side or edge-side orchestrator would mean that for any deep-link or organic traffic hitting our domain has to be analysed by an application server or an edge solution (lambda@edge for instance), in both cases we need to maintain a map of URLs that correspond to static HTML files (aka micro-frontends).
For instance, if a user logs out from our application we should probably unload the authenticated micro-frontend and load the sign in/sign up micro-frontend, therefore the application server or the code running on the edge should know which HTML file to serve for every URL or group of URLs in the case we are going to work with SPAs.
This technique could work without any problem considering we can change quickly the micro-frontends map directly on the server without any impact on the client-side, but presents some potential challenges, like finding the best way to share data across micro-frontends considering there are some limits of storage inside the browser and doing too many roundtrips to the servers is not ideal in particular for slow connections.
Another challenge would be finding a solution for initialising the application, considering with micro-frontends we split the monolith into multiple subdomains, are we going to initialise the application every time a new micro-frontend is loaded? Are we going to use Server Side Rendering storing the configuration inside the HTML? How do we communicate between micro-frontends? How do we scale our application servers when there is bursty traffic?
Those are some of the challenges for implementing a server-side or edge-side orchestrator.

Client-side orchestrator

Another possible approach could be to create a client-side orchestrator responsible for:

— initialise the application
— sharing the application’s configurations to all the micro-frontends
— load/unload a micro-frontend based on the user’s state
— routing between micro-frontends
— exposing an API for interacting between a micro-frontend and the client-side orchestrator

One of the PROs of this solution is that you have more control over the application initialisation.
If well designed, the client-side orchestrator doesn’t need to change too often, therefore, will be fairly stable.
It provides additional functionality that could be used by various micro-frontends but it’s not domain specific, it’s also a great solution when our aim is to abstract our micro-frontends from the platform they are running on (browser instead of mobile devices or smart TVs).
The main CON is the initial investment in identifying which feature should be handled by this orchestrator because the risk of a big ball of mud is behind the corner, a bug on this layer could blow up the entire application and the implementation of new features, if not well co-ordinated, could slow down other teams creating a cross-team dependency.

In DAZN we opted for a client-side orchestrator that we called bootstrap.

Bootstrap has all the responsibilities listed above plus an additional one related to our use case, in fact, bootstrap is abstracting the I/O APIs of the platform where the application is running on, in this way each micro-frontend is completed unaware in which platform is loaded.
With this technique, we can re-use a micro-frontend across multiple smart TVs, consoles or set-top boxes without the need to rewrite specific device’s implementations, unless the implementation has memory leaks or performance issues.
Bootstrap is served every time a user types our domain in the browser or opens the application on a smart TV, it’s always present and never unloaded for the entire duration of the user session.

DAZN loading flow 

Let’s try to expand further about the bootstrap in order to understand the main ideas behind it:

Initialise the application

Bootstrap should be responsible to set the application context, first of all understanding if the user is authenticated or not and based on the application initialisation we can load the correct micro-frontend.
Any other meaningful information your application needs for setting the context for the entire application should be managed at this stage.
It could be a static configuration (JSON) or dynamic one where an API needs to be consumed, either way, having an external configuration for our frontend allow us to change some behaviours of our system without the need of bootstrap releases.
For instance, a configuration could provide valuable information for the application lifecycle like features toggles, localised labels for the user interface and so on.

Micro-frontends routing

Bootstrap is definitely responsible for routing between micro-frontends, in our implementation, we have 2 routing spread between bootstrap and every micro-frontend.
Bootstrap doesn’t have the entire URLs map of our applications, instead, it loads in memory a map of which micro-frontend should be loaded based on the user status and the URL requested via user’s interactions or deep link.
Those two dimensions allow us to load the correct micro-frontend and leave to the micro-frontend code handling the URLs to manage inside different views that compose it.
A rule of thumb here is to assign a specific second level path for a micro-frontend so it would be easier to address the scope of a micro-frontend, for instance, the authentication micro-frontend should be loaded when the user types mydomain.com/account/*, instead, the micro-frontend for the help pages should be loaded when the user clicks on a link like mydomain.com/support/* and so on.
Inside every single micro-frontend, we can then decide to have additional paths like mydomain.com/support/help-page-A or mydomain.com/support/help-page-B, in this way the domain knowledge would be retained inside the micro-frontend without spreading it across multiple parts of the application.

The main takeaway here is that we have two types of routing in a micro-frontend application with a client-side orchestrator, a global one at bootstrap level and a local one inside the micro-frontend.

Micro-frontends lifecycle

As we mentioned before, each micro-frontends should be loaded via the boostrap, but how?
Single-spa, for instance, uses a javascript file as an entry point for mounting a new micro-frontend.
In DAZN, we took a different approach because using just a javascript file for loading a micro-fronted would have precluded the possibility to use server-side rendering at compile time that was an interesting option for us to provide faster feedback to our user meanwhile they were transitioning from a micro-frontend to another one.

Micro-frontend anatomy: HTML, JavaScript and CSS files

Considering an HTML file is basically an XML file with a specific schema, bootstrap can load and parse the file appending inside itself all the relevant nodes for loading a micro-frontend using DOMParser, a standard interface for parsing XML or HTML strings.
Anything inside the body or head tags could be appended inside bootstrap’s DOM tree.
Potentially, we can also decide to define specific attributes for all the tags we need to append in order to have a quick way of selecting them.
Anyway, the overall idea is parsing an HTML file and appending inside bootstrap what is needed for loading the micro-frontend, therefore any external dependency (like a JavaScript or CSS file) present in the micro-frontend HTML file will be appended and therefore loaded by the browser.

A huge benefit of this neat approach is that it’s not opinionated, anyone can start working on a new micro-frontend without learning the way we decided to deal with micro-frontends because at the end, as long the micro-frontend output results in the Frontend holy trinity: an HTML, a JavaScript and a CSS files.

I captured a video throttling the connection in order to show how the bootstrap appends the DOM elements inside itself, as you will see there are 4 phases:
— identifying the micro-frontend to load,
— load the HTML of the micro-frontend,
— parse it,
—append the relevant tags for displaying the micro-frontend in the page.
It’s a very simple but effective mechanism!

An additional feature added to each micro-frontend is the possibility to perform some actions after and before are mounted or unmounted, in this way the micro-frontend can do any logic for cleaning up any object appended to the window object or any other logic to run in one of the 4 lifecycle’s methods mentioned before.
Bootstrap is responsible to trigger the micro-frontend lifecycle methods and clean the memory before loading the next micro-frontend, this action ensures no conflicts are happening in different or the same versions of a library used by different micro-frontends.

Bootstrap memory and dependencies management

It’s time to deep dive into the micro-frontends memory management, considering bootstrap is loading one micro-frontend per time, as explained in the previous post, and each micro-frontend is not sharing any library or dependency with another micro-frontend, we could end up in a situation where a micro-frontend is loading React v.15 and the next one React v.16.
At the same time, we want to have the freedom to pick any technology and library version inside every micro-frontend because the development team that retain the business and technical knowledge should make the best implementation choice available instead of having constant trade-offs across the entire application as usually happens when we work with a Single Page Application.

At this stage, I believe is very easy to guess the challenge we are facing because any library or framework used by a micro-frontend will append objects on the global window one and in Javascript we cannot directly control the garbage collector but we can facilitate the disposal of an element removing all the references and instances of a given object.

For achieving this goal, an additional bootstrap responsibility is keeping track of any object that is appended to the window object by any micro-frontend and cleaning the window object after unloading the micro-frontend but before a new one is loaded (the joy of metaprogramming in JavaScript 🎉).
Bootstrap takes a snapshot of all the keys appended to the window object and removes them before loading a new micro-frontend, in this way we keep track of what should be removed without duplicating any objects in memory and with a simple iteration of this array we delete any objects used by the unloaded micro-frontends inside the window object.

APIs layer for communicating between bootstrap and a micro-frontend

The last bit worth mentioning is the APIs layer exposed by the bootstrap via the window object.
If you asked yourself how we share data and communicate between micro-frontends, bootstrap is the answer!

Remember that our implementation is based on the assumption we always load one micro-frontend per time and we slice a micro-frontend based on a subdomain of our application, you will soon realise that the data shared across micro-frontends are not happening too often if you work well in the initial session where you define all your subdomains.
Sharing data between micro-frontends is pretty easy, bootstrap shares some APIs for storing and retrieving information accessible by any micro-frontends, it’s up to you deciding which storage is more convenient for your implementation and what kind of limits you wanna add to the objects to store locally.
Considering the bootstrap is a tiny layer written in vanilla JavaScript in between a platform and a micro-frontends and it’s initialising the application, we need also to expose an API layer for abstracting the I/O layer for storing or retrieving information from and to a micro-frontend.
Working with multiple devices require to have different APIs for storing and retrieving files because web storage APIs are not always consistent across all those platforms.
Another important part to highlight is the configuration retrieved from a static JSON file or an API that usually is shared with all the micro-frontends to understand the context where they are running (for instance sharing particular configuration based on the country or languages).

The most important thing when we design the APIs exposed by the bootstrap is trying to be forward-thinking because the bootstrap should be a layer that doesn’t change at every release otherwise you could break some contracts with micro-frontends and coupling the micro-frontends to bootstrap functionalities could jeopardise all the great work done splitting up your business domain in multiple subdomains.

Summary

During this post, we have explored the possibilities for orchestrating micro-frontends, we deep dive into the client-side orchestrator that in DAZN is called bootstrap, in particular, we have seen the benefits and the challenges of this approach and how we have managed to solve them.
In particular, we saw the bootstrap has 3 main responsibilities:

— routing between micro-frontends (load, unload and lifecycle methods)
— initialise the application
— exposing an APIs layer for micro-frontends communication and web storage

One of the questions I received very often after sharing those posts is if and when the bootstrap will be open-sourced, the answer is that we are thinking about that but we cannot commit to a timeline at the moment (that’s also the reason why I didn’t share code in this post, sorry again 🙏).

I really hope you are getting a clearer idea of how to structure your next micro-frontends project if not feel free to reach out, so I can have food for thoughts for the next post! ✌️

Adopting a Micro-frontends architecture

Screenshot 2019-04-06 at 18.19.26.png

Considering the great feedback of the first post on micro-frontends and the questions received about the approach we are taking in DAZN, I decided to share a bit more about this topic.
In this post, I am covering one of the many possible implementations of a micro-frontends architecture.

Despite micro-frontends are a new model for our frontend applications, many companies tried to embrace the principles behind them and they have created multiple implementations for solving their frontend and organisation challenges.

I think is worth mentioning some of them before jumping in how we have designed our implementation, this is not an exhaustive list but it’s interesting being aware of the different possibilities available:

— Spotify uses micro-frontends in their desktop application leveraging iframes for stitching together different part of the same view.
The communication between iframes is made via an event bus that decouples nicely the different part of the application allowing them to communicate without knowing who is going to listen for a message or event.
Also, this approach saves a lot of time on managing the application memory because every time we change the iframe location, automatically all the objects are ready to be garbage collected.

Spotify’s micro-frontends approach

— IKEA decided to implement micro-frontends with a different approach, they are using Edge Side Includes (ESI) mixed with Client Side Includes (CSI), I don’t wanna spend too many words on this technique because it’s extensively covered in Gustaf’s post but it’s definitely another opportunity for generating dynamically the content of our pages and cache the result on the CDN level or client side, depends the approach we wanna take.

— OpenComponents is an interesting framework used by several companies like Skyscanner or OpenTable. OpenComponents is an opinionated framework that is levering the concepts of an end to end components (frontend + backend together) submitted to a register and used for composing an application.
Also, in this case, we can find a lot of information on OpenComponents project website

In between those 3 implementations, we can find similar flavours with some differences used by medium-large size organisations for creating independent and technology agnostic micro-frontends. It’s worth mentioning Zalando or BuzzFeed for instance as other contributors in this school of thoughts.
If we wanna summarise the implementations we discussed till now we can list the 3 different approaches:

. using iframes + event bus
. using ESI in conjunction (or not) of CSI
. using OpenComponents or similar runtime/compile time template systems

The “DAZN way”

As I mentioned at the beginning of this post, there is another implementation to discuss: the approach taken in DAZN.
DAZN is an OTT service available in several countries that streams live and on-demand contents. Our application is available not only on web and mobile but also on smart TVs, set-top boxes and console, and that’s important to highlight because we often face unique challenges and we need to think out-of-the-box for solving them.

Usually, when we start a micro-frontends project, we should ask ourselves several questions and based on the answers facing the challenges related to our decisions, for example:

· do we want multiple micro-frontends in the same view?
· how do we route between pages?
· how do we share data between micro-frontends?
· how do we generate our micro-frontends? Runtime or Compile time?

Let’s try to answer all those questions for understanding the approach we embraced…

Do we want multiple micro-frontends in the same view?
No, we want to have 1 micro-frontend loaded per time in this way we don’t have share dependencies between micro-frontends, every micro-frontend is small enough but not too small, we have full control on the final outcome, it’s technology independent and well encapsulated.
We can potentially work with different versions of the same framework without impacting other micro-frontends or even with different technologies without any impact on the overall application.
We follow Domain Driven Design (DDD) practices for slicing our subdomains and make them really independent mapping the product teams structure and creating a vertical inside a large organisation composed by product people + frontend developers + backend developers + manual QAs + devs in test, and this is very powerful for moving fast, with different speed between teams when is needed in large companies.

Bear in mind that more often than you think, our applications are not entirely consumed by users, for instance, when the user is authenticated, all the code and the dependencies of the sign in/sign up micro-frontend won’t be loaded because we load only the micro-frontends of the authenticated area.
At the same time, when a user is not authenticated, it’s not 100% sure that she is going to finish the on-boarding journey and successfully access the authenticated area of your application, check your stats on how your users are interacting with your application and if you don’t have them invest the right amount of time on creating the right observability with tools like Google Analytics, Sentry, LogRocket and so on.
Remember, micro-frontends are helping a lot achieving the goal of loading only what the user needs and not more than that.

How do we route between pages and how do we share data between micro-frontends?
There are several ways we could achieve that, on the backend, on the edge or on the client side. We choose the client side creating an orchestrator called Bootstrap that has 4 main goals:
· route between micro-frontends
· load and unload a micro-frontend (1 per time, never multiple)
· initialize the application retrieving the configuration
· expose an APIs layer for sharing data between micro-frontends

How do we generate our micro-frontends? Runtime or Compile time?
We prefer to be very predictable with the outcome of our artefacts and we want them highly cachable like a SPA would be, therefore we didn’t take the path of creating anything at runtime, but we prefer generating micro-frontends at compile time, store them on AWS S3 and serve via Cloudfront CDN.
In this way, we don’t have to worry about scaling our infrastructure or unpredictable edge cases happening when we serve our application, we can run end to end tests and performance test before deploying in production having more confidence of what we deploy before being live.

The architecture

In our case, we decided to split the application into multiple subdomains studying upfront how our users were interacting with our web application. For green-field projects, I recommend to deeply understand how your users are going to interact with the application in conjunction with your UX and Product team and follow Domain Driven Design for defining the subdomains and their associated bounded-context.
For the DAZN application, almost every subdomain technically translates into a Single Page Application, but there are some exceptions, for instance, the video player is a component due to the broad scope of that subdomain, then those components are imported inside a micro-frontend as any other library.
Micro-frontends are loaded and orchestrated by the bootstrap, a simple vanilla javascript application embedded in the main HTML page that loads different micro-frontends based on deep-link requests, user status or any request coming from the loaded micro-frontend.

This is how our architecture looks like:

Bootstrap is always available during the application lifecycle

Bootstrap is always available during the lifecycle of our application, it’s responsible for loading our micro-frontends and exposing a tiny layer of abstraction between the device and the micro-frontend.
This detail becomes even more relevant when you target multiple devices and not only web browsers, we have our applications available on many smart TVs, set-top boxes and consoles, all of them have often different requirements and I/O APIs that defers and can be encapsulated at the boostrap level.
In this way, we can run a micro-frontend in multiple devices without the need to change a line of code because the bootstrap is abstracting the platform where the micro-frontend is running on.

If we wanna summarise how the application loads inside a browser we could say:

  1. users request our web application typing our domain in the browser
  2. bootstrap is served
  3. bootstrap initialize the application retrieving some configuration from the APIs layer
  4. based on the initial state and the user request (deep-link or default URL) load the correct micro-frontend
  5. the user enjoys our web application based on micro-frontends 🥳!

Bear in mind that every micro-frontend is independent, therefore we are not sharing components or logic across micro-frontends.
If you think it is a waste of time and effort you won’t believe how much independence every team has got thanks to this decision.

Code duplication is not always a bad practice as we have learnt in the past, often cross-team dependencies and code abstractions risk to be way more dangerous and tedious than creating 3 or 4 times the same component.
We have noticed that spending the right amount of time analysing the user flows and identifying the subdomains lead to way less duplication than expected.
Also, we noticed using micro-frontends, the dependencies across teams didn’t happen too often like in other projects thanks to the initial effort on analysing the project and create meaningful subdomains.
If in your case it’s an absolute must re-using components, there is a way to mitigate the duplication using web components for standardising the component code, with this technique, it could be reusable in combination with any framework, but this is a discussion for another post 😉.

When we started this journey into micro-frontends, for me was very clear that I had to think for the future of the development teams and not only solving the technical aspects.
With micro-frontends, we were able to provide the independence I was looking for without impacting the speed of delivery, each team is owning end to end a specific domain guaranteeing an easy way to add new functionality, fixing a bug or add an improvement without risking to have a knock out effect on the rest of the application or dependencies spread across our multiple dev centres.

Having shared those information several times with new developers joining the company as well as during my talks or online workshops I know you could have millions of questions around the bootstrap, how it loads a micro-frontends, how it shares data and so on.
I will answer all those questions in the next post that will be focused on bootstrap only so follow me for not missing this deep dive inside the micro-frontends world.

If you have any curiosity or question about micro-frontends feel free to get in touch, I’m always keen to help the community as much as I can 😁!

Micro-frontends, the future of Frontend architectures

Micro-frontends architecture

In the past 30 months, I had the opportunity to work on one of the most challenging architectures I’ve ever designed in my career.
The main requirements were based on the speed of delivery, scalability and code quality.
Frontend applications are becoming more challenging daily and achieving those requirements in a company with a massive growth like DAZN was far to be an easy task.

The first step for me was identifying how to achieve those requirements in a meaningful manner, therefore, I started thinking how I can reach those goals in an ideal world and then work retrospectively through the constraints we had inside our company.

The speed of delivery could have been achieved parallelising tasks in multiple teams the real challenge although is having teams independent enough to not be stopped by external dependencies in particular when the teams are distributed and not co-located.

Scalability on the Frontend ecosystem is not only represented by technical challenges but mainly by autonomous teams, too often I experienced the frustration of frontend developers from external dependencies and because they have to maintain and improve a codebase started for one purpose and evolved in a monster becoming unmanageable after some months or a few years of work, ideally we should be able to scale our teams organically and adapting them to the business needs without too much friction, more than being trapped inside codebases that do not really follow the “business rhythm”.

Code Quality is a non-functional requirement that is always aimed by any team and company out there but often, despite the goodwill of each team members, due to pressure from the business, we had to make some hard decisions cutting some corners so the tech debt increases and, without being addressed properly, having a knock-out effect on the entire organization and the teams morale.

On top of those key goals, a personal one I thought was key for the project I was about to redesign was innovation, in the JavaScript community there are plenty of talented teams and individuals that are contributing to open source projects with great libraries, frameworks but more in general solutions, that could make our life easier or even accelerate the time to market of specific feature, ignoring this fantastic ecosystem would have been a technical suicide considering I was working on an architecture for the future that should have remained in the company for the foreseeable future.

For achieving all of these goals I had to think outside the box, leveraging the past experiences and the learnings from successes as well as failures happened in my career.
It’s then that I thought about micro-frontends, following the microservices principles, I was able to extract a manifesto based on what I need to achieve:

DAZN micro-frontends manifesto

Usually, when we design new architecture we need to bear in mind that architecture and technical decisions are not affecting merely the code and our technical teams but also the entire organization we work for, therefore is essential understanding the impact of those choices across our company.

If you wanna learn more, I summarise this incredible journey in this talk with my colleague Max Gallo during the last edition of Frontend Developer Love Conference, the feedback at the conference was really positive, but I decided to use this platform for understanding what other people think and create a genuine discussion around a topic that is going to change the future of our Frontend applications: micro-frontends.

Enjoy the talk and feel free to comment or ask any questions, I’d really like to gather the experience and common questions/doubts of the community around micro-frontends doing my best to answer them all.

Last but not least, if you wanna learn more on micro-frontends I warmly recommend joining me the 26th April in the 3 hours online workshop organised in collaboration with O’Reilly Media

A night experimenting with Lit-HTML…

This morning during my commuting time I read a post on Lit-HTML and this templating library intrigued me at the level that I needed to experiment as soon as possible, so I took a night off because I was curious to see this new approach in action.


DISCLAIMER: If you are an expert on Lit-HTML I beg you pardon if I didn’t report all the latest on lit or some information in this post are not up to date 😅 but if you are a curious like me 🤠, you may have found the right place understand what Lit-HTML is and why I was excited to try it 😇.


What is Lit-HTML?

Lit-HTML is a blazing fast template library that will be used for the new version of Polymer (v 3.0), it was presented during last Chrome Dev Summitin San Francisco and I warmly suggest to invest ~30 mins of your time watching the following talk for getting an idea of the library:

If you don’t have 30 mins right now here a summary of what lit-HTML is doing.

Lit-HTML doesn’t use Virtual DOM like the latest trends in many UI libraries like React or Preact for instance, but instead is using web standards to generate and update a UI component.
In fact, this library uses the <template>tag and ES2015 tagged template literals for generating a DOM node.
With this approach, lit-HTML is capable to analyse the template literals and update only the mutable part maintaining the static bit unchanged increasing the render performance compared to the virtual DOM approach.

To provide an idea of how lit-HTML differs from a VDOM library I created these animations so you can immediately see the work done by one and the other library for updating a DOM node value and an attribute:

with Preact (or React, both behave in the same way):

with lit-HTML:

As you can see, lit-HTML heavily optimise the updates just recalculate what effectively should be re-rendered instead of re-rendering the entire node, this behaviour is highlighted in the first h1 tag of our example: Preact is re-rendering the entire node including the text that is static by design (“Preact” word was static, the number instead is a random one I used for causing a DOM update), instead lit-HTML splits the string in what is static and what is not so it can update ONLY what potentially could change without the need of re-rendering anything else.

If you are wondering what is the black magic behind lit-HTML, I can summarise it in this way: JUST WEB STANDARDS!
Surprisingly enough, lit is not using anything too complicated but just web standards, when we define a template to render with lit-HTML we write a component like that:

As you can see it’s just a function returning a tagged template literal, the tag correspond to the word html provided by lit-html library.
Tags in templates literals have the characteristic to manipulate the template before being returned, in fact the tag is usually a function that is intercepting the output of a template literal before being returned.
The html tag, provided by lit library, is analysing the template before returning it to the render function used for updating the DOM, if we output on console how our template becomes before being rendered, we can see that the html tag is performing an analysis for dividing what is static, what is dynamic and creates an array of raw data:

For benchmarking lit-HTML, I created a simple test with some random HTML elements updated every 500ms.

Looking at the performances, the node values or attributes update or the subtree update inside a template is incredibly fast compare to the VDOM approach.
This is noticeable also with not nested components like the ones above, I run several tests on them and this is the outcome:

These data are reporting how long did take on the average of several DOM updates.
We can see in the image below that sometimes Lit-HTML was even faster than the values inside the table and sometimes a bit slower, but comparing with Preact (and also React because I tried both), Lit-HTML is really consistent time wise, the discrepancy between an update and the other is really small.
Lit-HTML is definitely loosing against Preact or React on the first time render, in fact I noticed that Lit-HTML is on the average 60–70% slower just for the first few renders, after that is blazing fast compared to the VDOM one.
Also, after 10-15 mins of keeping the test up and running, I noticed the 2 components weren’t on sync anymore and apparently the Preact one was a tick behind the lit-HTML one.

It’s worth mentioning how I was able to retrieve these values so you can try your own tests as well if you like.
After creating a Preact and a Lit-HTML component, I used the performance APIs in the following way:

  • Before returning the html to render I added the starting mark with the following code:
    performance.mark('litStarts’)or performance.mark('reactStarts’)
  • Then, using the MutationObserver object I observed every change to characterData, childList or subtree calculating the time it took to update the DOM with the final mark andmeasure method from performance APIs:

Let’s observe now how a lit-HTML component is created, the 2 essential parts are the html tag used or analysing the template literal and the render method used for updating the DOM, this is a simple lit component:

The render method has to be called every time there is a template update, in order to do that we can create our own logic via setInterval or requestAnimationFrame or any other way will trigger the render method after changing a value inside the template (proxy or reactive programming could be other 2 interesting methods to try).
A more in depth explanation could be find reading this article: A bit about Lit-html rendering.

Luckily, lit-html is integrated in the next version of Polymer (v3.0) therefore we won’t need to spend much time wrapping this template engine inside custom code for creating our components library.
Bear in mind, as highlighted in the Polymer repo, LitElement is not ready yet for production but we can start experimenting with it.

LitElement is currently in development

Considering Lit-HTML is a standalone template library, if you are not comfortable on using polymer you can always create your own components library 🤩🤟 and integrate it with your favourite state management!

Other online resources

Before finishing this quick article, I thought would be useful sharing additional resources for understanding a bit better lit-HTML, hopefully you will find them useful

Geo-routing, A/B testing and dynamic configuration with Lambda@Edge — part 1

Working at the edge is one of the fantastic opportunities offered by Amazon and AWS Lambda is the key component for enhancing our infrastructure on the edge.
More recently many other vendors started to offer similar services like Cloudflare with edge workers for instance, in general many CDNs providers are looking to add a similar service like the AWS one.

Lambda@Edge introduction

Lambda@Edge provides you the possibility to extend the behaviours of a request or response directly on the edge.
This paradigm, in conjunction with the serverless one, can provide a great flexibility in structuring our applications and it can prevent that too many requests hit our application servers executing operations directly on the edge like headers manipulation, access control, redirection rules, images manipulation and so on.
In order to work with Lambda@Edge in AWS we just need to setup a Cloudfront distribution in front of our infrastructure, the Cloudfront distribution will allow us to setup our logic on the edge because we are able to intercept the 4 states of a request and interact with them.
In fact Lambda@Edge can be triggered at:

· viewer request: in this case the lambda is triggered when Cloudfront receives a request from a client
· origin request: the lambda is triggered before Cloudfront forwards the request to origin
· origin response: this state happens after origin replies to a request and Cloudfront receives it
· viewer response: the last state is triggered when Cloudfront forwards the response to the client.

Cloudfront is a global CDN therefore Lambda@Edge is triggered on any edge available across the world.
This means that independently from the region we set our data centre, we can manipulate or serve responses on the edge before they even arrive to our application servers.

I spent some time working on a spike for handling 2 specific features, geo-routing and a/b testing, in the mix I tried also to dynamically retrieve configurations parameters for the Lambda in order to avoid code deployment for every change I needed to do.
I’ll share the results of my spike between 2 posts, this one talks about the initial thoughts, goals and results achieved with Lambda@Edge, the second one will be more technical and I’ll explain how I configure Cloudfront, the Lambda code and setup and the overall setup for interacting with other AWS services on the edge.

Spike goals

Before we start I think is valuable understanding what I wanted to achieve with this spike, the goals are:

1. geo-routing a request to a specific static file stored in S3 bucket based on user country
2. A/B testing different applications serving always maintaining a sticky session per browser so a user always interacts with the same version
3. The previous 2 goals shouldn’t significantly impact the response time, Lambda@Edge has to be executed redirecting the user to the correct static file in 50ms or so
4. “bonus goal” is the possibility to dynamically apply different configurations without redeploying the Lambda code and without impacting too much the response time

Lambda@Edge configuration and limits

All that glitters ain’t gold! Lamba@Edge has several limitations that we need to be aware of before taking it in considerations.
In order to achieve the geo-routing we need to receive from Cloudfront the country viewer header, that will be used to determine where the request is coming from.
This header is received right after Cloudfront forwards the request to origin, we won’t receive at the viewer request state, therefore our Lambda has to be triggered as origin request.
Bear in mind that Cloudfront removes all the headers if not whitelisted for increasing the response cachability, so when we configure our distribution we need to whitelist some specific headers or all of them, depends from our needs.

Lambda@Edge is a particular kind of Lambda with different limitations than the one we are used to work inside an AWS data centre.
First of all the Lambda@Edge has to be created in North Virginia only, we can associate only a numeric released with Cloudfront and not the $latestversion.
When we debug our Lambda@Edge we need to remember that all the logs in Cloudwatch won’t be available in North Virginia only but in the nearest data centre from where the Lambda was executed, for instance I’m based in London therefore during the spike, all the logs on Cloudwatch were available in the London AWS data centre only.
Finally we have also some soft limits to take in consideration like max memory associated to our Lambda@Edge, concurrent executions and so on, you can find them in the image below:

An important thing to remember is how to debug our Lambda@Edge, luckily AWS thought pretty well about this point and they provide a way to simulate an origin request customising the parameters to send it directly from the Lambda console:

Geo-routing

The Geo-routing feature was really easy to achieve considering that Cloudfront provides everything we need out-of-the-box.
After whitelisting the cloudfront-viewer-country header we can receive in our Lambda@Edge the country from where the request was coming from and we can apply the behaviour we need for that specific country.

After configuring Cloudfront distribution properly we can think about describing our behaviour in the Lambda@Edge, in this case I used Node.js for defining the logic:

These are the headers we are going to receive from Cloudfront:

As we can see the implementation is really easy despite the APIs that could have been designed in a more “friendly” way, but I appreciate they are very extensible and flexible allowing the introduction of new features and maintaining retro-compatibility.

A/B testing

For A/B testing the best way to achieve that on the edge is using cookies after select the static file to serve, also in this case we need to configure properly the Cloudfront distribution for forwarding the cookies to the Lambda@Edge:

In this spike for a specific country I was able to redirect a certain percentage of users to a static file and all the others to another one.
This mechanism becomes very handy when we want to test new versions of our web applications, so we can do a canary release and see how the new version behaves compare to the previous one.
Combining A/B testing functionality with the geo-routing feature we can deploy a new artifact in a specific country with a small percentage of users redirected to it, leaving the majority of our users consuming the previous version.
Considering we are using cookies, bear in mind that all the latest version of the browsers allow to store a cookie before doing a redirection but if you are targeting older browsers like IE 10 or similar it’s better to give it a try.
The same concern is valid if you have in your logic multiple redirections in place.

Dynamic Configuration

The bonus feature for the spike was retrieving a configuration for the URLs and the percentage of users we need to redirect on a region basis.
In this case I tried a couple of solutions, the first one with DynamoDB and the second with an S3 bucket where I stored a JSON file.
First of all I need to admit I was surprised that I was able to access a Dynamo table from the edge considering Dynamo is not global like S3 or Cloudfront, so I decided to give it a go.
I structured my table with an ID for each single information I wanted to make dynamic (the URL of an experiment, the percentage of users to redirect to a static file or the other one…) and then I tried 2 approaches, one using scanmethod and the other using get method.
Using scan was slightly slower than using get method but in any case I was over 50ms for Lambda execution therefore DynamoDB wasn’t a viable option for this use case.

I then tried a simple JSON file stored in a S3 bucket, in this case I was able to quickly configure my Lambda retrieving all the parameters I needed for changing URLs or percentage of users redirected to a specific experiment without redeploy the Lambda code.

This could look a small win but you will understand soon that for deploying a new version of a Lambda@Edge we need around 15 minutes before it’s fully propagated across the world.
Retrieving the parameters from an external file allowed to change the key part of the script in a matter of seconds, just the time to make the change in the JSON file and upload on S3.

Considering the operations we usually handle on the edge are very delicates for our infrastructure, having a mechanism to quickly change the behaviour of our logic is fundamental in particular if you are running a B2C service like in my case.

Performances

I didn’t mention in the post any metric because I wanted to gather them in a paragraph for easily compare them, so I created this table related to the executed time in each test I did for achieving the full functionality.
In the table you will see 4 scenarios:
. without any external configuration
. retrieving a JSON file from a S3 bucket
. using DynamoDB with the get method
. using DynamoDB with the scan method

I’d like to add a bit of more context just to make sure you understand how these metrics were retrieved.
DynamoDB was created in North Virginia and the Lambda was running from Europe, I didn’t try yet working with Global Tables but that will be part of a new spike I need to do in the next month, Global Tables could effectively change the execution time of the Lambda but we’ll see.
The execution time for each lambda was gathered from Cloudwatch running each scenario at least 10 times and providing the average you can see in the table.

I leave to you the considerations on the different methods used during the spike.

Useful resources

Following I gathered some useful resources for starting your journey… on the edge

Lambda@Edge limits
Lambda@Edge restrictions
Lambda@Edge pricing
Cloudfront and Lambda@Edge
Routing at the edge tutorial

Wrap up

As we have seen in this post, Lambda@Edge can become very handy for many situations, in particular for alleviating the load of our application servers.
The spike described above is just one of the many possibilities that Lambda provides on the edge, searching on the web you can find interesting tutorials like image manipulations or JWT validation on the edge, this could be just the beginning of a new way to structure our applications providing better performances and easy configurations for our web applications or APIs.
If you are wondering what would be the cost of using Lambda at the edge I leave you with this cost scenario available on the AWS website:

In the next post I’ll present how I set up Cloudfront, S3 and the Lambda code for achieving the goals of my spike, keep an eye on my social accounts 😉