Q47 — Why do microservices exist — and when are they the wrong choice?
What came before microservices?
Before microservices, the dominant architecture was the monolith: one codebase, one deployable artifact, one process. The entire application — user auth, billing, recommendations, notifications, search — lives in a single binary that you build and deploy as a unit.
This isn't naive or lazy. A monolith is simpler to develop, simpler to debug, and simpler to operate. Function calls are cheap. Transactions are local. You don't need to worry about network failures between your billing code and your auth code because they're in the same process. For a small team building a new product, a monolith is almost always the right choice.
So what goes wrong?
What breaks in a monolith as the codebase grows?
Deployment coupling. In a monolith, deploying any change means deploying the entire application. A one-line fix to the billing module requires rebuilding and redeploying everything — the recommendation engine, the auth service, the email templates, all of it. If the billing change causes a bug, the rollback takes everything with it. The team that owns recommendations can't ship their feature until the billing bug is fixed.
Scaling coupling. Suppose your recommendation engine is CPU-intensive, but your auth service is mostly I/O-bound. In a monolith, you scale both together by adding more instances of the whole application. You can't provision 10 recommendation servers and 2 auth servers — you provision N copies of the whole thing. This is wasteful and often architecturally impossible if different components have different resource profiles.
Team coupling. As the team grows from 3 engineers to 30, the monolith becomes a contention point. Multiple teams write to the same codebase. Every merge involves conflicts. Every deployment requires coordination. Every team's release schedule is blocked by every other team's readiness. The codebase becomes a shared resource with high coordination overhead.
Technology coupling. The entire monolith runs in one language with one set of libraries. If one component would benefit enormously from a different language, runtime, or framework, you can't adopt it without migrating the whole system. The monolith's technology stack is a global constraint on every team.
These couplings compound as the organization scales. At some size, the coordination overhead of a monolith starts costing more than the operational complexity of breaking it apart.
What does microservices actually mean?
A microservice is a small, independently deployable service that owns a bounded piece of functionality — and critically, owns its own data store. "Bounded" is the key word. A microservice should have a clear, narrow responsibility: the order service manages orders, the inventory service manages stock levels, the notification service sends notifications.
The key properties:
Independent deployability. You can deploy the order service without touching the inventory service. Teams own their service end-to-end — they write it, deploy it, monitor it, and are on-call for it. No cross-team coordination required for a release.
Independent scalability. The recommendation service gets 10 machines; the auth service gets 2. Each service scales based on its own resource profile and traffic pattern.
Isolated failure. If the recommendation service crashes, auth and billing continue working. The failure is contained to the service that owns it. A monolith crash takes everything down.
Technology heterogeneity. The notification service is in Go, the ML ranking service is in Python, the billing service is in Java. Each team picks the right tool for their problem.
Communication between services happens over the network — HTTP/REST, gRPC, or messages through a queue. This is both the central benefit (decoupling) and the central cost (network calls fail, are slow, and require explicit handling).
What breaks that didn't break before?
When you split a monolith into services, you trade in-process function calls for network calls. This changes everything.
Distributed transactions become hard. Creating an order used to involve calling a function that wrote to two tables in one transaction — if anything failed, you rolled back. Now "create an order" means calling the order service, which calls the inventory service, which calls the payment service. If the payment call fails after inventory was decremented, you have an inconsistency. There's no distributed ACID transaction that spans all three services' databases. You need compensating logic (sagas — see Q50), eventual consistency, or careful idempotency design.
Network latency replaces function call latency. A function call costs nanoseconds. A network call costs milliseconds. A chain of 5 synchronous service calls in a user request can add 50–100ms of latency before you've even done any real work.
Operational complexity explodes. One monolith = one deployment, one log stream, one set of metrics. Fifty microservices = fifty deployments, fifty log streams, fifty dashboards, fifty alert channels. You need service discovery, distributed tracing, centralized logging, circuit breakers, and a sophisticated deployment platform (Kubernetes or similar) just to keep track of what's happening.
Testing becomes harder. Unit testing a monolith is straightforward. Integration testing fifty services with real network calls, varying latencies, and partial failures requires a sophisticated test environment.
When are microservices the wrong choice?
Microservices are an organizational solution to an organizational problem. The problem they solve is that large teams can't move independently when they're all coupled to a single codebase and deployment pipeline.
If you don't have that problem — you're a small team, you ship fast, coordination overhead is low — microservices impose all the costs with none of the benefits. You get distributed transaction complexity, network failure handling, operational overhead, and inter-service latency without the independent deployability and team autonomy that justify them.
The failures are consistent: teams adopt microservices on day one for a new product (no team coupling problem yet), spend months on infrastructure instead of features, struggle with distributed transactions for simple CRUD operations, and end up with a "distributed monolith" — all the services are still tightly coupled to each other's APIs and must be deployed together anyway.
The honest rule: start with a monolith. Extract services when a specific coupling problem becomes concrete and costly — when a team's deployment is actually being blocked, when a component's scale requirements are actually diverging, when a technology choice is actually being constrained. Extract services around business boundaries (domains), not technical boundaries.
The mental model
Monolith:
One codebase, one deployable, one process
In-process function calls, local transactions, single DB
Simple to develop, debug, operate — RIGHT default for small teams
Breaks as scale grows:
Deployment coupling: one change → redeploy everything → team blocks team
Scaling coupling: can't scale components independently
Team coupling: shared codebase → coordination overhead → slow shipping
Technology coupling: one stack for everything
Microservices:
Small, independently deployable services, each owning its data
Communicate over network (HTTP, gRPC, queues)
Gains:
Independent deployability: team ships without coordinating
Independent scalability: scale each service by its profile
Isolated failure: one service down ≠ everything down
Technology heterogeneity: right tool per service
New costs:
Distributed transactions: no ACID across services → sagas, outbox
Network latency: ms vs ns — chains of calls add up
Operational complexity: 50 services × (deploy + monitor + trace + alert)
Testing complexity: integration tests across real network calls
When to use:
Team coupling is ACTUALLY hurting velocity → extract service at that boundary
Scale requirements ACTUALLY diverge → extract the component that needs different resources
When NOT to use:
Small team, new product, coordination overhead is low
"We might need it someday" — premature optimization kills early products
Rule: monolith first, extract services when the coupling pain is concrete and measurable
Microservices don't make software better — they make large organizations able to ship independently. The complexity they introduce is justified only when the organizational coupling they eliminate is more expensive. Before you have the coupling problem, you only have the cost.