“Should we use microservices?” depends on the system, team, and timing.
Some signals justify splitting a system. Others only look convincing because large companies use microservices. The default answer for most systems is not yet, until the complexity is earned.
This chapter covers when microservices are worth considering, which signals are misleading, and what to do when a modular monolith is the better starting point.
There are a handful of conditions that genuinely justify the cost of distribution. When you see them, the operational burden starts to buy something real. When you do not, you are paying for benefits you cannot yet use.
| Signal | Why it argues for splitting |
|---|---|
| Team count outgrows the codebase | Too many teams merging into one deployable means releases block each other. Separate services let each team ship on its own cadence. |
| Deploys are held hostage | When you cannot ship because someone else's half-finished work is in the same release train, that contention is real coordination pain. |
| One module's failure sinks the product | If a bug in reporting can take down checkout, isolating the risky component into its own service contains the blast radius. |
| One feature needs to scale very differently | When search needs 10x the capacity of everything else, extracting it lets you scale that one path without scaling the whole system. |
| One part needs a fundamentally different stack | ML serving, streaming, or real-time workloads often need a different language, runtime, or datastore that does not belong in the main app. |
The first two signals are about people, and they are the ones that carry the most weight. A rough heuristic: below roughly 20 to 30 engineers, the coordination overhead of microservices rarely pays for itself. One or two teams sharing a monolith do not interfere with each other's work often enough to justify fifteen deploy pipelines.
The codebase is small enough to understand, the merges are manageable, and a single deploy is a feature, not a constraint. The coordination problem that microservices solve does not exist yet at that size.
The deploy-frequency signal is the same problem viewed from a different angle, and it is more concrete than headcount. If your release process has turned into a weekly negotiation where teams wait for each other to finish before anyone can ship, that is the bottleneck microservices were built to remove.
What matters here is how often a team that is ready to ship cannot, because they are coupled to someone else's timeline through a shared release, more than the raw engineer count on paper.
The last three signals are narrower but still legitimate. Fault isolation matters when one component is both risky and non-critical, and you would rather it fail alone than take the important paths down with it.
The scaling signal matters when one workload's resource profile is far out of step with the rest, so that scaling the monolith to feed that one feature wastes capacity everywhere else.
And the technology-fit signal matters when a workload is a genuinely poor fit for your main stack, such as a Python ML model that has no business living inside a Java commerce application. Each of these justifies extracting one thing, which is a different and much cheaper decision than splitting everything.
Just as important as the signals to split are the conditions that argue against it, because these are the ones that are easy to rationalize away. A counter-signal is more than a minor inconvenience to push through. It is a reason the architecture will likely make you slower rather than faster.
| Counter-signal | Why it argues against splitting |
|---|---|
| Small team | A handful of engineers cannot staff the on-call, tracing, and pipeline work that a fleet of services demands. |
| Low or modest traffic | If one server handles the load comfortably, distribution adds failure modes without solving a scaling problem you have. |
| Tight consistency requirements | A heavily transactional core resists splitting. Replacing ACID with sagas across services is expensive and error-prone. |
| Immature CI/CD | Without automated, reliable deploys, shipping fifteen services by hand is far worse than shipping one. |
| No distributed tracing | When a request crosses many services and you cannot follow it, debugging in production becomes guesswork. |
The consistency counter-signal deserves a closer look because it is the one most often underestimated. A system whose core job is to keep several pieces of data atomically in agreement (a ledger, an inventory count at checkout, a transfer between accounts) is exactly the kind of thing that gets painful to distribute.
Split that core across services and the single ACID transaction you relied on becomes a saga with compensating logic, idempotent consumers, and eventual consistency to reason about. Sometimes that is worth it. Often it means taking the one part of the system that most needs to be correct and making it the hardest part to keep correct.
The operational counter-signals, immature CI/CD and missing tracing, are different in character. They are gaps in your readiness rather than permanent properties of the problem.
Treating them as details to sort out after the split is a mistake, because they are what make the split survivable. Adopting microservices without them is how teams end up with a distributed system they cannot see into or deploy reliably.
When several of these counter-signals are present at once, the design conversation should slow down rather than speed up. A small team, on modest traffic, with a transactional core and hand-rolled deploys, that splits into services signs up for most of the cost of distribution before any of the benefit.
Sam Newman, who wrote a widely used book on this topic, frames the readiness question well: there are capabilities you want solid before you start splitting, not capabilities you hope to build along the way. Three of them matter most.
Monitoring and centralized logging come first because the moment a request crosses service boundaries, you lose the single stack trace and the single log file that made a monolith debuggable. You need to be able to see what is happening across processes before you create the processes.
This means centralized logs you can search across services and metrics that tell you when one service is unhealthy, ideally before your users do.
Deployment automation is next. Deploying one monolith by hand occasionally is annoying but workable. Deploying fifteen services by hand is a full-time job that no one wants and everyone does badly. If a deploy still involves a human SSHing into a box, multiplying that by the number of services you plan to run gives you the real cost.
Reliable CI/CD ties it together. Each service needs to build, test, and ship independently and safely, because independent deployability is the goal of the whole exercise. If your pipeline is flaky or slow on one codebase, that flakiness gets multiplied across every service, and the autonomy you split for evaporates into release-day firefighting.
The reason to treat these as prerequisites rather than parallel work is sequencing. If you split first and build the foundation later, you spend the in-between period operating a distributed system blind, learning these tools under the most pressure.
Building them while you still have one deployable to practice on is far less painful, and it gives you an honest test: if you cannot get monitoring, automation, and CI/CD solid for one service, you are not ready to run thirty.
When the signals do point toward microservices, there is still a right and a wrong way to move. The wrong way is the big-bang decomposition: freeze feature work, spend six months carving the monolith into twenty services, and flip the switch.
This almost always goes badly, because you are committing to a full set of boundaries before you have learned whether any of them are correct.
The better approach is to extract one capability at a time. Take the single highest-value boundary, the one where the signals are strongest, and pull exactly that one capability out into its own service. Leave everything else in the monolith.
Learn from that first extraction: how the data separates, how the contracts hold up, how the operational tooling behaves. Then, and only then, decide what to extract next.
This incremental path has two advantages that the big-bang approach throws away. The first is that it is reversible: if the first extraction turns out to be a mistake, you have one service to fold back in, not twenty.
The second is that it keeps delivering value the whole way through, because the monolith keeps running and shipping features while you peel off pieces one at a time. You are never betting the company on a migration that has not proven itself.
There is also a learning compounding effect. The first service you extract teaches you what your real operational gaps are, what your team finds hard, and where your boundaries actually sit versus where you thought they were.
Carrying those lessons into the second extraction makes it cheaper and safer. Doing all twenty at once means making the same mistakes twenty times simultaneously, with no feedback loop to catch them.
The order of extraction usually follows one of three priorities: pull out the most painful part first (the slowest or most fragile), the most independently changing part first (where team autonomy buys the most), or the easiest part first (where the coupling is already weakest).
Any of these is defensible. What is hard to defend is extracting everything at once and hoping the boundaries were right.
Whenever this topic comes up, someone points at Amazon or Netflix. Both ran famous microservices transformations, both operate at enormous scale, and both are held up as proof that microservices are where serious engineering ends up. The conclusion people draw is that if the best companies did this, the rest of us should too.
That reasoning has a hole in it, and the hole is survivorship bias. Amazon and Netflix did not succeed with microservices because microservices are universally correct. They succeeded because they invested heavily in the supporting machinery first and because their scale genuinely demanded it.
Netflix built an entire ecosystem of resilience and deployment tooling, much of which they open-sourced precisely because nobody else had it. Amazon's move to services came alongside a deep investment in internal platforms, automation, and a strong-ownership team model. The services were the visible part. The platform underneath was the part that actually made it work.
The trap is copying the architecture without the foundation. A 15-engineer startup adopting "the Netflix architecture" gets the network hops, the eventual consistency, and the operational surface area, but none of the platform team, tooling investment, or scale pressure that made those costs worth paying at Netflix.
The diagram is the same while the outcome differs. What those companies actually show is that they were already operating at a scale and maturity where microservices made sense, and they built the tooling to support them.
The honest lesson from Amazon and Netflix is to earn microservices rather than simply adopt them. Invest in the foundation, let your scale and your team count make the case, and extract incrementally. Their architectures are an endpoint that matched their context, not a starting point you can borrow.
When to adopt microservices is a judgment call, and the judgment improves when you separate the real signals from the imitation ones.
The signals that genuinely argue for splitting are about people first (too many teams in one codebase, deploys blocking each other) and about specific technical needs second (isolating a risky component, scaling one feature independently, fitting a workload that does not belong in the main stack).
The counter-signals (a small team, modest traffic, a tightly transactional core, immature deploys, no tracing) are reasons to wait, and they are stronger than they look.
Before splitting anything, the foundation has to be in place: monitoring and centralized logging, deployment automation, and reliable CI/CD. These are prerequisites, not parallel work, because the gap between splitting and building them is exactly when a distributed system is most painful to operate.
And when the time does come, the move is to extract one high-value service at a time rather than splitting everything at once, so that the migration stays reversible, keeps delivering value, and teaches you something before you commit further.
The decision, distilled into a single flow, looks like this.
Working through that flow with the reasons attached is the point. Recommending against microservices when the situation does not warrant them reflects as much architectural maturity as designing them well when it does.
This chapter and the last one have treated the choice as monolith versus microservices, with the modular monolith as the middle ground.
But that spectrum has more points on it than those three, and the names matter. The next chapter places microservices alongside their close relatives, SOA and service-based architecture, so you can tell them apart and recognize when an older or middle-ground style is actually the better fit.
10 quizzes