How to Avoid Microservices Anti-Patterns: Architecture Decision Guide
Identify and fix the 8 most common microservices mistakes. Covers distributed monoliths, service granularity, data ownership, and when NOT to use microservices.
Microservices are not inherently good architecture. They’re a trade-off — you gain deployment independence at the cost of operational complexity. The anti-patterns below are the most common reasons microservices projects fail.
Anti-Pattern 1: The Distributed Monolith
Symptom: All services must be deployed together. Changing one service breaks others. Teams can’t ship independently.
Root Cause: Services share databases, deploy in lockstep, or communicate via synchronous chains.
❌ Distributed Monolith
┌─────┐ sync ┌─────┐ sync ┌─────┐
│ Svc │──────────▶│ Svc │──────────▶│ Svc │
│ A │ │ B │ │ C │
└──┬──┘ └──┬──┘ └──┬──┘
│ │ │
└────────┬────────┘ │
▼ ▼
┌────────────┐ ┌────────────┐
│ Shared DB │ │ Shared DB │
└────────────┘ └────────────┘
Fix: Database-per-service, asynchronous communication, API contracts.
✅ Proper Microservices
┌─────┐ event ┌─────┐ event ┌─────┐
│ Svc │──────────▶│ Svc │──────────▶│ Svc │
│ A │ (async) │ B │ (async) │ C │
└──┬──┘ └──┬──┘ └──┬──┘
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│ DB A │ │ DB B │ │ DB C │
└──────┘ └──────┘ └──────┘
Anti-Pattern 2: Wrong Service Granularity
Too Fine-Grained: Every CRUD operation is a service. You have 200 services for a 10-developer team. Deployment overhead exceeds development capacity.
Too Coarse-Grained: “We split our monolith into 3 services.” The services are still 500K lines of code each. Nothing changed except you added a network call.
The Right Granularity
| Team Size | Service Count | Ratio |
|---|---|---|
| 5-10 devs | 3-8 services | 1-2 services per dev |
| 10-25 devs | 8-20 services | ~1 service per dev |
| 25-50 devs | 15-40 services | Team-aligned services |
| 50+ devs | Domain-count services | Bounded context per team |
:::tip[Two-Pizza Rule Applied] Each service should be owned by a team that can be fed by two pizzas (5-8 people). If a service requires more than that to maintain, it’s too big. If a developer maintains more than 2 services, they’re too small. :::
Anti-Pattern 3: Synchronous Everything
Symptom: Request chains that create 10 network calls before returning a response. Latency is additive across every hop.
# ❌ Synchronous chain — latency = sum of all calls
async def process_order(order):
customer = await customer_svc.get(order.customer_id) # 50ms
inventory = await inventory_svc.check(order.items) # 80ms
pricing = await pricing_svc.calculate(order) # 40ms
payment = await payment_svc.charge(order, pricing) # 200ms
shipping = await shipping_svc.create(order, customer) # 100ms
notification = await email_svc.send(customer, order) # 150ms
# Total: 620ms minimum
Fix: Use events for non-blocking operations.
# ✅ Event-driven — only synchronous for what you need NOW
async def process_order(order):
customer = await customer_svc.get(order.customer_id) # 50ms
inventory = await inventory_svc.check(order.items) # 80ms
pricing = await pricing_svc.calculate(order) # 40ms
payment = await payment_svc.charge(order, pricing) # 200ms
# Total: 370ms
# Everything else happens asynchronously via events
await event_bus.publish("order.created", {
"order_id": order.id,
"customer": customer,
"items": order.items
})
# Shipping, notifications, analytics — all async consumers
Anti-Pattern 4: No API Contract Management
Symptom: Service B deploys a breaking change. Service A discovers it at 3 AM when production breaks.
Fix: Consumer-Driven Contract Testing.
# Pact — consumer-driven contract test
# Consumer side (Service A)
from pact import Consumer, Provider
pact = Consumer('ServiceA').has_pact_with(Provider('ServiceB'))
pact.given('a customer exists') \
.upon_receiving('a request for customer by ID') \
.with_request('GET', '/api/customers/123') \
.will_respond_with(200, body={
'id': '123',
'name': Like('John Doe'),
'email': Like('john@example.com')
})
Anti-Pattern 5: Shared Data Ownership
Symptom: Multiple services write to the same database table. Impossible to know who owns the data.
Fix: Single-writer principle — one service owns each data entity.
| Entity | Owner Service | Read Access | Write Access |
|---|---|---|---|
| Customers | Customer Service | All (via API) | Customer Service only |
| Orders | Order Service | Customer, Shipping | Order Service only |
| Products | Catalog Service | Order, Search | Catalog Service only |
| Payments | Payment Service | Order (via events) | Payment Service only |
Anti-Pattern 6: Missing Observability
You cannot operate what you cannot observe. Every microservice needs:
# OpenTelemetry configuration
exporters:
otlp:
endpoint: "otel-collector:4317"
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
metrics:
receivers: [otlp]
exporters: [otlp]
logs:
receivers: [otlp]
exporters: [otlp]
Three Pillars:
- Traces — Follow a request across all services (Jaeger, Zipkin)
- Metrics — Latency, error rate, throughput per service (Prometheus)
- Logs — Structured, correlated by trace ID (ELK, Loki)
Anti-Pattern 7: Premature Microservices
Symptom: Starting a new project with 15 microservices before product-market fit.
When to Use Microservices
| Signal | Monolith | Microservices |
|---|---|---|
| Team size < 10 | ✅ | ❌ |
| Product is evolving rapidly | ✅ | ❌ |
| You need independent scaling | Consider | ✅ |
| Teams can’t deploy independently | Consider | ✅ |
| Different services need different tech stacks | Consider | ✅ |
| You have DevOps/platform team capacity | Required | ✅ |
:::caution[Start Monolith, Extract Later] The safest path: build a well-structured monolith with clear module boundaries. When specific modules need independent scaling or deployment, extract them into services. This is cheaper and faster than starting distributed. :::
Architecture Decision Checklist
- Can each service be deployed independently?
- Does each service own its own database?
- Is inter-service communication primarily asynchronous?
- Are API contracts tested and versioned?
- Is there a single-writer for each data entity?
- Can you trace a request across all services?
- Is team size sufficient for the number of services?
- Have you validated the need for microservices?
:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For architecture audits, visit garnetgrid.com. :::