Micro-frontends (MFE): Why should I add the complexity

I will be the first to say that I am very hesitant when it comes to micro anything. Microservices are traditionally done very poorly. They claim to allow a large scale team to work on segments of the codebase without having to understand the entire system. In theory, this is great, in practice, I’ve yet to work on a codebase that does this effectively, specifically when it comes to contract testing between the services. To me, all the benefits of a microservice approach are lost when you don’t or can’t test your code properly. I’m not saying you can’t contract test between services, I'm just saying in all the real-world production systems I have seen, I have yet to see effective contract testing.

With that being said recently I needed to get up to speed with micro frontends for an upcoming project. I had used microservice backends a few times in production and they have been okay, but the testing had been laxes. I had used micro frontends only very sparingly for a few tickets in a production system for a large company. The micro frontend setup was one that was connected at build time which meant if you update one repo you then had to push that and update the other repos for it to take effect. This to me was quite a limitation and very irritating when you need to update multiple repos to get one ticket done. Furthermore, when running the main site and then working in that micro frontend for a small part of the site, you would have to perform quite a complicated setup just to get the micro frontend to render live in your local environment. Furthermore, there was zero hot re-loading which meant you had to reboot the server each time you made a change. 

On the positive side, it was much easier to reason with the code as it was much smaller. Again there was no contract testing so if I changed something in the MFE I had to manually test if it was working on the rest of the site. This was a pain and became even more apparent when the react native app was consuming a micro frontend as well, this became a nightmare to link properly and get working.

My initial thoughts that I just couldn’t get my head around when considering a micro frontend were: sharing state between frontends, contract testing between boundaries, and build time vs runtime linking.

The most interesting way I found to implement an MFE architecture is via Module Federation with Webpack, there are other ways but this was the route I went down. Webpack module federation allows two or more codebases to expose and consume other codebases at runtime. I won’t be showing any specific code examples as Jack Herrington does a much better job showing this off than I ever could, take a look here. I would highly recommend all his videos on the subject as he’s extremely knowledgeable and effective at teaching.

Runtime Linking

The key being at runtime, one of my initial concerns was the build time linking between MFEs, this to me is an enormous pain when it comes to development and releasing your code. Webpack allows for runtime loading of your MFEs which means that when a server boots, in its webpack it has the information it needs on where it can look for the other MFEs it requires. As long as those URLs to the other MFEs are live for example www.example.host for the host and www.example.banner for the banner MFE your webpack will simply pick it up and it will render like it was part of a monolith.

The upside is that whenever one of the MFEs is updated it's reflected live in all the other instances as it makes its way to each environment stage. The downside of this is that if a contract changes between two boundaries and there isn’t sufficient testing then it could make it into production, although with a UAT environment this isn’t that likely, or at least shouldn't be likely. You will simply have a broken section of your UAT site. A safer way to get to production would be to run a smoke test of all the major pages and major flows to make sure that each service is indeed running whenever you push the service to production.



Sharing State

Large scale applications need some form of state, large scale applications also benefit the most from MFE. This means we need to add some state at some point to potentially every single MFE in the system, this could be hundreds of services. The way that Jack Herrington outlines this is a central data store MFE that’s only responsibility is to handle all the storage of data for an application, with each MFE reading and writing from that store. To me this was a revelation, typically I would rely on something like apollo cache to handle all this caching for me or redux or another state management tool if needed. This can with some effort mind you, be transformed so that all the MFEs talk to the one service that contains the cache. 

When using Redux for example you can have all your store code inside a store frontend. Then each MFE will read and write to this central store as they see fit. It works the same as you would when adding Redux to your application on a component level. Each component shares one store, now each MFE shares one store. Check out this state management video for more details. 



Ideally, you want to keep the sharing of information between each boundary as small as possible, sure you can’t eliminate it and sometimes you don’t want to for performance reasons but having central state storage is crucial for effective micro frontends at scale. 

Above is two different MFEs talking to a central store. You might have a page that lists all your blog posts and another page that shows the top 3 most popular posts. Both of these sit in vastly different sections of the site and thus shouldn’t be tightly coupled in the same MFE, however it would be silly to re-fetch the posts twice. Thus you can have a single store to read and write from, where one update gets reflected in both locations instantly.




Contract/Boundary Testing

One of my biggest issues when it comes to any non-monolith approach is boundary testing. This is my experience has never been done well or at all. Often we add testing around the service itself or the micro-frontend itself but we didn’t have rigour around the boundaries which causes many issues when one person is expecting a data structure to look one way and the other expects it to be another. 

