Our Journey from SaaS to OSS: Embracing the Modular Monolith over Microservices
Table of contents
When interviewing candidate software engineers, I always ask the question, “What will you do to improve Appsmith?”
The all-too common response from candidates is, “I would make the Appsmith codebase microservice-oriented.”
This answer reflects the popularity of microservice-based architectures today, as well as the misconception that all monolithic codebases should be re-written with this newer model. After a discussion about microservices and the downsides and benefits they bring, I inevitably have to reveal that we’ve made the deliberate choice to not use microservices, and explain why.
Just because Facebook, Amazon, Netflix and other big tech firms use microservices, doesn’t mean that you should use microservices too.
In fact, I believe that using microservices is bad for most small and medium enterprises (SMEs) with teams of up to 150 engineers.
As the CTO and co-founder of Appsmith, a platform that helps developers build internal apps like support tools, dashboards, and asset-tracking apps quickly, I have had a unique view of how our architecture and development practices have changed from the early days. I’d like to use this three-part series to distill some of what I’ve seen during this period. This includes my opinion on the “monolith vs. microservices” question, how we’ve implemented Appsmith as a monolith, how this choice has influenced our shift to an open source development model, and most importantly, how having a monolithic codebase benefits the end users of Appsmith.
Monoliths vs. microservices
The question of whether monoliths or microservices are better is missing the point. Each is appropriate for meeting different development and deployment requirements.
The best software architecture is the one that allows your team to create the highest quality software the fastest, and that can vary based on many factors. In order to understand these factors, let’s go over some of the history, benefits, and drawbacks of monoliths and microservices.
Monolithic architecture: simpler, but can be costly later on
Prior to the widespread adoption of the internet and improvements to connectivity, the concept of microservices was in its infancy, as the infrastructure to reliably support distributed applications had not yet been developed. The only practical option was a monolithic architecture including a single codebase, (typically) a single binary, and a (typically) large application where all of the functionality is self-contained, running locally or on a private mainframe.
There are several benefits to using a monolithic architecture at all stages of the software lifecycle. During development and debugging, the ability to easily jump around the entire codebase to really drill down on what is happening can be extremely useful. Monolithic applications can be deployed to a single, consistent environment, which results in less complexity and fewer installation errors. During operation, calls are made to functions that are stored in memory locally rather than having to go through many layers of API calls to remote applications, which allows the application to run much faster.
Some of the drawbacks to the monolithic architecture are that small changes to functionality are costly to deploy and functionality cannot be scaled independently. If you make a small change to one piece of functionality, you generally need to rerun regression tests and redeploy the entire application. Depending on your application size, this can be a lot of extra work.
Scaling is also problematic, particularly for web applications. It is often beneficial to be able to just scale one component as traffic dictates, while keeping other low-traffic components unchanged to keep cost low. With a monolithic architecture, scaling components individually isn’t easily possible.
Monolithic architectures were the main choice of developers into the early 2000s. The early growth in the internet was fueled almost entirely by these architectures, as web stacks were still relatively simple and easy to package into a single monolithic application. The monolithic architecture is well-proven, but as the internet grew, an alternative arose to meet some new scalability requirements.
Microservice-based architecture: more complex, more scalable later on (in theory)
As the internet and the nature of the services built on it evolved, some companies realized that a monolithic architecture can be limiting for connected software. One of the first examples of this is Netflix, who in 2009 decided that scaling with a monolithic architecture would be too costly to fit their applications.
They rethought their architecture from the ground up, taking into account the changing consumer landscape and technological improvements brought about by a stronger internet. These improvements made it possible to develop distributed applications that weren't feasible before, which allowed them to shift their business model from physically mailing DVDs to streaming videos on demand. To accomplish this, they transitioned their hosting from on-premises to the cloud and their software architecture from monolithic to distributed microservices.
This was one of the earliest successful implementations of the microservice-based architecture. The main idea behind microservices is that, instead of having a single large application with all of the functionality self-contained, functionality is broken up into many smaller applications – usually on many different servers – which communicate with one another through well-defined APIs.
Benefits of the microservice-based architecture include more organized and modular functionality, easier scaling, and the possibility of development in multiple programming languages. The different components are generally broken down to the level at which each microservice has one job. This forces much clearer boundaries than with a typical monolithic approach, which can help keep things organized. As functionality is distributed in different binaries, those can be deployed on different instances, which allows scaling of individual microservices without scaling the entire application.
This division of labor is particularly beneficial for web applications with large or changing traffic patterns. Another benefit is that as long as the APIs are clearly defined, each team developing a microservice can choose a language that fits with their preferences and skills. Every smaller team is not forced to use the same language like they would be in a monolith.
Of course, this model also has drawbacks, which include higher network bandwidth requirements during runtime, higher developer skill and knowledge requirements, and more potential security vulnerabilities. You must have an extremely knowledgeable and skilled team to handle this increased complexity if you are implementing a microservice-based architecture.
What Appsmith has chosen
Since the beginning, we’ve considered and experimented with many architectures. We originally considered a microservice-based architecture, as that was what everyone else around us was doing. However, we rejected this option after all because there were simply too many concerns for on-premise deployment of a microservice-based application. Given that on-premise deployment was our business focus, we couldn’t afford to make the deployment process complicated for end users.
Instead, we relied on a purely monolithic architecture during the early days of Appsmith. This allowed us to make great progress on the project, but eventually we ran into significant engineering team scaling problems with a monolithic codebase.
We knew that these scaling issues would continue to hold us back, so we went back to the drawing board to rethink what we needed from first principles, rather than just adopting the most popular approach of the day. We eventually settled on a compromise between the two major architecture types: the modular monolith.
We didn’t know it at the time, but we had independently stumbled onto a solution that had already existed in the community and was allowing companies like Shopify, Gusto, Boxfuse, and many others to scale their development without adopting a microservice-based approach.
The best of both worlds
The modular monolithic architecture relies on a monolithic codebase that’s structured with the modular concepts found in microservice-based architectures.
The source files in a modular monolithic codebase are organized hierarchically based on distinct functionality, with specific teams being given ownership and responsibility for these specific directories. This is a good compromise between the two architectures because you can separate functionality, ownership, and responsibility, without introducing some of the other performance and deployment drawbacks associated with a purely microservice-based architecture.
From a practical standpoint, this means that when you are adding new functionality, you can create a new directory structure for it and take ownership, confident that you are not interfering with anyone else’s ongoing work. And if you are an Appsmith contributor, you can discover relevant code that you need to change much quicker and with less guidance from core developers.
The modular monolithic architecture also comes with all of the deployment advantages of monolithic architectures — a single binary that users can deploy, rather than tens of separate services. This was the key benefit for Appsmith’s end users and is particularly valuable for anyone deploying the application on premises, as they only have to worry about one self-contained application, rather than tens or hundreds of microservices communicating with one another.
Making the right choice—for us
The modular monolithic architecture allowed us to maintain one large codebase and deploy a single binary, but also modularize functionality in a clear, organized way. This allowed us to balance the interests of our end customers (who wanted a simpler deployment process) with those of our internal team and open-source community (who wanted clearer separation of functionality and better direction for contributions).
Overall, transitioning to the modular monolithic architecture was one of the best decisions we’ve made at Appsmith, for us, our contributors, and our users. We haven’t always been perfect and we’re still learning about the best ways to use this architecture, but we have learned some important lessons that I cover in our next article in the series about choosing the right architecture.