At some point a growing company's monolith starts to feel like the problem. It is big, it is slow to deploy, a change in one corner seems to break another, and someone in a meeting says the word that has become architectural shorthand for progress: microservices. The pitch is seductive — independent services, independent teams, scale what you need, deploy without fear. Some of that is real. But microservices are one of the most consistently misapplied ideas in software, because teams reach for them to solve a problem they do not actually have, and inherit a set of problems they were not warned about.
What microservices actually solve
Microservices are, first and foremost, an organizational tool. Their core benefit is that separate teams can own, deploy, and scale separate services without coordinating every release with each other. If you have one team, that benefit is almost entirely theoretical — you have paid for independent deployment you have no one to deploy independently. Their second real benefit is targeted scaling: when one part of your system has wildly different load or resource needs than the rest, splitting it out lets you scale just that part instead of the whole thing.
There is a third benefit people cite — fault isolation, the idea that one service failing does not take the whole system down — and it is real but oversold. It only holds if you design for it, with careful boundaries and fallbacks, and a naive split often produces the opposite: a web of services so dependent on each other that any one going down takes several with it. Isolation is something you engineer deliberately inside a distributed system, not a property you get for free by having many services.
Notice what is not on that list. Microservices do not make your code cleaner. A tangled monolith becomes a tangled distributed system, and the tangle is now spread across a network where you can no longer see it in one place. If your real problem is messy code, unclear boundaries, or slow delivery caused by fear, splitting the deployable units does nothing for it — you have taken a code-quality problem and added an operations problem on top. The boundaries you failed to draw inside the monolith do not draw themselves once there is a network between the pieces.
The distributed-systems tax nobody quotes you
The moment a function call becomes a network call, you start paying a tax, and most teams badly underestimate it. A call that used to be instant and reliable can now be slow, or fail, or succeed twice — and every one of those cases is now your code's problem to handle. Data that lived in one database, protected by transactions, now spans several services, and keeping it consistent becomes a genuine engineering discipline rather than something the database did for you for free.
Then there is everything you need just to see what is happening. In a monolith, a stack trace tells you where a request failed. Across a dozen services, a single request hops between them, and understanding one failure means correlating logs, traces, and metrics across all of them — observability you now have to build and maintain. Add the operational surface: more things to deploy, monitor, secure, and keep running. None of this is impossible, and mature teams handle it well. But it is a permanent, ongoing cost, and it is the price of admission, paid whether or not you ever use the benefits.
Testing deserves its own mention, because it quietly gets much harder. A monolith you can run and test end to end on one machine. A system of services means either standing up the whole constellation to test a change realistically, or leaning on mocks that drift from how the real services behave. Both are more work than the single-process test suite you had, and the gap between what your tests exercise and what production actually does is where distributed bugs like to live.
Most companies do not need them
This is the uncomfortable part. The great majority of companies reaching for microservices would be better served by a well-built monolith. The famous examples — the streaming giants, the marketplaces running thousands of services — operate at a scale, and with a headcount, that makes the tax worth paying many times over. Your company is almost certainly not at that scale, and copying the architecture of a company a hundred times your size is how you inherit their costs without their reasons.
A single well-structured application deployed as one unit is not a primitive stage you must grow out of. For most teams it is the correct architecture for years, possibly forever. It is simpler to build, simpler to test, simpler to debug, and simpler to run — and simple is not a compromise, it is a feature you keep spending when the complexity is not earning its keep.
The tell that a company has copied rather than chosen is the mismatch between its architecture and its calendar. If you have fifteen services and deploy the whole set together, on the same day, because they cannot safely move independently, you have bought the entire distributed-systems tax and are using none of what it pays for. That is not microservices; it is a monolith that has been scattered across a network and made harder to run.
The modular monolith is the right first step
The choice is not binary between a big ball of mud and a fleet of microservices. The option most teams skip is the one they should almost always take first: the modular monolith. You keep the operational simplicity of a single deployable, but inside it you enforce clear module boundaries — well-defined interfaces, isolated data, one module not reaching into another's internals. It is one application, but organized as if it might one day come apart.
This gets you most of what people actually want from microservices — clear ownership, understandable boundaries, code that is easier to reason about — without a byte of network tax. And it is the honest preparation for a later split: if you draw the module lines well and a genuine reason to extract a service later appears, that module lifts out along a boundary that already exists. If you cannot cleanly modularize inside a single process, where it is easy, you have no business trying to do it across a network, where it is hard. The modular monolith is both the destination for most teams and the on-ramp for the few who will genuinely need more.
Extract a service only along a real seam
When should you actually pull a service out? Only when a concrete, present reason appears — not a hypothetical future one. There are two that hold up. The first is organizational: multiple teams are stepping on each other, blocked on shared code and coupled releases, and giving a team its own deployable would genuinely unblock them. The second is technical: one component has scaling or resource needs so different from the rest that isolating it is the clean solution — a heavy background processor that should not compete with your web traffic, for instance.
When one of those is real, extract that one service, along the seam the reason defines, and no further. Resist the urge to split everything at once because you are already in there. Every service you carve off adds tax; carve off only the ones a real problem is asking you to. A system of one monolith and two deliberately extracted services is a perfectly healthy architecture, and far healthier than twenty services split on a diagram.
Conway's law, and deciding honestly
There is an old observation, Conway's law, that organizations ship systems shaped like their own communication structure. It is not a curiosity, it is a design constraint: if you impose a microservices architecture on a team that does not have the independent, well-bounded teams to match, you will get services that are technically separate but constantly forced to coordinate — the cost of microservices with none of the benefit. Architecture and org chart have to move together, or the architecture loses.
The honest version of this decision is also reversible in one direction only, which is worth weighing. Going from a modular monolith to services later is a controlled extraction along lines you already drew. Going the other way — consolidating a sprawl of premature services back into something coherent — is a painful, expensive project that few teams undertake, so they live with the sprawl instead. When the choice is that asymmetric, the conservative default earns its keep: start simpler than you think you need, and split when reality, not anticipation, demands it.
So the honest way to decide starts with your organization, not your code. How many teams do you have, and are they actually blocked on each other? Does any component truly need to scale on its own? If the honest answers are one or two teams and no real scaling divergence, the microservices question answers itself: not yet, and maybe not ever — build the modular monolith and revisit when a real reason arrives. If you are genuinely unsure, that uncertainty is itself the signal to talk it through with someone who has paid the distributed-systems tax before deciding to sign up for it.