Today I want to address a great question I received from subscriber Patrick in reply to my email on Friday:
I really enjoy your newsletters, especially the oldletters. This week, I most enjoyed the DHH article you shared about the Majestic Monolith. In my mind, when I read “monolith,” I picture one large Node.js application, with all of the backend logic for an organization, running on an EC2 instance. How does the concept of a monolith translate into a small Serverless application? Does this mean deploying everything in one stack? Keeping all the code in one, shared repo? With Serverless you have a lot of asynchronous workflows so it’s clearly not the case that all of the logic is being run on one machine. I’m curious what your thoughts are about the “serverless monolith.” Does such a thing exist? And if it does, how can you architect your application to be a “serverless monolith” and reap the same benefits DHH described in his article.
This is a really interesting question and one on which I don’t think there is consensus within the serverless community. Take this Twitter thread from Brian LeRoux in October 2021 asking “is a CloudFormation stack of Lambda functions a monolith?”. The poll results and comments are totally mixed. I asked a similar Twitter question back in February 2020 and again the responses were all over the place.
Let’s start by addressing the branding issue. As DHH said in his article, “monolith” has become a tainted term for many (possibly even a majority of) architects and developers who associate it with badly designed Big-Ball-Of-Mud architectures with poor decoupling and little or no modularity. Whether I agree with this take or not, this makes me reluctant to use the term “serverless monolith” to describe an architectural approach that I advocate for, as it starts conversations off on the wrong footing.
There is also the fact, which Patrick mentioned in his question, that the compute of serverless applications is inherently distributed (async event-driven workflows, etc) and this in itself, for some folks, would rule out any type of serverless application being viewed as a monolith. (Sidenote: I think DHH himself uses this criterion to rule out serverless as a monolith — it seems he’s not a serverless fan and doesn’t realise that Serverless ≠ Microservices 😛)
All this said, there is one crucial attribute of monoliths that I think is worth keeping for small teams—the monolithic deployment of all the application code and associated cloud resources. Being able to make the assumption that everything gets deployed together simplifies so many concerns. In practice, this oftens means that you deploy all resources within the same CloudFormation stack, although this is not a precondition as there are some valid reasons for deploying multiple stacks together. It also almost always means using a single monorepo to hold the entire application codebase.
The alternative to doing monolithic deployments is to split the system into microservices along domain boundaries that you have identified. However, once you start doing this you’re signing your team up for many new responsibilities:
- Maintaining a separate CD pipeline for each microservice
- Ensuring that each microservice does not share any cloud resources (such as DynamoDB tables or S3 buckets) with others, so that they can be released independently without requiring a lock-step deployment
- Applying common operational concerns consistently across services (e.g. monitoring configurations, security policies)
- Mocking out external microservice depnencies in integration tests. You will still want to test integration to real cloud resources used internally but without relying on “external” microservices (whose availability your team may not control)
- Inter-microservice E2E tests are a new category of test you now need to write and find a place for in your CD pipeline
- Finding a mechanism to share of common code across services
- Enable developers to provision their own individual cloud environment that represents the whole system
- Ensuring that microservices communicate only via their published APIs or event buses
This last point is the real kicker for me having worked with many small teams building early-stage products, with serverless and without. In my experience, it’s very rare that you’ll get domain boundaries (along with their associated APIs and data stores) right first time when everything is changing so fast. And the redrawing of these boundaries down the road can involve a lot of rework or even more insidiously, lead to “temporary” sharing of internal resources between services “until we get enough time to refactor it properly”.
This doesn’t mean that you shouldn’t still modularise your codebase, separating concerns within it into single-responsibility code modules and creating abstractions where they make sense. You absolutely should be doing all of these! It’s just that your boundaries are code modules rather than service interfaces.
And you can still use asynchronous, event-driven workflows within your monolithically deployed application. One of my favourite and most-referred serverless articles describing such workflows is Jeremy Daly’s Serverless Microservice Patterns for AWS, the vast majority of which, despite the article title, are not specific to independently-deployed microservices.
I’m also not saying that there won’t become a point in your product/team’s growth where extracting microservices might start to make sense. But IMO all the overhead I listed above isn’t worth it for almost every team I’ve worked with building a new product and so I generally recommend that new serverless apps should use a monolithic deployment.
What are your thoughts?
I’m particularly keen to hear from folks with differing experiences—in particular anyone who’s built a greenfield serverless app with a small team and started out with a microservices architecture.
Indie Cloud Consultant helping small teams learn and build with serverless.
Learn more how I can help you here.
Join daily email list
I publish short emails like this on building software with serverless on a daily-ish basis. They’re casual, easy to digest, and sometimes thought-provoking. If daily is too much, you can also join my less frequent newsletter to get updates on new longer-form articles.