Microservices Anti-Patterns
What NOT to do when building microservices - Common mistakes and how to avoid them
Quick Summary (30 seconds):
The most dangerous anti-patterns: Shared Database, Distributed Monolith, Nano-services (too fine-grained), Sync by Default, No Circuit Breaker, Ignoring Fallbacks, No Observability. Avoid these to prevent distributed system chaos.
The most dangerous anti-patterns: Shared Database, Distributed Monolith, Nano-services (too fine-grained), Sync by Default, No Circuit Breaker, Ignoring Fallbacks, No Observability. Avoid these to prevent distributed system chaos.
Anti-Pattern 1: Shared Database
The Problem
Order Service ----\
|--- Single Database
Payment Service ---/
Symptoms:
- Services cannot scale independently
- Schema changes affect all services
- Single point of failure (database goes down = everything down)
- Cannot choose different database technologies
- Tight coupling between services
Solution:
- Database per Service pattern
- Use events for data synchronization
- Each service owns its data
- Services communicate via APIs, not direct database access
Anti-Pattern 2: Distributed Monolith
The Problem
Services are split technically, not by business capability. Deployed separately but must be upgraded together.
Synchronous call chain: Service A -> Service B -> Service C -> Service D -> Service E Symptoms: - Changing one service requires changing 5 others - Cannot deploy independently - High network latency (worst of both worlds)
Symptoms:
- Multiple services must be deployed together
- Services are not independently deployable
- Long synchronous call chains
- Network latency + coordinated deployments = nightmare
Solution:
- Proper bounded contexts using DDD
- Use async communication where possible
- Decompose by business capability, not technical layers
- Ensure each service can be deployed independently
Anti-Pattern 3: Nano-services (Too Fine-Grained)
The Problem
Creating too many tiny services - each with only 2-3 endpoints.
Example: 200 services for a simple e-commerce site Single request calls 15 different services Network overhead: 15 x (latency + serialization)
Symptoms:
- High network overhead
- Increased latency (each call adds 10-50ms)
- Complex debugging across dozens of services
- Difficult to maintain data consistency
- Operations nightmare
Solution:
- Find the right service size - not too big, not too small
- Start with larger services, split when needed
- Rule of thumb: a team of 3-6 developers per service
- Each service should have meaningful business value
Anti-Pattern 4: Sync by Default
The Problem
Using synchronous HTTP calls for everything, even when async would work better.
Service A --HTTP--> Service B --HTTP--> Service C
| |
v v
Service D Service E
Total response time = sum of all service times
Symptoms:
- High latency accumulating across service chain
- Tight coupling between services
- Cascading failures (one slow service slows everything)
- Poor scalability
Solution:
- Use async messaging for non-critical operations
- Event-driven architecture
- Message queues (Kafka, RabbitMQ, SQS)
- Only use sync when immediate response is needed
Anti-Pattern 5: No Circuit Breaker
The Problem
Not implementing circuit breakers for service-to-service calls.
Payment Service fails (slow)
-> Order Service waits for timeout (30 seconds)
-> All threads blocked
-> Order Service fails
-> API Gateway waits
-> All users see errors
Symptoms:
- Cascading failures across services
- Thread pool exhaustion
- Slow degradation instead of fast failure
- Entire system can go down from one bad service
Solution:
- Implement Circuit Breaker pattern
- Use libraries: Hystrix, Resilience4j, Polly
- Configure timeouts and fallbacks
- Monitor circuit breaker states
Anti-Pattern 6: Ignoring Fallbacks
The Problem
No plan for when a service call fails - user just sees an error.
Bad approach:
Payment fails -> Order fails -> User sees "Error 500"
Better approach:
Payment fails -> Mark order as "Payment Pending"
-> Notify user, retry later
-> Show cached/partial data
Symptoms:
- Poor user experience
- Complete failure instead of graceful degradation
- No retry mechanism for transient failures
Solution:
- Always provide fallback responses
- Implement retry with backoff
- Cache data when possible
- Design for partial availability
Anti-Pattern 7: No Observability
The Problem
Deploying microservices without proper monitoring, logging, and tracing.
Monolith debugging: grep error.log Microservices debugging (without observability): Search 50 services x 10 instances Different log formats No request tracing
Symptoms:
- Cannot debug production issues
- No visibility into service dependencies
- Don't know which service is slow or failing
- Blind deployments
Solution (the three pillars):
- Logging: Centralized log aggregation (ELK, Loki)
- Metrics: Prometheus + Grafana
- Tracing: Distributed tracing (Jaeger, Zipkin)
- Health checks and readiness probes
Anti-Pattern 8: Versioning Ignorance
The Problem
Making breaking API changes without versioning strategy.
Problem: Service A uses API v1 of Service B Service C uses API v2 of Service B Cannot upgrade all services simultaneously Result: Coordinated deployments or breaking changes
Symptoms:
- Cannot deploy services independently
- Breaking changes cause production failures
- Forced coordinated upgrades
Solution:
- API versioning in URL (/v1/resource, /v2/resource)
- Consumer-driven contracts (Pact)
- Keep backward compatibility for at least one version
- Deprecation period with clear communication
Anti-Pattern 9: No Idempotency
The Problem
APIs that are not idempotent, causing duplicate operations on retry.
Without idempotency: User clicks "Pay" -> Network timeout -> User clicks again Result: User charged TWICE With idempotency: Each request has unique id -> Server processes once Result: User charged once
Symptoms:
- Duplicate charges, orders, or records
- Data inconsistency after retries
- Cannot safely retry failed operations
Solution:
- Idempotency keys for all write operations
- Client generates unique request ID
- Server stores processed IDs in database
- Safe retry mechanism
Anti-Pattern 10: No Platform Team
The Problem
Each service team builds their own infrastructure (service discovery, logging, tracing).
Symptoms:
- Inconsistent implementations across teams
- Reinventing the wheel for each service
- High cognitive load on developers
- Cannot debug across services
Solution:
- Dedicated platform team for infrastructure
- Provide standardized frameworks and libraries
- Service mesh (Istio, Linkerd) for cross-cutting concerns
- Internal developer platform
Quick Reference: Anti-Patterns Summary
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Shared Database | Tight coupling, no independence | Database per Service |
| Distributed Monolith | Coordinated deployments + network latency | Proper bounded contexts |
| Nano-services | Too many services, high overhead | Find right service size |
| Sync by Default | High latency, cascading failures | Use async where possible |
| No Circuit Breaker | Cascading failures | Implement circuit breaker |
| Ignoring Fallbacks | Poor user experience | Always provide fallbacks |
| No Observability | Cannot debug or monitor | Logging, Metrics, Tracing |
| Versioning Ignorance | Breaking changes cause failures | API versioning + contracts |
| No Idempotency | Duplicate operations on retry | Idempotency keys |
| No Platform Team | Inconsistent infrastructure | Dedicated platform team |
The Worst Anti-Pattern of All: Premature Microservices
"Starting with microservices when you don't need them. The cost, complexity, and operational overhead are massive. Start with a well-structured monolith and extract services only when you feel the pain that microservices solve. Premature microservices are worse than no microservices."
"Starting with microservices when you don't need them. The cost, complexity, and operational overhead are massive. Start with a well-structured monolith and extract services only when you feel the pain that microservices solve. Premature microservices are worse than no microservices."
Self-Assessment Checklist:
[ ] Do services share a database? (Shared Database anti-pattern)
[ ] Do you need to deploy multiple services together? (Distributed Monolith)
[ ] Do you have more than 5 services per developer? (Nano-services)
[ ] Are all calls synchronous? (Sync by Default)
[ ] Do you have circuit breakers implemented? (No Circuit Breaker)
[ ] Do all API calls have fallbacks? (Ignoring Fallbacks)
[ ] Do you have centralized logging and tracing? (No Observability)
[ ] Do you use idempotency keys? (No Idempotency)
Expert Insight:
"The biggest anti-pattern is the distributed monolith. You get the worst of both worlds: network latency AND coordinated deployments. If your 'microservices' share a database, you don't have microservices - you have a distributed monolith with extra network calls."
"The biggest anti-pattern is the distributed monolith. You get the worst of both worlds: network latency AND coordinated deployments. If your 'microservices' share a database, you don't have microservices - you have a distributed monolith with extra network calls."
Another Critical Insight:
"Fallbacks are not optional. Every service dependency must answer: 'What happens when this call fails?' If you can't answer that question, you're not production-ready. A system without fallbacks is a house of cards."
"Fallbacks are not optional. Every service dependency must answer: 'What happens when this call fails?' If you can't answer that question, you're not production-ready. A system without fallbacks is a house of cards."
Key Takeaways
- Shared Database is the #1 anti-pattern - Database per Service is mandatory
- Distributed Monolith gives you the worst of both worlds
- Too small services (nano-services) create more problems than they solve
- Circuit breakers and fallbacks are not optional - they are mandatory
- Observability (logs, metrics, traces) must be built from day one
- Idempotency is required for all write operations
- Don't start with microservices unless you have a proven need
- If services share a database, you don't have microservices
Final Advice:
"Start with a monolith. Seriously. Extract services only when you have proven need - different scaling requirements, different teams, or different tech stacks. When you do migrate, avoid these anti-patterns at all costs. They have destroyed many microservices projects before they even had a chance to succeed."
"Start with a monolith. Seriously. Extract services only when you have proven need - different scaling requirements, different teams, or different tech stacks. When you do migrate, avoid these anti-patterns at all costs. They have destroyed many microservices projects before they even had a chance to succeed."
Quick Reference Card:
Aspect Pro Con Anti-Pattern Deployment Independent Complex orchestration Distributed monolith Data Technology choice Consistency challenges Shared database Scale Elastic per service Network overhead Nano-services Resilience Fault isolation Partial failures No circuit breaker Performance Parallel processing Network latency Sync by default Debugging Small codebase Distributed traces No observability