When to Use Microservices
Context
Section titled “Context”You can define microservices and you can recite their trade-offs. Now comes the decision that actually matters on a real project: should this system be a microservices architecture, and if so, when? This is where good engineering judgment shows, because the wrong answer in either direction is expensive. Decompose too early and you pay distributed-system costs before you have the scale to justify them; decompose too late and you fight a monolith that has become a bottleneck for a large team.
Problem
Section titled “Problem”There is no universal rule, but there are signals. The mistake is to decide based on fashion (“everyone uses microservices now”) or fear (“our monolith feels messy, so it must be wrong”). A messy monolith is usually a sign you need cleaner internal boundaries, not necessarily a network between your modules.
So the question is: which concrete factors should push you toward microservices, and which should keep you on a monolith?
Solution
Section titled “Solution”Weigh three factors together — they reinforce each other, and no single one is decisive on its own:
- Team size. A handful of developers ships fastest on one codebase. Once you have many teams contending for one codebase and one deploy pipeline, the coordination cost rises, and giving each team its own deployable service can remove the contention.
- Domain complexity. A small, well-understood domain rarely needs splitting. A large domain with clear sub-areas that evolve at different rates and have natural boundaries is a candidate for decomposition along those boundaries.
- Scaling needs. If the whole system scales fine as one unit, you do not need to split it to scale. If specific components have very different load profiles — one is CPU-bound, another is memory-bound, another gets ten times the traffic — independent scaling becomes a real reason to separate them.
The decision flow below walks these signals in order. Notice how many paths end at “monolith”: that is intentional.
flowchart TB
Start([New or growing system]) --> Q1{Large team blocked<br/>on one codebase or<br/>one pipeline?}
Q1 -- No --> Mono[Build / keep a<br/>modular monolith]
Q1 -- Yes --> Q2{Domain has clear,<br/>independent<br/>sub-areas?}
Q2 -- No --> Mono
Q2 -- Yes --> Q3{Components have<br/>very different<br/>scaling needs?}
Q3 -- No --> Q4{Operational maturity:<br/>CI/CD, automation,<br/>observability in place?}
Q3 -- Yes --> Q4
Q4 -- No --> Mono2[Invest in automation<br/>first; decompose later]
Q4 -- Yes --> Micro[Decompose into<br/>microservices] Monolith first
Section titled “Monolith first”There is a strong default here, often called monolith first: start with a well-structured monolith, get the product right, and learn where the real boundaries in your domain are. A young product’s boundaries are guesses, and guessing them wrong is far cheaper to fix inside one codebase than across a network of services. As the system grows and you observe genuine pain — a team blocked on a shared deploy, a component that must scale on its own — you extract that piece into a service. You decompose along boundaries you have discovered, not boundaries you imagined on day one.
This does not mean a sloppy monolith. The asset that makes later extraction cheap is a modular monolith: one with clean internal module boundaries, so that pulling a module out into a service is a mechanical refactor rather than untangling a knot.
Resulting context
Section titled “Resulting context”When the three factors line up — a large organization, a complex domain with natural seams, and components with divergent scaling needs — and you have the operational maturity to run many services, microservices are likely to pay off. When they do not line up, a modular monolith is almost always the better, cheaper, faster choice, and it keeps the option to decompose later open.
The reassuring truth is that this is rarely an irreversible, all-or-nothing decision. You can start as a monolith and peel off services one at a time as the need becomes real — extracting the highest-pain component first and leaving the rest in the monolith for as long as it serves you.
Related patterns
Section titled “Related patterns”- Benefits and Drawbacks — the ledger this decision balances.
- Monolith vs Microservices — the two options the flow chooses between.
- The Pattern Language — what to learn once you have decided to decompose.
| Use microservices when | Don’t when |
|---|---|
| The team is big enough (8+) to split into squads | The team is smaller than 5 |
| The domain is clear — you know each service’s boundary | The domain isn’t well understood yet — boundaries keep shifting |
| You have DevOps maturity — CI/CD, monitoring, Kubernetes | You don’t have that infrastructure yet |
| Scaling needs differ sharply between components | Traffic scales about the same across every part of the system |
| Compliance requires isolating data | You need strong consistency across the whole system |
Common mistakes
Section titled “Common mistakes”Microservices as the default architecture — starting every new project with microservices. Symptoms:
- The first sprint is spent setting up Kubernetes, a service registry, and a CI/CD pipeline.
- The first feature still isn’t built.
- Use a monolith or modular monolith, and extract when the time comes.
Migrating because a competitor did — adopting microservices because Netflix or Amazon uses them. Symptoms:
- Ignoring the very different context — Netflix has thousands of engineers.
- The complexity isn’t worth it at your actual scale.
- “If Netflix can do it, so can we” — but Netflix’s migration took 7 years.
💡 Real-world examples
Basecamp / Hey: has run on a Rails monolith throughout, scaling well enough for its size of business. DHH (Rails’ creator) wrote “The Majestic Monolith” (2017), arguing that most web apps should stay a well-structured monolith for their entire lifespan rather than splitting into microservices (Signal v. Noise: The Majestic Monolith).
Shopify: started as a Rails monolith and evolved into a modular monolith with clear boundaries between components — it split out the Storefront Renderer specifically because that read-only path has very high throughput and needed to scale and deploy separately from the merchant-facing admin (Shopify Engineering: Under Deconstruction — The State of Shopify’s Monolith).