The Serverless Monolith: An Easier Way to Get Started
Keep It Simple, Listen to Customers, Grow as Demand Requires It
As we start with serverless, it is easy to fall into the perfectionist trap of context-less best practices.
“Surely”, one might think, “if all these serverless experts are going full-on with microservices, event-driven architecture, and overly complex data models, then I should be doing the same!”.
As we’ve said before, many of those best practices heavily depend on context.
There is an easier way to get started. Some call it a serverless monolith, although it’s not exactly like a monolith, at least not in the way most people understand it.
We’re going to call it a serverless monolith anyway, just to make our life easier.
What Does a Serverless Monolith Look Like?
Here are some things that a serverless monolith doesn’t care about:
Who owns which tables: in a serverless monolith, every Lambda can access any table, regardless of the domain. If I need X, and table Y has it, I’m going to query the table directly and use the data as I see fit.
Event-driven or asynchronous communication: just like in most basic monolithic systems, each Lambda can call any other service (including other Lambdas 😱) in sequence. It’s okay for the response to take a few extra milliseconds.
The best and coolest way to design a DynamoDB table: in the world of serverless monoliths, a table for each entity will do just fine. If I need data from multiple tables, then querying two or even three different tables at once is perfectly acceptable.
While this is still a far cry from an old fashion monolith (and thank goodness for that), it resembles it in some surprisingly comforting ways.
For example, in a simple monolithic application, I don’t need to care about microservice domains and resource ownership. I have the code, and I have the database. If I need something, I make a query and use the data as I see fit.
The same can be said about asynchronous communication. Sure, some advanced monoliths make room for some event-driven communication. But it’s not how I would start a monolith-based project. Asynchronicity is not a default pattern in the same way as when working with microservices.
And, of course, most modern monolith frameworks come with their own ORM. This implies that in most small to medium monolithic projects, developers don’t bother thinking about scalable data modelling and access patterns. They simply create a new table for each business entity that is needed in the codebase, regardless of the fact that performance will not be optimal.
Who Should Use a Serverless Monolith
Personally, I am not a fan of the serverless monolith. I have spent most of my career building highly scalable, distributed systems. But I have come to appreciate that it is a process and that you can get there one step at a time.
It is often a good idea to hit the ground running with a simple application that looks something like a serverless monolith.
As the application grows, both user feedback and internal metrics become available to make more informed decisions. At that point, parts of the codebase can be split into domain-based systems; services used by endpoints that require a faster response can be turned into event-driven resources; tables can be optimised based on known, real-world access patterns.
Trying to get everything right from the outset can lead to premature optimisation. It risks delaying useful value that could be delivered to potential customers. In most cases, a more iterative and agile approach is to be preferred.