Our task was to merge two existing apps, and in the same process, merge two existing teams. The existing apps were MinBedrift – an enterprise mobile self-service application – and MinPortal – an enterprise datacom self-service application. Both were monolithic react apps that had grown quite large over their life spans.
Looking at our concerns with the existing apps, and taking into account that our first priority – as in all we do – is to create a good customer experience, we decided on which goals we wanted to achieve with our new architecture. These goals are detailed in the next section, and if you want to read more about the technical implementation we landed on, you can read about that here
Previously, we have faced issues with one change already merged to the master branch blocking the deployment of another merged feature, because the first feature is not ready to be shipped yet. We wanted the new architecture to support independent deployment of features, and preferably the ability to deploy individual parts of the application independent of each other.
We also wanted the ability to work on one, isolated, part of the application, and to have the ability to make changes to that part while disregarding the rest of the application – the size and interconnectedness of our old applications made it hard to refactor components or APIs for instance, because it was hard to be completely sure that your changes would not inadvertently break some other part of the application.
In addition, since the applications were large, they took quite a while to get up and running locally, we wanted a smoother developer experience by being able to run small parts of the project, while getting the rest of what you needed from a staging environment.
Another issue with large, interconnected code bases, is that more or less all technology choices create a rather rigid lock-in and path dependency. Once you have started using React, it would be a huge job to change to something else. Once redux is in charge of your state, switching to something new becomes a long project. We wanted our new architecture to make it easy for us to experiment with new technologies, and to easily rewrite only parts of the application at a time. In short, we wanted the architecture to be as technology agnostic as possible.
All of these points concern technical stability, developer experience, and giving us the confidence to deploy without being afraid to take down the app. These are important aspects, but we cannot let them compromise the user experience. An important goal of the new architecture was therefore also that there should be seamless, smooth integration between different parts of the application – the user should not be exposed to our technical decisions. This also means that there needs to be some sort of shared state – the user should log in once, and not be asked to log in again when going to a different part of the system. She should be able to set her language choice in one app, and that choice should be picked up by all the apps.
A hypothesis we worked with, was that doing a micro frontend architecture would force us to write more loosely coupled applications, which would, in turn, alleviate many of the issues we had faced with our monolithic apps.
Apart from just avoiding the issues we had already experienced working on our separate applications, we expected these problems to be exacerbated in the new application. First, mathematically, (big app + big app) = (2big app), and a too big app is a priori not a good thing. Second, we were going to be a lot of people working on the app simultaneously. About 10 developers, plus UX people, business analysts, team leads and product owners. Because of that it was necessary to have a setup where we could work on several things at once without getting in each other’s way.
Third, as we were merging two existing apps, a lot of the work was in the beginning, and still is, concerned with moving functionality from the old applications into the new one. That means that the new app is a mishmash of domains and functionality to begin with – something that requires a rigid architecture to make sure that the functionality is added where it belongs in the first place, to secure high cohesion and low coupling. As stated previously, a micro frontend architecture makes it a lot harder to unwittingly or lazily create dependencies that should not be there.
We also assumed micro frontends could give us the flexibility we wanted with regards to deployments and technology agnosticism. So when setting out with our micro frontend proof of concept, what we were most interested in figuring out was first, whether we would be able to create a seamless experience for the end-user, and, second, whether we would be able to get all these benefits without a large technical overhead.
In general, our assumptions have turned out to be correct. To put it less academically, we are very happy with how it has worked out so far. We will write more about the technical architecture we have chosen in an upcoming blog post. We are also planning on writing a few more posts, about the pros and cons of our micro frontend setup. Here we will detail how it has affected the team, our processes, and more about how we handle deployment, orchestration and so on.
To give you a sneak peek, here are a few sentences about how micro frontends have worked in merging our teams so far. As we are quite a few people in the team now, we have had to try out different ways of splitting up the work, and currently, we have tried creating feature teams for each larger initiative we want to be implemented. Typically one team works on one micro frontend until the feature is on the level we want it to be, and then everyone can work on something else for a while.
Choosing to work with micro frontends changes the dynamic in the team. Instead of having very specific roles, you work more closely with several parts of the app. This again contributes to better insight, varied skills and creative ways of solving problems. Each feature team can figure out the best way to solve the problems they have been asked to solve, not being limited by other people’s/feature teams’ decisions. This is currently more of a theoretical advantage than a practical one for us right now, as all the micro frontends are typescript/react apps. But the independent builds and deploys are nice!
We got a new developer on board a few months ago, and according to her it has been both fun and challenging to join the team as we are trying to figure out micro frontends. Some of the issues that occur with our micro frontend architecture are new and unknown. The errors can show up in unexpected ways, which sometimes causes them to be difficult to discover and hard to debug. On the other hand, it was amazing to be able to contribute to one of the apps already in the first week, not having to worry about breaking something else!
Well, no. As a closing remark, we would like to stress that our view is not that you need to use a micro frontend architecture to achieve these goals. The issues we faced with MinBedrift and MinPortal did not only spring out of their monolithic architectures. Rather, our main point of view is that the selection of architecture can be an important tool in getting the outcomes you want with regards to code maintainability, flexibility and general developer experience. Micro frontends have worked well for us.
Have you or your team had any experiences with micro frontends, good or bad? We would love to hear from you. Drop us a line on twitter!