Monolith vs. Microservice architecture: Embracing the Monolith safely with feature flags
One of the biggest reasons companies come to us to adopt feature flags is because they are moving from a monolith to a microservice architecture. While this makes sense for tons of companies, we find ourselves moving in the other direction to better support them. It has taken us a while to realise this, but slowly the fog has cleared and we have appreciated that, for us, in the microservice vs monolith debate, a monolith architecture is the right answer to a bunch of different questions.
It has also served as a reminder that when engineering software, there are no right answers; just a lot of arguments about architecture!
Flagsmith: The early years
When we started out designing Flagsmith, we knew that we wanted to have an API interface that could be used to power different types of clients. So it seemed intuitive at the time to create an application for our API, and then separate components for all our different clients; the web dashboard, Java SDK, Javascript SDK etc.
This separation of concerns really helped at the start of the project to ensure that we didn’t accidentally create any sort of tight coupling between the different components that we were working on. However, as time went on, we felt a number of frustrations with running the API and the web dashboard as separate applications.
This week we pushed a commit to our main branch which effectively combined these two repositories together. Right now, we are still building two separate components, an API and a Web Dashboard, but this monorepo lays the foundation to enable us to build our monolith service. API and Dashboard living together like cats and dogs.
Why did we decide to take these steps? We realised there would be a number of benefits.
Far Fewer Versioning Headaches
Managing the versioning across multiple components can be frustrating and time consuming. Really, for Flagsmith the version that matters is the API interface contract between the API and our dashboard and client SDKs. That contract is specified in code within our python project, but the web dashboard consumes that entire API. When we make a change or addition to the API, it proved tricky to maintain that across two different repositories. Who is “right”? Does API tag 2.7.1 work with Front End Tag 2.7.0?
Testing this stuff started to prove tricky. With 1 codebase, we can test this with a single Github action. There’s only 1 git tag. All of these versioning issues just melt away, and we feel that is going to be a big help.
Single Source for Pull Requests and Issues
Pull requests can now span multiple components. This is a really big win for Flagsmith, where it is very common for a single feature to require changes to both of our core components.
Issues that crop up no longer have to deal with the problem of people “throwing them over the wall” to the other project.
Better Pipelines
If your code is spread out around multiple repositories, it can be really hard to build a single end to end test that runs through your entire platform. It often means testing one component against a pre-built artefact of another. It is hard to test code of 1 component against the current code of another.
Imagine you want to build a feature that requires code commits against 2 distinct components. For Pull Requests that touch multiple components, you end up having to create multiple PRs across your repos, and that makes it super hard to track and test. With a mono repo, you can have a PR that spans multiple components with ease.
Confusing your Tooling
There are some complexities that are introduced when moving to a monorepo. Some tools assume that your project/component is self-contained within its own repository, and that it lives at the root of that repo. For example, tools like Heroku or Vercel will inspect your root repo folder and try to figure out what sort of project it needs to build based on finding files like package.json or pom.xml in the root directory.
Generally you need to do some extra work to tell these tools about your folder structure, and where to build from.
Tricky Pipelines
Having a single code base can also introduce interesting questions around what to build and when. For example, Flagsmith is split out into an API and Web Front end. Our api lives in /api and our React application is in /frontend (amazing, right?!). But the question then becomes: “If we commit some code and push it, what components should be rebuilt?”.
We use Github Actions to run our CI/CD pipelines, and they have an option of defining when to run jobs based on code changing in specific folders. So one of our actions looks like this:
name: API Deploy to Staging
on:
push:
branches:
- main
paths:
- 'api/' - '.github/'
What we are doing here is telling Github to only build and deploy our API into staging if code has changed in the /api folder, or the .github folder (in case we update our build actions). This solution is fairly simple for us as our code is split up in a fairly straightforward way, and our two applications are still decoupled and built around an API contract. It might not be as easy and obvious if you have lots of microservices that have more complex coupling relationships.
Combining your stars
OK let’s get this out the way; there’s a nice marketing boost from combining repos and pooling all of your future stars!
Just build the monolith!
It might not be particularly fashionable, but we plan on pulling in our web dashboard into our Python/Django application. Our monolith.
There are a number of reasons for this:
- It makes our deployment story way simpler. Moving down to 1 application means fewer headaches with multiple domains/CORS/SSL and so on.
- Our versioning issues become even simpler. If there’s only 1 ‘component’ you don't have to worry about versioning mismatches between the two.
- Production deployments are way, way simpler. With 1 component there’s only 1 ECS cluster, only 1 Openshift component, 1 point of ingress and so on.
- Atomic deployments. You don't have to worry about coordinating the release of two components when there is only one component.
Combining our components into a monolith would have been a lot harder to achieve with the code in two separate repositories. It also would have just been really weird.
Final Thoughts
The interesting thing about this architecture that we are working towards is that, in a way, it is still decoupled. The front end still talks to the API over REST. We could theoretically point the front end dashboard to an API hosted elsewhere. So in some ways this is something of a hybrid approach that takes the best aspects of both worlds.
Our repo: https://github.com/Flagsmith/flagsmith