Skip to content
Architecture

Microservices: When They Actually Earn Their Keep

The three legitimate reasons to go distributed, the rent you pay from day one, and the survival rules — few and chunky, contracts not shared databases, async by default, platform before proliferation.

5 min read Updated Sep 2, 2026
Microservices: When They Actually Earn Their Keep

I wrote a whole love letter to monoliths, so let me balance the ledger: I've also built microservices on purpose, more than once, and would again. The trick is that "should we do microservices?" is the wrong question. The right question is "which specific problem do we have that distribution solves?" — because distribution solves exactly three problems, and it charges rent for all of them.

Microservices: team boundaries, service contracts, independent deploys, and the distributed cost

The three legitimate reasons

1. Independent team velocity (the big one)

When you have, say, forty engineers in six teams and every release train collides in one pipeline, the coordination tax gets real: release freezes, merge queues, "who broke staging" archaeology. Services with owned contracts let team A deploy eleven times on Tuesday while team B deploys nothing all week — and neither notices the other. This is the reason that actually justifies most successful microservice adoptions. Notice it's an organizational reason. If your whole engineering org fits in one meeting room, you don't have this problem, and services won't give you velocity — they'll take it.

2. Genuinely divergent runtime needs

The ML inference service needs GPUs and Python; the API is .NET; the image processor needs 16GB of RAM for ninety seconds at a time. Forcing these into one deployable is awkward in real, technical ways. Extracting along runtime seams is cheap and uncontroversial — this is also how a "monolith plus three services" architecture quietly forms, and it's excellent.

3. Independent scaling and failure isolation

The report generator that pegs CPU shouldn't share a process with checkout. Bulkheading a risky or spiky workload behind a queue and its own service means Black Friday checkout latency doesn't care that someone exported a year of analytics. (Honorable mention: compliance isolation — sometimes the payment-card scope is legally happier as its own small, auditable service. Ask your QSA before your architect.)

The rent you will pay

Every one of these costs arrives on day one, whether or not the benefits do:

  • The network is now in your business logic. Every cross-service call needs timeouts, retries with backoff, circuit breakers, and an answer to "what if this succeeds but I never hear back?" — which is how you end up needing idempotency keys internally, not just at your public edge.
  • Transactions are gone. "Reserve stock, charge card, create order" is no longer atomic. Sagas, outbox tables, compensations — all buildable, none free, and every one of them is a place where a partial failure needs a designed answer instead of a rollback.
  • Data gets duplicated. Each service owns its data (if it doesn't, you've built a distributed monolith — all the rent, none of the benefits). Which means the order service keeps a copy of customer names, and now there's an eventing pipeline keeping copies fresh, and a Slack channel where someone asks why they're not.
  • Observability becomes mandatory infrastructure. With a monolith, tracing is nice. With services, distributed tracing, centralized logging with correlation IDs, and per-service dashboards are the difference between a ten-minute incident and a ten-hour one.
  • Local development gets weird. "Run the app" becomes "run these nine containers, or hit the shared dev environment, which is down."

If you do it: rules that keep it survivable

  1. Extract from a working system, along proven seams. The monolith teaches you where the real boundaries are — they're the module edges that never chat. Greenfield microservices means guessing boundaries with the least information you'll ever have.
  2. Few and chunky beats many and chatty. The failure mode isn't "too few services", it's a hundred nano-services where one user click fans out into thirty RPCs. Start with services that map to team-sized business capabilities: billing, not invoice-line-item-tax-calculator.
  3. Contracts are APIs, not shared databases. One service reaching into another's tables re-couples everything you just paid to decouple. gRPC or REST, versioned deliberately (additive-first), with consumer-driven contract tests in CI.
  4. Async by default between domains. Publish events for "this happened"; reserve synchronous calls for "I need an answer to proceed". Every sync call is availability math: five services at 99.9% chained together is 99.5% — you lose half a nine per hop.
  5. Platform before proliferation. Before service #4 exists, you want templated service scaffolding, standardized deploys (this is where Kubernetes genuinely earns its complexity), and golden-path observability. Otherwise every service is a snowflake.

The decision, compressed

Count your teams, not your ambitions. One or two teams: modular monolith, extract hot spots only. Three-plus teams genuinely blocking on each other, or hard runtime divergence, or a workload that needs bulkheading: extract deliberately, few and chunky, with the platform work budgeted in. And if someone proposes microservices "for scale" without naming the bottleneck — send them the monolith piece first. It's cheaper than the alternative education.

Standing at this exact fork — or halfway through a migration that stalled? This is the most common architecture review I run, and the second opinion usually pays for itself in deleted services.

Keep reading

Related articles

Architecture 4 min read

Clean Architecture Meets a Real Deadline

The one load-bearing idea under all the concentric circles, the interface-with-one-implementation tax, and the three-folder dose that keeps domains pure without nine-file one-line changes.