Serverless - The Ultimate Promise of Cloud Native
The promise is seductive: no servers to manage, automatic scaling, and pay-per-use pricing. Just write your business logic, and let the cloud provider handle the rest. It’s the natural evolution of cloud computing, the purest form of “focus on your code, not your infrastructure.”
But as with many promises in technology, the reality is more complicated than the sales pitch.
The Serverless Paradox
The term “serverless” can be misleading, as it implies the absence of servers, when in fact, servers are still present - they’re just not under your direct management. This nuance might seem minor, but it underscores the change in management responsibilities, rather than the complete removal of servers.
I believe that at the core it is a noble idea: the developer should not have to worry about the underlying infrastructure if it is not relevant to the business problem. But isn’t this actually how things used to be? When I was working with PHP twenty years ago, I just uploaded my code to some FTP server and it magically worked.
One difference is that back then I could run my code locally and test it. All I needed was to install the LAMP stack on my computer. How do I locally test a modern Cloud Native application made up of dozens of serverless functions? Well, I need a way to run the functions locally - all cloud vendors provide this. But my function depends on other functions - how should it reach them? Then, after spending days configuring API Gateways and NoSQL DBs in LocalStack you hit a wall with the IAM integration and decide that it isn’t worth the effort, and you’ll just accept a feedback loop of 15 minutes where it should have been 2 seconds.
Making a development team run the system locally impossible is a great way to make them miserable. Forcing them to deploy every change to a cloud environment just to test it slows down their development feedback loop significantly.
I once worked on a fairly new application where every single REST endpoint was implemented as a separate serverless function, all hidden behind an API gateway. It was so “cloud native” that the developers couldn’t run anything on their laptops. The development environments cost $1000 USD per month per developer due to poorly configured defaults, and development velocity was approaching zero due to the slow feedback loop. Of course, management didn’t accept our proposal to refactor it to solve the velocity issue, because refactoring always takes the backseat to new features.
The Cold Start Problem
The cold start problem is another example of the serverless abstraction breaking down. In theory, your code is always ready to run, waiting for events in the cloud. In practice, if your function hasn’t been called for a while, the cloud provider deallocates its resources to save costs. When the next request arrives, you get to wait while the provider provisions a new container, boots up your runtime, and initializes your code.
The common solution? Keep your functions “warm” by periodically sending them fake requests. It’s like leaving your car running all night so it starts faster in the morning. We’re essentially managing servers with a very long stick and a bag over our head - scheduling jobs to poke our functions awake while pretending we don’t have to think about the underlying infrastructure. We’ve gone from “don’t manage servers” to “manage servers indirectly through elaborate workarounds.” The underlying infrastructure is still there; we’ve just lost the ability to control it directly.
When Serverless Makes Sense
To be fair, serverless isn’t all bad. It can be excellent for:
- Truly event-driven workloads with sporadic traffic
- Simple, single-purpose functions that need to scale independently
- Cloud “glue code” - like generating thumbnails when files are uploaded to S3, or processing events between cloud services
- Prototypes and experiments where infrastructure setup would be overkill
And serverless doesn’t just mean serverless functions. Serverless container runtimes like AWS Fargate can be a great choice for workloads with unpredictable scaling needs, or as a simpler starting point compared to managing Kubernetes clusters.
But using serverless as your default architecture for everything? That’s probably not the best approach. The added complexity and loss of control need to be carefully weighed against the benefits.
Conclusion
The next time someone suggests going “fully serverless,” ask them if they’ve considered the trade-offs. Ask them if they’ve thought about local development, debugging, monitoring, and the actual costs involved.
Here’s a suggestion: start with a simple, deployable unit - maybe even (gasp!) a monolith. Add serverless functions where they make sense, where the benefits outweigh the complexity costs. Let your architecture evolve based on actual needs rather than buzzword compliance.
And remember: the best architecture is the one that lets your team deliver value efficiently. If your developers are spending more time fighting their tools than solving business problems, you might want to reconsider your choices.
Finally, if you’re advocating for a serverless architecture, make sure you’re part of the team building features with it. There’s no better way to understand the implications of architectural decisions than experiencing the day-to-day developer experience firsthand.