Microservices vs Monolith: Real Talk
The Microservices Decision: Why Context Matters More Than Trends
You may be working with a codebase where increasing complexity is making changes slower and harder to predict. The microservices vs monolith decision is not new, but the right choice depends on understanding your product, team and operational requirements.
We’ve worked with systems where teams introduced multiple services, message queues and additional infrastructure in an effort to improve delivery. In some cases, the original challenge became harder to solve because teams also needed to manage distributed systems, service boundaries and operational complexity.
Let’s talk about when microservices actually make sense, and when you’re just adding complexity to feel productive.
The Monolith Isn’t Usually the Problem
Here’s what we’ve learned from rescuing projects: the monolith architecture is rarely the root cause of your problems. What looks like “we’ve outgrown our monolith” is usually one of these:
- Poor code organisation — Circular imports, God classes, no clear boundaries between domains. We’ve seen Django backends where importing one module pulls in half the application.
- Missing abstractions — Direct database calls scattered everywhere, business logic in views, no service layer. When you need to change how users work, you’re touching 47 files.
- Test gaps — No integration tests, so every deployment is a prayer. The fear of breaking things slows everyone down.
- Deployment friction — Slow CI/CD pipelines, manual deployments, no staging environment. This has nothing to do with architecture.
Splitting into microservices does not automatically solve these challenges. It can move existing complexity across network boundaries while adding new considerations around communication, monitoring and reliability.
When Microservices Actually Make Sense
There are legitimate reasons to extract services. Here’s when we recommend it:
Different Scaling Requirements
Your PDF generation service needs 10x the CPU of your API but handles 1/100th the requests. Running them on the same instance means you’re either over-provisioning or throttling. This was exactly the situation with Bet Karma—their odds calculation was computationally expensive but needed to run independently of the main API serving user requests.
Different Deployment Cadences
Your core billing logic changes quarterly after legal review. Your user dashboard gets updated weekly. If these are in the same codebase, either billing releases slow down dashboard work, or you’re deploying billing code you haven’t re-verified. Separate services let each team ship at their own pace.
Different Technology Requirements
Sometimes Python just isn’t fast enough. We migrated an IoT platform’s critical path from Python to C++ for a 90% cost reduction. But that C++ code doesn’t need to live in your Django app—it’s a separate service that handles performance-critical work while Python handles the parts where developer productivity matters more than microseconds.
Team Boundaries
If you have genuinely independent teams (6+ engineers) working on genuinely independent domains, service boundaries can reduce coordination overhead. But if you’re a team of 4, you’re just adding ceremony.
The Red Flags We See
When founders tell us they’re planning a microservices migration, we ask why. These answers make us nervous:
- “It’s best practice” — For whom? Netflix has thousands of engineers. You have five.
- “We need to scale” — Scale what exactly? Your monolith on a $50/month server handles 30,000+ monthly active users just fine. We know because we built one that does exactly that for a renewable energy calculator.
- “The code is too tangled” — Then untangle it. Introduce modules, define boundaries, add a service layer. This is cheaper than microservices and teaches you where the real boundaries should be.
- “We want independent deployments” — You can achieve this with feature flags and good CI/CD. Have you tried that first?
Promises That Should Make You Nervous
Whether you’re evaluating an agency for a microservices migration or a monolith cleanup, watch out for these claims:
- “The architecture will be future-proof” — Nobody can predict the future. The best architecture is one that’s easy to change, not one that anticipates every possible requirement.
- “It will be scalable” — Scalability depends on requirements that haven’t been discovered yet. A good team builds for current needs with extraction points for later.
- “We can fast-track your microservices migration” — Fast usually means cutting corners. Rushed service extraction creates distributed monoliths—the worst of both worlds.
What you want to hear instead: “Let’s validate whether you actually need this before we build it.”
What Microservices Actually Cost
Before you split, understand what you’re signing up for:
- Network calls are not function calls. They fail. They timeout. They succeed but return stale data. Every service boundary is a place where things can go wrong in ways a monolith never will.
- Distributed transactions are hard. What happens when the order service saves but the inventory service fails? You need saga patterns, compensation logic, eventual consistency reasoning. This is genuinely difficult engineering.
- Debugging across services requires tooling. Distributed tracing, log aggregation, correlation IDs. Without these, you’ll spend hours figuring out why a request failed.
- Local development gets complicated. Running 8 services, a message queue, and a service mesh on your laptop is a different beast than
python manage.py runserver. - Your AWS bill will grow. Each service needs its own compute, load balancer, and often its own database. We do AWS cost optimisation regularly, and premature microservices is a common cause of runaway costs.
The Middle Path: Modular Monolith Architecture
Here’s what we usually recommend instead: build a modular monolith with service extraction capability. In the microservices vs monolith decision, this modular monolith architecture approach often delivers the benefits of both.
This means:
- Clear domain boundaries in code. Separate Django apps or Node.js modules that could become services but don’t need to yet.
- No direct database access across modules. Each domain owns its tables and exposes an internal API. This is the hard part, and it’s the same work you’d do for microservices.
- Async processing for anything that doesn’t need synchronous response. Background jobs, event handlers—these are easy to extract later.
- Feature flags for independent releases. Ship code that’s not yet active, enable it when ready, roll back without deploying.
When you structure your monolith this way, extracting a service later is straightforward. You already have the interface defined. You just move the code to a new process and change the internal call to an HTTP call. One client saw their development velocity double after we helped them restructure their monolith this way—no microservices required.
How We Approach the Microservices vs Monolith Decision
When clients come to us for technical architecture consulting, here’s our process for the microservices vs monolith question:
- Measure first. Where is development actually slow? Often it’s one area of the codebase, not the whole thing. Fix that area.
- Identify the real constraints. Is it deployment speed? Testing confidence? Code coupling? Each has solutions that don’t require microservices.
- Extract only what’s proven. If one domain genuinely has different requirements, extract that one. Don’t boil the ocean.
- Keep the option open. Structure your monolith so extraction is possible but not required.
This is the pragmatic approach. Not “microservices are bad” or “microservices are good”—but understanding your specific situation and making the choice that lets you ship features to users.
We’ll push back if we think you’re overcomplicating things. We’ll tell you when we don’t know something. And we’ll involve you in the trade-off decisions because it’s your product and your business.
“The honest feedback – as a company owner who has limited knowledge of how mobile applications work, having their input on the best decisions to make for the app has been extremely helpful.”
— Molly Sinclair, Owner, Cleverpop
When You Actually Need Help
If you’re staring at a codebase that feels unmanageable, the answer probably isn’t architecture. It’s getting someone who’s taken over complex codebases before to help you see clearly.
We’ve inherited React apps with 10 different state management libraries. We’ve untangled Django backends with circular imports that took 30 seconds to start. We’ve fixed Node.js performance issues that turned out to be N+1 queries, not architectural problems.
Sometimes the answer is microservices. Usually it’s not. Either way, we’ll tell you the truth—even when it means recommending less work for us.
Not sure if your architecture is helping or hurting? Book a free architecture consultation and get honest advice on whether your codebase needs splitting, restructuring, or just some focused cleanup.
Frequently Asked Questions About Microservices vs Monolith
Should I choose microservices or a monolith for my SaaS product?
The right choice depends on your team size, product complexity, scaling requirements and operational needs. Many SaaS products are better served by a well-structured monolith before introducing the complexity of microservices.
When should a monolith become microservices?
A monolith may be ready for service extraction when specific domains have different scaling requirements, independent deployment needs, or clear ownership boundaries. The decision should be based on real constraints rather than following a trend.
What is a modular monolith architecture?
A modular monolith architecture keeps an application as a single deployable system while creating clear boundaries between business domains. It provides many benefits of microservices without the operational overhead of distributed systems.
Why do microservices projects fail?
Microservices projects often fail when teams introduce distributed complexity before solving underlying problems such as poor code organisation, unclear boundaries, weak testing or deployment issues.