MMS • Ruben Casas
Casas: I wanted to show you something really cool I found. This is Piedra del Penol in Medellin, Colombia. It is a huge monolith. It is about 200 meters tall, and it weighs about 66 million tons. There is a really cool fact about this monolith. My favorite fact is that to get to the top, you have to go and climb 659 steps. That’s a lot of steps. What do you get when you get to the top? You get this amazing view. You might be wondering, why am I showing you this? First, I wanted to show you that monoliths can be cool, but also, monoliths can be really painful. Hopefully, this presentation is going to give you a lot of the tools to identify how to move away from a monolith into more distributed architecture. I hope it doesn’t feel like climbing nearly 700 steps. I hope this presentation doesn’t feel like that. At the end, you get an amazing reward and a lot of things that you can take back to your companies.
Outline, and Background
The name of this presentation is Micro-frontends: The evolution of frontend architecture. What can you expect from this talk? This is going to be a journey through the evolution of frontend architecture. You will get answers like, what’s the difference between monoliths, monorepos, and micro-frontends? Do I need micro-frontends? What are the risks of distributed systems? Also, there will be a lot of practical advice on how to embark on your distributed system journey. My name is Ruben Casas. I am a Staff Engineer at Postman. I also have a master’s degree in internet and distributed systems. Distributed systems is exactly what we’re going to be talking about.
Problem: Software Has a Tendency to Grow
There is a big problem with software. Software has this annoying tendency to grow. That’s like the natural state of software, things just get too big at some point. The problem is, when things get too big, when things start growing, there is a point where things start to break. The problem where things start to break is, what do we do about it? We as engineers, we love solving problems. We have to do something about it when things are breaking. That’s why a lot of companies have started this journey into a distributed architecture, and they want to implement a distributed architecture. However, there is a problem. This happens. We thought it’s going to be easy. We think that we can embark into a distributed architecture, and we thought, this is going to be easy. Let me tell you something, it is not easy. Distributed systems are really hard. Also, there is another problem, and it’s like, halfway through the journey into distributed systems, we realized that we don’t even know why we want the distributed systems in the first place. The problem that we wanted to solve is still there but there is just a big difference. The problem is still there, and the difference is, now your problems are distributed across multiple layers out there. This is a big problem. Before embarking into a distributed system journey, I think it’s very important to examine all your options. I’ll give you a pro tip. Please make sure that you solve the problem that you wanted to solve at the beginning. How do you solve that problem? You can’t look at the options on how to solve that problem if you don’t know what the problem is, in the first place.
Let’s define, what’s the problem that we need to solve. Do you have a problem? Let’s start there. Ask yourselves, do we actually have a problem or are our systems just working fine? If you don’t have a problem, you don’t need a distributed architecture. I think if you’re doing fine, just save yourself the complexity. This is a very informative talk, but it will probably save you a lot of problems and complexity. That’s probably not the case. There are a lot of problems in software. The tendency of software is to grow, but also to get complicated and have problems. When we have the problem identified, is that we can also have the right incentive. When you start from the problem, you have the right incentives to solve that problem. I am not here to tell you that you should go and implement a distributed architecture, or micro-frontends are the best thing in the world. Because that’s not what I’m here to do. I want to show you how to implement the best architecture to solve your problem. I want to show you how to reach that outcome. I’m not going to be focused on a particular tool or architectural pattern. What’s that outcome? What does that outcome look like? The best outcome out of all of this is that we are going to be fixing the problem that we have. We hopefully are going to fix it without making even a bigger problem in the process. Isn’t that the dream? We solve the problem that we wanted to solve, and we just don’t create any more problems along the way. That usually is not the case.
Symptoms of Problems
Let’s start trying to figure out if you actually have a problem. In order to figure out if you have a problem or not, we are going to be looking at some symptoms. You might relate to these things. Let’s start to look at the symptoms of, if you have a problem, what that problem looks like. First of all, your applications are very unstable. Application instability is one of the symptoms. Applications become really fragile. It’s something that people call the butterfly effect, when you make a change that is completely unrelated, a small change here, a small adjustment there, and they could cause huge regressions. The application starts becoming really fragile. When the application becomes fragile, it also starts to lack confidence, like when you send a new version, when you ship a new version and deploy a new version, you don’t have confidence that you didn’t accidentally break something else in the process. There is also a lack of failure isolation. Something that you moved and changed here might break something completely unrelated. Because of the lack of failure isolation, just a small component could potentially take down the entire application. It was a small element of the system that you didn’t even think that was that important could potentially take down the entire application. That’s the first symptom, application instability.
Moving on, we have exponential growth. Exponential growth is probably a good thing. We want our businesses to grow. We want our applications to grow. We want more developers, and that’s good for business. Growth is part of the process. A company that doesn’t grow is in trouble. The problem with growth, especially when it’s exponential growth, when you are hiring a lot of developers, when you are writing a lot of features, and you end up with increased numbers of lines of code and developers, is that things that used to be really fine and work well when the application was small, and the teams were small, just suddenly start becoming really complicated, and things start to break. The CI/CD pipelines become slow. The deployments become really slow. You have difficulty scaling the technology and the organization. Exponential growth will just start revealing some of these issues as you start growing.
Another symptom, and this one is very important, is organizational issues. The organizational issues are mainly why people want to move into a distributed architecture. The technology is probably not the main reason, it’s usually because the organizations grew so big, then you start having a lot of problems like lack of team ownership, people stepping on each other’s toes. New developers require a lot of context to come and contribute. It is really hard to make changes because you don’t really know how the code works and how everything works. There are really steep learning curves and really overwhelming code bases. You can imagine when you started at a new job, and you start and you are presented with this huge code base and you don’t really know what to do, and you need to learn the context of a, b, c, and d before you can make a change, those were the types of organizational issues that are moving people towards looking at alternatives for a distributed architecture.
The Distributed and Decoupled Spectrum
We have a problem. If you do have a problem, let’s investigate, what are the ways we can solve the problem? The thing is, there is no one single way of solving this problem. There are many ways of solving this problem. At least we have defined that we have the problem. There is another big problem and it is that people like to go from 0 to 100, and try to solve this problem, going from a monolith to a fully distributed architecture. Zero to 100 without actually going through the steps of what the problem is for us, or exploring all those alternatives in the middle. A fully distributed architecture is a very complex system. If you go from 0 to 100, to a fully distributed architecture, you will encounter even more problems.
To illustrate this, I have created what I call the distributed and decoupled spectrum. It’s a diagram that will show you what steps you can try before going into a fully distributed architecture. Here it is. Let me present it to you. This is the distributed and decoupled spectrum. We have the monolith. We have modular monoliths, integrated applications, and finally, micro-frontends. We’re going to be exploring each one of these steps, and the pros and cons of these different types of architectural patterns. We will choose which one is the best one for you. Let’s do it. Let’s start with the humble monolith. The monolith is the traditional old school monolith, which is basically how most applications start. Most applications start as a monolith. If you don’t think that you have a particular architecture, you probably have a monolith. You’re using a monolith. The definition of monolith that I want to clarify here is mainly as a single unit of deployment. You deploy the whole code, including the frontend, backend, and database. We will explore a little bit more about this. It’s a single deployment unit. That’s why I like to call it the monolith. The problem with the monolith is they have this bad reputation, like people have associated monoliths with legacy code, with bad patterns, with things that don’t work and with things that are legacy and old. However, monoliths are fine. Some monoliths just work fine, and they could scale to millions of users. There are a lot of companies that are built on monoliths, and for them, it works fine, or at least they seem to manage the problems a little bit better. They get a lot of hate. Actually, the problem is not the actual technology, it’s that applications start to grow, then the monolith might not be enough for what you want to do, and how your organization is evolving.
Types of Monoliths – Full-Stack Monolith
What are the pros and cons of the full-stack monolith? The pros of the full-stack monolith are that it is easy to develop. It’s how you get started. Code management is really easy in the early stages of a project. Everything is in one place, easy to find. Easy to modify because everything is in the same code base. There is not a lot of mental overhead here, so you can find everything and you can deploy and you can do things really fast at the beginning. What are the cons? What happens? The cons are, the development gets really slow in the later stages of a project. At the beginning, everything was fine. Then as soon as you start adding more developers, and as you start adding more features, and the project gets older, you start having issues with speed. There is also a problem with scalability. There is a big difference between the full-stack monolith and microservices and frontend monoliths, in terms of scalability that I’m going to discuss. Mainly, the problems with the monolith are that everything is coupled. There is a lot of things that are just intertwined. There is lack of flexibility. At some point, also, because the monolith is so large, and there are so many lines of code, then the code becomes really hard to reason about. There is a lot of mental overhead. Those are the issues that we described at the beginning when we start looking to a code base, and that code base is really convoluted, and you don’t know where to find things, and everything is mixed together. This is the full-stack monolith.
There are some examples. I’m going to be doing pros and cons and also some examples. The examples of full-stack monolith, your traditional web frameworks like Ruby on Rails, and Django, PHP. Any framework that has everything included: frontend, backend, database, that’s your full-stack monolith. There are some companies that are using this pattern of a monolith. There are many. The problem is, it depends. Stack Overflow is still using a monolith, like a traditional monolith where they have everything, including the database, and their own data centers, and they have a monolithic architecture there. There was a blog post recently just talking about how they manage to scale Stack Overflow with a monolith. Also, GitHub. GitHub is an example. The thing with GitHub is it’s not black and white traditional full-stack monolith, there are some changes that they have been making. It was like the traditional example of a full-stack monolith using Ruby on Rails.
The Frontend Monolith
We go into the next part of this, which is the frontend monolith. The frontend monolith became a thing because of the advent of microservices. The backend team just decided to have a detour and say, I’m going to do my own thing, see you there. We are going to do our microservices. Then the frontend just remained as a monolith, as a single unit. As companies started to separate the backend and frontend, the frontend didn’t really catch up, they caught up with the trend of splitting things into individual services, and we ended up with the frontend monolith. You have this particular flavor of monolith where single page applications and the API-plus model where you communicate to backend services through APIs. Basically, single page applications make the frontend monolith a more popular choice, because we have everything in the backend that you can communicate through APIs. We have single page applications, which made the frontend monolith pretty popular. From now on, if I talk about the monolith, I’m likely referring to the frontend monolith, because at the end of the day, this is a talk about a frontend track. I’m going to be talking about the frontend monolith, if I say monolith. Unless there are some exceptions, I’m talking about the frontend monolith.
The Meta Framework
Let’s make a stop here. There is this merging trend, lately, that I have seen and people are asking, are the monoliths cool again? These full-stack monoliths or frontend monoliths, are they cool again? The reason people are saying that is because there is a rise on the new frontend monolith type, aka Meta frameworks. There is this rise of Meta frameworks. The Meta frameworks, you will encounter your Svelte, Remix, Next.js, and all of these Meta frameworks that are trying to make the monolith cool again. It’s not just monolithic architecture, you could have a more distributed with these frameworks. The principle is that they provide you with out of the box features like server-side rendering, and bundling, authentication, routing, all of this. Also, they have been doing something really interesting, which is, they’re bringing back the backend closer to the frontend. It’s a frontend framework, but they are saying, actually, let me have some API routes here. Let me connect to the database and run just server-side code only and have your loaders just connecting to them. You can also have the flexibility to connect to APIs. It’s not as restricted as the traditional full-stack monolith where you just have a database connection, and that’s it. These ones are a bit more flexible in terms of where the data comes from. You can decide to just start with the backend included in there. It’s so modular that you can extract it away, and move it into microservices later on. Then you just switch that interface, which is communicating to APIs. This is a really cool trend that is happening today, which is the Meta frameworks. They are bringing them back, and there is improved modularity here.
This is all great. What are the cons of Meta frameworks? Is it still a monolith? Is it still one single unit of deployment? You will have all these disadvantages of scaling your organizations and scaling your companies, because your monolith still has all these issues that we mentioned before. The difference is, obviously, you have more tooling, better user experience, and slightly improved modularity, but you still have these problems. Monoliths start to crack and break when you add more people. The problem is not actually the technology. The problem is more about organizational issues. Organizational issues are probably the main reason people want to move into a more distributed architecture. Monoliths are fine, but they’re mainly good for small teams, or solopreneurs. People that can actually manage the organizational issues better. It could be a larger team, but if they can manage that, then they’re fine with the monolith.
The Modular Monolith
What’s next? Let’s say we have a problem with the monolith. We can’t really scale our teams. We have too many issues that we described at the beginning, so we move into the next phase, which is the modular monolith. As monoliths are getting bigger and growing, they start getting out of hand. Some companies have been doing this, which is breaking apart some structures inside the monolith and making them more modular. This is why we call it the modular monolith, which is basically separating concerns out of the code and making it modular. Still, it’s a single unit of deployment. The good advantage of a modular monolith is that you are getting a little bit more modularity, really good boundaries, but you don’t have all that complexity of maintaining multiple units of deployment. You avoid a little bit of complexity there. The pros of a modular monolith are, more scalable. They are less complex compared to a fully distributed architecture. There is better code organization. That’s the point of a modular monolith is that you can organize the code in different sections that are easy to reason about. There are some enforced boundaries. Not a lot, but there is a resemblance of some boundaries. Those are the pros of the modular monolith.
Now, the cons. Still, it’s a single unit of deployment. You have to deploy the entire thing to get anything to your users. That is probably one of the main differences, and you still have that problem. If you had a problem with individual deployments with monoliths, you still have it with modular monolith. Modules are not fully independent. Modules resemble a little bit of modularity, but they’re still attached to a lot of the things that come from the monolith. They’re not fully independent. It’s still a huge code base. If you have a modular monolith, you still have these problems where you have people coming to your team, and they don’t know where to find things, because still, everything is in one code base and it’s probably huge. There are a lot of features in there. Examples of modular monoliths. There is an article that Shopify wrote called, “Decomposing the Monolith,” and they explain how they made their Ruby on Rails monolith into a modular monolith to solve the problems. They didn’t want to go to microservices all the way, they wanted to just split them. You can also have a modular monolith that is a frontend modular monolith, or a backend modular monolith. You can still have that separation between the frontend and backend. Then the key is that you make the insides of that monolith more modular, and having some scope, and encapsulating your features and your code a little bit better.
What’s next? This is a really hard to describe one, and this is what I call integrated applications. For example, monorepos fall into this category, maybe. Monorepos are here in many different steps of this architecture. There are more modular applications that are a bit more independent, could be composed at build time, or they could be composed on a URL. For example, you give a section of the URL to this application. Then when you deploy, they come together at build time. Modular monolith and integrated applications are a little bit interchangeable. They are very similar. The key difference is how independent these smaller modules are. In an integrated application, I think we are going a little bit more independent, where you could potentially extract away those applications without having to do a lot of work. They are really self-contained units, but they’re still deployed as a single unit, and they come at build time.
Finally, we arrived at micro-frontends. This is a fully distributed architecture. Micro-frontends are at the far end of this spectrum. They are completely independent. They’re fully independent. That’s the key definition of micro-frontends. They’re 100% independent applications. They don’t depend on the monolith or a container or anything. They could deploy it as an individual app. There will be multiple units of deployments. The key here is also you don’t deploy one thing, you deploy really smaller pieces of the UI into these units of deployment. The key is obviously the composition and modularity. Micro-frontends are about just slicing the application really, into self-contained, business domain-driven applications. That will provide you with a lot of team autonomy along the way, because a team could own one micro-frontend, and that part of UI.
There are a couple of types of micro-frontends. The first one is build time composition. The second one is runtime. Let’s explore those two. Build time composition micro-frontends, you can build and package your app, but you don’t know when it’s going to be composed and served to the user. I can deploy independently, but something like a shell or a container will be deciding where to package and how to compose them, but they are still independent. There will be some versioning, so build time micro-frontends will usually have a versioning system. You can think about build time micro-frontends similar to npm packages, that you can just package, for example, a header or a footer, and then you deploy them. Then only until you get the application consuming the header and the footer that have been deployed independently, at that point, you get the micro-frontend sent to the user. An example of this as well is there is a company called Bit.dev. They have introduced build time composition on micro-frontends. The only problem with that is it’s very granular, so they go down to the really button and design system. Those are examples of build time micro-frontends.
Runtime micro-frontends. Actually, if I say micro-frontends, I’m likely referring to runtime composition micro-frontend. This is mainly the main type of micro-frontends. It is that, when you refresh your browser, you just got a new version. There was no deployment, things get composed at runtime. The users, we just get new versions of applications. There is no single deployment. The composition happens at the user level. The user will get one version of one application, depending on what is enabled for them or not. They could be built and deployed. Then only until I switch this version to the particular user, there is no deployment of the entire application, I just get a new version. They are fully independent deployments. There are no builds needed. What I mean by that is you don’t have to build a micro-frontend. I can build it today and then I can deploy it tomorrow to one of the users, or roll back. Fully independent. The examples I have, there are some tools that you can use. Single SPA and module federation are some of the main tools that can help you with runtime composition micro-frontends. There are many other ways you can do that. You can do server-side composition, Edge time composition. There are many ways that you can achieve it with runtime. This is probably the simplest way, which is using module federation or single SPA.
Let’s just have a little quick detour on code organization, so micro-frontends in terms of where you have the code. There are two types. One is the multiple repository micro-frontends, where you have one repository per micro-frontend. This is good. There are some pros and cons of having multiple repositories and one repository per micro-frontend. It is that, you have fully independent code bases. That’s really good. You have single responsibility principle. You know that this is in charge of that. Then you have particular repository. Completely separated. The cons are, obviously they’re really hard to track. There are many repositories. I need to know where to look for the repositories. It’s also a bit harder to enforce and keep governance. There will be really hard to track code styling and tooling, you need to create libraries and things that are shared. There is another type which is micro-frontends with monorepos. Monorepos and micro-frontends work really well. You could potentially include your micro-frontends in a monorepo. The pros is, you have still independent applications, but they have cohesion so you can share some code and some libraries. They’re easier to track. They’re easier to manage. Easier to enforce governance because everything is in one place, so you can find the code. The cons are, they’re really complex to set up. It’s been getting easier lately, but they’re complex to set up. The only problem is they could potentially introduce accidental coupling if you’re not careful and you don’t set your boundaries properly.
Distributed Systems and Architecture
Here we are, we finally have arrived to the distributed architecture. Our distributed architecture looks like this. We have, from the monolith to micro-frontends. The more to the right you go, the more decoupled your system will look like. On the left, my monolith, it doesn’t have a lot of decoupling. Everything is in one place and coupled. Micro-frontends is a fully distributed and decoupled architecture. Also, the independent deployability, so the numbers of deployment units. On the left, we have a single deployment unit. As you move to the right, we end up with the micro-frontends, which is multiple deployment units. Once you go to the right, the number of people and teams tends to increase. Also, if you do that, the complexity also tends to increase.
Did we forget anything? Do we have anything missing? Yes, there is one more thing missing, which is the distributed monolith. This type of architecture is an accidental architecture, which is called the distributed monolith. Once upon a time, I was working with a senior engineer, and that senior engineer just came and said, actually, this micro-frontend architecture is really complex. We’re having a lot of issues. The first issue was like deployments are taking 5 hours plus, we have 70 modules of micro-frontends. If something breaks in the middle, we have to roll back and start again, and that potentially will take 5 hours to roll back. I was like, what is going on? What are you doing? This is insane. The point of micro-frontends and distributed systems are to solve these problems, not to make these problems worse. The downside of the distributed monolith, which I just described, is that you could end up with all the complexity, the downside of monolith, and the downsides of distributed systems, and you’re not solving your problem, you just ended up with a system that is really complex. This is one of the main risks of distributed systems and architecture.
Some of the other risks include complexity. All organizations are different, so your implementations will be different. Don’t expect something that is really normalized. You will find really a lot of edge cases in a distributed architecture. Also, pushback. You will find pushback. Microservices, micro-frontends, and distributed systems are really hard. Consistency especially in the frontend, how you make something that is distributed have that visual cohesion and making sure that things look the same, even though in the background, they’re made up of multiple things. Also, incorrect splitting. In the story I just told, the splitting part was probably one of the factors. They had too many micro-frontends, they had 70. They probably sliced them in the wrong place, which means that they are still coupled. That’s why it ended up with this problem with too many micro-frontends and a distributed monolith.
The benefits of a distributed architecture are a lot, are huge. You can have team autonomy. Faster time to market because you can deploy smaller changes to production. You can scale your teams. Also, the scaling, mostly, for micro-frontends is organizational. I know with microservices, you have the scaling of services, actual horizontal scaling of the infrastructure. Micro-frontends, the benefits are mainly organizational. You need to bear that in mind. You need to make sure that the benefits, you’re not going to get faster applications. You probably end up affecting the performance, if you’re not careful. Mostly organizational, the benefits. The little-known benefit is when you manage to get the right architecture, you will find things like these. Continuous deployments, instant rollbacks to production. Production can just roll back in one second because you just switch a version, and then the new application is there. A/B testing. A really cool one is chaos engineering, and self-healing. If part of the application is failing, then you load another version, and really cool stuff and benefits that you can get with micro-frontends and distributed architecture.
You might be wondering, where do we start? What can we do? Where in that scale we are? Where we want to go. It is really hard. One thing that you can do today is this, for a distributed system to be distributed, it has to be decoupled first. A takeaway from this presentation is decoupling. That’s where you need to start, you need to start decoupling. What does decoupling mean? Let me just illustrate it with an example. How many of you like pizza, or how many of you like pasta? I love a Carbonara. The thing with pizza is that it’s very versatile. It comes in a square box. It is round, but you slice it. Also, there is a saying there that you can feed a team with two pizzas, the two-pizza team thing. Pizza is really versatile. We’re now talking about decoupling, is, look at your system as a pizza, not as spaghetti. Because a pizza is very versatile. You can divide it and ensure that you decouple all these pieces that you can move, put back, transport everything. A lot of people talk about spaghetti code. A lot of people talk about this, but not a lot of people talk about distributed architectures. It doesn’t matter when you scale the distributed and decoupled architecture or UI, as long as you decouple architecture, you will end up with a really good architecture.
One of the reasons is something really cool that I call the reverse composability. Because a decoupled system will allow you to decouple the application and move in these different steps of the application. You can start with a monolith. If it’s modular, you can go with a modular monolith, and then you can go to micro-frontends. Then if micro-frontends is too complex, and it’s not working for you, you can go back one step. It will allow you to go back and forth between these architectural choices. If you don’t want independent deployability because that’s causing you too many problems, then you can move a step back and then release everything at once as an integrated application. The key for you to be able to do that is to decompose and decouple the application into smaller self-contained business domain-driven parts.
The actual conclusion is, we have defined the problem. Decouple your systems, please. You can apply reverse composability. You can go back and try different types of architecture. You can get them until they solve your problem.
See more presentations with transcripts