System Design · Lesson 12 of 26
Microservices — What They Are and What They Cost
Microservices are simultaneously one of the most powerful architectural patterns and one of the most over-applied. Teams adopt them to solve scalability problems, then find themselves fighting distributed systems complexity instead.
This article gives you a realistic picture of what microservices are, what they cost, and when to actually use them.
What Microservices Actually Are
A microservice is an independently deployable service that owns a single business capability and its data.
The key words are:
- Independently deployable — you can deploy the Order service without touching the User service
- Single business capability — not "the notification code" or "all database operations", but "manage customer orders"
- Owns its data — the Order service has its own database that no other service touches directly
Monolith:
┌─────────────────────────────────────────────────────┐
│ E-commerce App │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Orders │ │ Products │ │ Users │ │Payments│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ │
│ └────────────┴────────────┴────────────┘ │
│ Shared Database │
└─────────────────────────────────────────────────────┘
Microservices:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐
│ Orders │ │ Products │ │ Users │ │Payments │
│ Service │ │ Service │ │ Service │ │ Service │
│ [DB] │ │ [DB] │ │ [DB] │ │ [DB] │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘
└─────────────┴─────────────┴──────────────┘
Message Bus / API callsThe Two-Pizza Rule
Jeff Bezos's rule of thumb: a microservice team should be small enough to be fed by two pizzas (6-8 people).
This isn't really about pizza — it's about cognitive load and communication overhead. A small, autonomous team can:
- Own their service end-to-end
- Deploy independently without coordinating with 10 other teams
- Make technology decisions appropriate for their service
If your microservice requires a 20-person team to operate, it's probably not a microservice — it's a mini-monolith.
Why Companies Move to Microservices
1. Team Autonomy and Parallel Deployment
In a monolith, all teams share one deployment pipeline. The team that deploys a bug fixes blocks everyone's release.
Monolith release train:
Orders team ready ✓
Products team ready ✓
Users team ready — BUG FOUND ✗
→ Everyone waits for Users team to fix it
Microservices:
Orders service deploys independently — done
Products service deploys independently — done
Users service has a bug — only Users is blocked, others unaffected2. Independent Scaling
Different services have different load profiles. Microservices let you scale what needs scaling.
E-commerce peak traffic:
Product Search: 50,000 QPS — needs 20 instances
Order Checkout: 500 QPS — needs 2 instances
Admin Panel: 10 QPS — needs 1 instance
With microservices: scale Product Search to 20 instances
With monolith: must scale the entire app to handle Product Search load
(20 instances of admin panel code nobody uses)3. Technology Flexibility
Different services can use different languages and databases.
- ML inference service in Python (sklearn/PyTorch)
- Core API in .NET/C# (team expertise)
- Real-time service in Go (performance-critical)
- Each using the database best suited to its access patterns
4. Fault Isolation
A crash in the Recommendations service doesn't take down the Order service.
Monolith: memory leak in Recommendations → OOM → entire app dies
Microservices: Recommendations crashes → Order/Checkout still serve trafficWhat Microservices ACTUALLY Cost
This is where most tutorials stop telling the truth. Microservices are not free.
1. Network Latency
Every service call that was a function call is now a network hop.
Monolith:
orderService.getUser(userId) → in-memory call → 0.001ms
Microservices:
GET http://user-service/users/123 → HTTP round trip → 0.5–5ms
(in same datacenter, under normal load)An operation that chains 5 services may add 5–25ms of network latency. At 99th percentile (with retries, timeouts), this compounds significantly.
2. Distributed Transactions
In a monolith, wrapping a multi-step operation in a database transaction is trivial.
// Monolith — dead simple
using var tx = db.BeginTransaction();
inventory.Decrement(productId, quantity);
order.Create(userId, items);
payment.Charge(userId, total);
tx.Commit(); // all or nothingIn microservices, you don't have a shared database or shared transaction:
Create Order involves:
1. Order Service: create order record
2. Inventory Service: decrement stock
3. Payment Service: charge card
4. Notification Service: send confirmation email
What happens if Payment fails after Inventory decremented?
What if Notification fails after Order was created?
→ You need Saga pattern (compensating transactions)
→ This is exponentially more complex3. Operational Complexity
Monolith:
Deploy: 1 thing
Monitor: 1 thing
Debug: 1 log stream
50 microservices:
Deploy: 50 pipelines, dependency ordering, versioning
Monitor: distributed tracing, aggregated logs, service mesh metrics
Debug: a request touching 8 services across 50 log streams
(you need correlation IDs, distributed tracing - OpenTelemetry)You will need:
- Container orchestration (Kubernetes)
- Service discovery
- Distributed tracing (Jaeger, Zipkin, Azure Monitor)
- Centralized logging (ELK, Loki)
- Service mesh (Istio) or at minimum consistent retry/circuit breaker libraries
4. Testing Complexity
Unit testing individual services is easy. But integration testing across services requires:
- Each service running locally or mocked
- Consumer-driven contract tests (Pact)
- End-to-end test environments that mirror production
- Test data management across multiple databases
Test "create order" in microservices:
- Need User Service running
- Need Inventory Service running
- Need Payment Service (mocked or running)
- Need Notification Service (mocked)
- Need a message broker running (Kafka/RabbitMQ)
- Need all their databases seeded
vs. monolith: spin up one process and one database5. The Distributed Systems Fallacies
Peter Deutsch's eight fallacies that developers new to distributed systems always hit:
1. The network is reliable
2. Latency is zero
3. Bandwidth is infinite
4. The network is secure
5. Topology doesn't change
6. There is one administrator
7. Transport cost is zero
8. The network is homogeneousWith a monolith, these fallacies don't bite you — everything is in-process. With microservices, every one of them becomes a real engineering problem.
The Modular Monolith — The Often-Better Alternative
A modular monolith is a single deployable unit organized into strict modules with enforced boundaries.
┌─────────────────────────────────────────────────────┐
│ E-commerce App │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Orders │ │ Products │ │ Users │ │Payments│ │
│ │ Module │ │ Module │ │ Module │ │ Module │ │
│ │ │ │ │ │ │ │ │ │
│ │ Public │ │ Public │ │ Public │ │ Public │ │
│ │ API │ │ API │ │ API │ │ API │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
│ Modules communicate through interfaces only │
│ (no internal class access) │
│ Shared Database │
│ (separate schemas per module) │
└─────────────────────────────────────────────────────┘Benefits you get:
- Team autonomy (each team owns their module)
- Clear boundaries (can extract to microservice later)
- Simpler deployments (one thing to deploy)
- Simpler transactions (shared DB)
- Simpler testing
What you sacrifice:
- Independent scaling per module
- Technology freedom
- Fault isolation (one crash still takes down everything)
The modular monolith is often the right choice for teams under ~30 engineers, or when the business domain isn't fully understood yet (premature service splitting is painful).
The Strangler Fig Pattern — Migrating to Microservices
The strangler fig tree grows around another tree, slowly replacing it. The pattern mirrors this.
Phase 1: Monolith handles all traffic
Client → API Gateway → Monolith → DB
Phase 2: New service handles one capability
Client → API Gateway → /users/* → User Service (new)
→ /* → Monolith (legacy)
Phase 3: More capabilities migrated
Client → API Gateway → /users/* → User Service
→ /products/* → Product Service
→ /orders/* → Monolith (shrinking)
Phase 4: Monolith deprecated
Client → API Gateway → routes all to microservicesKey: the API Gateway (or a proxy like YARP) routes traffic. You never do a big-bang migration. The monolith shrinks slowly as each capability is extracted.
When NOT to Use Microservices
Be honest about these signals:
- Team is fewer than 10 engineers — operational overhead isn't worth it yet
- Domain is not well understood — premature splitting creates wrong boundaries (a distributed monolith is worse than a monolith)
- No DevOps capability — without Kubernetes, CI/CD pipelines, and observability tooling, microservices are unmanageable
- No clear scalability need — don't optimize for problems you don't have
- Startup / MVP phase — move fast with a monolith; extract services when you understand what needs to scale
The truth: most systems don't need microservices. What they need is a well-organized codebase with clear module boundaries — and the option to extract services if the need arises.
Key Takeaways
- A microservice is independently deployable, owns a single business capability, and has its own database.
- Teams move to microservices for: team autonomy, independent scaling, tech flexibility, and fault isolation.
- Real costs: network latency per call, distributed transaction complexity (Saga), massive operational overhead, testing complexity.
- The distributed systems fallacies will all bite you — plan for network failures, not just happy paths.
- Modular monolith often gives you most of the benefits with far less complexity. Start here.
- Migrate incrementally using the strangler fig pattern — never do a big-bang migration.
- Don't use microservices if: small team, unknown domain, no DevOps capability, no clear scale need, or MVP stage.