"Should we go microservices?" is one of the most consequential — and most misunderstood — architectural decisions. Pick microservices too early and you'll spend years debugging distributed systems instead of building features. Pick monolith too long and your team grinds to a halt as everyone steps on each other's deploys. The right answer depends entirely on team size, complexity, and operational maturity.
Most teams who say "we're moving to microservices" should actually build a modular monolith first. Most teams already on microservices wish they had started with one.
02
The spectrum
It's not a binary. Real architectures live on a spectrum:
Big-ball-of-mud monolith — single deploy, no internal boundaries. The horror story. What every monolith becomes if undisciplined.
Modular monolith — single deploy, but enforced internal module boundaries (separate packages, no cross-imports, talks via interfaces). 80% of microservices benefits, 5% of the cost.
Service-oriented (a few services) — 3-10 services aligned to subdomains. Most companies' sweet spot.
Microservices (many small services) — 50+ services. Only justifies itself at FAANG scale or in genuinely independent product teams.
Nanoservices / Lambda functions — every endpoint is its own deployment. Operational nightmare. Almost always wrong.
03
What microservices actually buy you
Independent deploy. Team A ships without coordinating with Team B. The biggest real benefit.
Independent scaling. The recommendation service needs 10× the capacity of the user service. Scale just it.
Tech heterogeneity. Use Rust for the latency-critical service, Python for the ML service. Each team picks.
Fault isolation. One service crashing doesn't take everything down (with proper circuit breakers).
Team autonomy. Conway's Law says your architecture mirrors your org. Microservices align technical boundaries with team boundaries.
04
What microservices cost you
The hidden taxes nobody talks about until they're paying them:
Network calls everywhere. What was a function call is now an HTTP/gRPC request. 1ms becomes 10ms. Failures everywhere.
Operational complexity. 50 services × 3 environments × 5 dependencies each = thousands of deployment artifacts. Need service mesh, distributed tracing, centralized logging.
Schema coordination. Service A depends on Service B's API. B changes the API → A breaks. Now you need API versioning, backwards compatibility forever, contract tests.
Local development hell. "To run my service I need to spin up 12 dependencies." Hours of setup; productivity drops.
Cost. Each service has overhead — its own pod, its own DB connections, its own monitoring, its own on-call rotation.
Conway's Law in reverse
If you have 5 engineers and you build 50 microservices, you're guaranteed to fail. Each service needs ~2-5 engineers to maintain it well. Don't build more services than you have teams.
05
Deep dive — the modular monolith middle path
The pattern most successful product teams converge on:
Single deployable artifact (one Docker image, one fleet, one DB connection pool).
Internally organized into modules that mirror your bounded contexts — orders, users, payments, inventory.
Modules can only call each other through explicit interfaces — never direct DB joins, never importing internal classes.
Each module owns its tables; cross-module queries go through the owning module's interface.
Linter/CI enforces the boundaries: "orders module cannot import payments.internal.*"
You get 80% of the architectural benefits (clear boundaries, replaceable modules, isolated changes) without the operational cost. When a module genuinely needs independent scaling or its own deploy cadence, you can extract it to a service — and the work is straightforward because the boundaries are already clean.
Shopify's monolith — millions of lines of Ruby — runs this way. So does Stripe's main API. Both could afford full microservices; both deliberately avoided it.
The Three StagesMermaid
flowchart LR
A["Big-ball-of-mud monolith"]
B["Modular monolith (80% of teams should be here)"]
C["Microservices (only at scale)"]
A -->|refactor| B
B -->|extract when justified| C
style A fill:#fce8e6,stroke:#8b1a12
style B fill:#e4f5ec,stroke:#1a5c38
style C fill:#fff7d4,stroke:#8b6914
Interview answer
"For a team of N engineers I'd start with a modular monolith — single deploy, enforced module boundaries aligned to bounded contexts. Extract a service only when there's a concrete forcing function: independent scale, separate language, or genuine team-of-teams autonomy."
06
Real-world
Shopify
Modular Ruby monolith
Millions of LOC, billions in GMV. Deliberately monolithic with strict module boundaries. Extracted only a handful of services (Tax, Identity).
Stripe
Mostly monolith
Most of Stripe's API is one Ruby service. Specific concerns (Radar, Connect) are separate. Strong internal libraries enforce boundaries.
Amazon
Microservices done right
The "two-pizza team" rule + every team owns a service end-to-end. ~10,000 services. Forced by org scale, not preference.
Netflix
Pure microservices
Pioneered the pattern. Hundreds of services. Justifies the cost because of streaming-scale ops + independent product teams.
07
Used in problems
E-commerce architectures choose modular monolith vs services per domain. News feed at scale uses microservices for ranker / feed-store / fan-out independently. Uber's microservice fleet is the textbook example of the pattern at scale.