Skip to main content

The Monolith Isn't Dead

Microservices aren't always the answer. Sometimes a well-structured monolith is exactly what enterprise teams need.
10 July 2022·7 min read
John Li
John Li
Chief Technology Officer
I've had three conversations in the last month with CTOs who want to move to microservices. When I ask why, the answer is always some variation of "that's what modern teams do." When I ask what problem they're solving, the answer is usually less clear. Microservices are a solution. The question is whether you have the problem they solve.

The Microservices Tax

Microservices solve real problems for large organisations with large teams building large systems. When you have fifty developers and they can't work on the same codebase without stepping on each other, microservices make sense. When you need to scale one part of your system independently from the rest, microservices make sense. When you need different parts of your system to evolve at different speeds, microservices make sense.
But microservices come with a tax. And most teams underestimate it dramatically.
Operational complexity. Instead of deploying one thing, you're deploying twenty things. Each needs monitoring, logging, alerting, and health checks. Each can fail independently, which means your failure modes multiply.
Network calls instead of function calls. In a monolith, one module calls another module. It's fast, reliable, and type-safe. In microservices, one service calls another service over the network. It's slower, it can fail, and the contract between services needs to be managed explicitly.
Distributed data management. In a monolith, your data lives in one database. In microservices, each service owns its data. That means distributed transactions, eventual consistency, and reconciliation logic. These aren't impossible problems, but they're problems you didn't have before.
Debugging across boundaries. When something goes wrong in a monolith, you read the stack trace. When something goes wrong across microservices, you correlate logs from five different services, reconstruct the request flow, and hope your tracing infrastructure is working.
63%
of organisations that adopted microservices reported increased operational complexity they hadn't anticipated
Source: O'Reilly Microservices Adoption Survey, 2022

The Monolith That Works

A well-structured monolith is modular, testable, and deployable. The key word is "well-structured." A bad monolith - a big ball of mud where everything depends on everything - is genuinely painful. But the answer to a bad monolith isn't microservices. It's a better monolith.
Module boundaries. A good monolith has clear internal boundaries. The billing module doesn't reach into the user module's database tables. Each module has a defined interface. If you can't draw clear boundaries inside your monolith, you certainly can't draw them between services.
Shared database, separate schemas. You can get many of the data isolation benefits of microservices by using separate schemas or naming conventions within a single database. The billing module uses billing_* tables. The user module uses user_* tables. They communicate through defined interfaces, not direct table access.
Single deployment, multiple modules. Deploy once. Test once. Monitor one thing. When something goes wrong, there's one log to read, one stack trace to follow, one system to debug.
If your team can't maintain clean boundaries inside a monolith, they won't maintain clean boundaries between microservices. The cost of failure is higher with microservices.
John Li
Chief Technology Officer

When to Actually Move to Microservices

There are legitimate triggers. But they're more specific than "we should modernise."
Team size exceeds what a single codebase can support. When merge conflicts are a daily occurrence and teams are waiting on each other to deploy, the codebase has become a bottleneck. Extracting services gives teams autonomy.
Scaling requirements are genuinely uneven. Your search indexing service needs ten times the compute during peak hours. Your user authentication service needs consistent low latency. Your reporting service is CPU-intensive but runs once a day. Different scaling profiles are a legitimate reason for separate services.
You need technology diversity. Most of your system is fine in Node.js, but the machine learning pipeline needs Python, and the real-time processing needs something with lower latency. Different services can use different technologies. In a monolith, you're stuck with one.
You've outgrown your deployment pipeline. A monolith where a small change requires deploying the entire system and running the full test suite creates deployment friction. If deployments are infrequent because they're risky and slow, that's a real problem.
If none of these apply to you, the monolith is probably fine. More than fine. It's probably better.

The Modular Monolith Pattern

The pragmatic middle ground that I recommend for most enterprise teams: a modular monolith with the option to extract later.
Build a monolith with module boundaries that could become service boundaries. Use interfaces between modules. Don't share database tables between modules. Write integration tests at module boundaries. Keep the deployment simple.
If you need to extract a module into a separate service later, the boundaries are already clean. The interface becomes a network API instead of a function call. The database tables become a separate database. The transition is surgical rather than archaeological.
This approach gives you monolith simplicity today with microservice optionality tomorrow. And for most enterprise teams, tomorrow never comes - because the monolith keeps working fine.

The Honest Question

Before you start a microservices migration, answer this honestly: is the problem your architecture, or is it your code quality? Because microservices don't fix bad code. They distribute bad code across more places, add network calls between the bad code, and make the bad code harder to debug.
Fix the code first. Structure the monolith properly. If, after that, you still have scaling or team-size problems that the monolith can't solve, then consider microservices. You'll do the migration better because you'll be extracting clean modules, not untangling spaghetti.