A brief rundown on what contract testing is: contract testing is the testing of the data that goes into one area from another, validating that the data is in fact of the shape and type that is required. This allows you to communicate from one MFE to another with reassurance that the data structure is as it should be. This also gives you the assurance that if there are changes in the sender that the consumer is updated accordingly.


In micro-frontend land, this can be fairly easy to navigate as we can use a tool such as cypress to test each page and each user flow. This makes it really handy to see what frontends are rendering and performing correctly and what aren’t. My biggest issue with boundary testing is not that you either have it or not because you need to have it. It's when you run them! If you use build-time linking these can easily be run each time you bump the version of one of your MFEs. You have to create a PR anyway which means you’re CI/CD will run all the tests again. However, if you truly want to make the most out of micro frontends I would argue that you should be using run-time linking, which I talked about earlier. This means that you never know when a codebase is updated and pushed which means you never know when you should run your test suite. Furthermore, if you added a new button on the checkout page but the about page received a new data model that the host site didn’t know it needed to send that whole page would break and all your tests would break too. Who’s responsibility is it to fix those tests? Well, some might argue that this should never happen as they should be completely isolated, which in theory is true, but in practice, this isn’t always the case. 

Another example would be if you keep your data stored in a different MFE than everything else. This would mean that if you had a function that created an entry in the data store eg. `addPost` which took 2 arguments but you then needed to change it to 3 arguments. You would then need to update every single instance of `addPost` throughout all your codebases otherwise they will break. Now you wouldn’t necessarily know this unless you have proper boundary testing.



In my mind, this is a big issue, but when researching this, it doesn't appear to be the biggest issue for most people and in retrospect maybe it’s not. Having exceptional testing inside each of your services should be enough for 95% of the bugs. To capture the next 4% having a tool such as cypress that runs at regular intervals to test the main pages as well as the main user flows. This should be enough to capture 99% of bugs. That last 1% is most likely going to lie in the unavoidable dependencies between the frontends which will come down to some contract testing and team discipline when changing known boundaries. When moving to an MFE approach the goal is to have as little coupling between your individual frontends which means that the scope of bugs that happen in these boundaries should also be minimal, at least in theory. 



Organisational discipline

When it comes to micro frontends I’ve never been the biggest fan, I think the complexity that they add is huge in practice! In theory, they are amazing, they allow organisations to scale giving clear boundaries on where new teams can be created with specific domains. It’s worked in the backend so why not in the frontend? My biggest concern as I’ve stated is the complexity that arises. You reduce the cognitive load of each team on the amount of code they need to understand to accomplish the implementation of new features. The drawback is the added complexity of the system as a whole, and the brittle nature that arises. I think you can mitigate most of these problems with the proper testing but I think it mostly comes down to organisational discipline. You can’t have a micro-architecture without proper discipline. Otherwise, you just get a monolith that's split 100 ways with duplication all over the place. 

If you can maintain discipline within each team, stick to the company standards, make sure that each frontend is as decoupled as it can be and implement extensive testing at all levels of the testing pyramid then you can make it work. The problem is that as the development sector of the company scales there are more people naturally. For every new person, you add is a new way code will be written. This means you must have an easy way for every single person to understand how each frontend should behave. It must have consistent boundaries, it must have consistent testing, and it must be consistent. Consistency is the key to a successful micro frontend architecture and it's the most overlooked aspect. Too often team A builds one frontend or service one way and another team builds it another way. These repositories must be templated so that it's the same technology, the same linting, the same everything unless there is a very specific business case to deviate. One of the sighted benefits of micro frontends is allowing you to mix and match technologies, this is okay, if there is a valid reason for doing so. Even still, the general philosophies should remain the same: testing and boundaries being some of them.

Closing Thoughts

In theory, micro frontends are amazing, they reduce the mental load on each individual developer allowing for the entire organisation to scale development-wise. In practice this never works out nearly as well as people hoped, having said that after going through and answering the biggest questions I had about micro frontends I feel much more comfortable recommending them to a business. I still think they require a very high level of discipline otherwise they become much harder to deal with than a typical monolith. Yet sticking with your traditional monolith simply becomes a nightmare as well. In my opinion, micro frontends are the way forward for large scale businesses, you just have to have the company culture to stick with the core principles and at the end of the day no matter what architecture you have, if you don’t have the organisational leadership in the technology side, you won’t have a great system anyway.




References

Below are all the links that I felt were extremely helpful in my understanding of micro frontends. I would highly recommend the Jack Herrington videos. He supplies great code examples on everything.



Previous
Previous

SST Making serverless easier

Next
Next

The cost of quality