12 Microservices Anti-Patterns That Turn Distributed Systems Into Distributed Problems
Microservices are the default architecture recommendation in 2026. But most teams don't build microservices — they build a distributed monolith with extra network hops, eventual consistency headaches, and 10x the operational complexity.
Anti-Pattern #1: The Distributed Monolith
The most common microservices failure mode: you split your codebase into 30 services, but they all need to be deployed together. A change in Service A requires simultaneous changes in Services B, C, and D. You've lost the monolith's simplicity without gaining anything.
Symptoms
- You can't deploy one service without deploying others
- A single API change requires PRs across 5+ repositories
- You have a "deploy order" spreadsheet
- Integration tests take longer than the monolith's tests ever did
- Teams spend more time coordinating than building
Can you deploy Service A on a Tuesday without coordinating with any other team? If no, you have a distributed monolith. Independent deployability is the entire point of microservices. If you don't have it, you're paying the complexity tax without the benefit.
Anti-Pattern #2: Chatty Services
When a single user request triggers a cascade of 15 synchronous service-to-service HTTP calls:
User → API Gateway → Auth Service → User Service → Order Service
→ Inventory Service → Pricing Service → Tax Service
→ Payment Service → Notification Service → Audit Service
Total latency: 15ms + 23ms + 8ms + 45ms + 12ms + 31ms + 67ms + 19ms + 11ms
= 231ms for what was a 25ms database query in the monolith
Each network hop adds latency, failure probability, and debugging complexity. If any service in the chain is slow or down, the entire request fails. You've created a system where uptime is the product of individual service uptimes: 99.9%^10 = 99.0% — a 10x increase in downtime.
The Fix
- Asynchronous communication — use events (Kafka, RabbitMQ) instead of synchronous HTTP for non-critical paths
- Data duplication — let services cache the data they need instead of fetching it every time
- API composition — aggregate responses at the gateway level using BFF (Backend For Frontend) pattern
- Bulk operations — batch requests when possible instead of N+1 service calls
Anti-Pattern #3: The Shared Database
Multiple services reading from and writing to the same database tables. This creates hidden coupling — any schema change affects every service that shares those tables.
# Service A writes:
INSERT INTO orders (id, user_id, total, status) VALUES (...)
# Service B reads:
SELECT total FROM orders WHERE status = 'pending'
# Service A renames 'total' to 'subtotal'...
# Service B breaks at 3am on Saturday 💥
Database-Per-Service Rules
| Pattern | When to Use | Trade-Off |
|---|---|---|
| Separate database per service | Default approach for new services | Data duplication, eventual consistency |
| Shared database with schema-per-service | Migration from monolith, same team owns all services | Partial coupling, easier transactions |
| CQRS (separate read/write models) | High read-to-write ratio, complex queries | Complexity, event sourcing overhead |
| Event-carried state transfer | Services need other services' data for reads | Stale data, storage duplication |
Anti-Pattern #4: Wrong Service Boundaries
The most critical decision in microservices is where you draw the boundaries — and the most common mistake is drawing them around technical layers instead of business capabilities.
Wrong: Technical Boundaries
# Technical layers (BAD — creates cross-cutting changes for every feature)
├── api-gateway-service/
├── database-service/
├── cache-service/
├── notification-service/
└── reporting-service/
Right: Business Capability Boundaries
# Business domains (GOOD — each team owns a full vertical)
├── order-management/ # orders, fulfillment, returns
├── customer-identity/ # auth, profiles, preferences
├── product-catalog/ # products, categories, search
├── billing/ # payments, invoices, subscriptions
└── logistics/ # shipping, tracking, warehousing
Your service boundaries will mirror your org chart whether you plan for it or not. If one team owns both "orders" and "inventory," those services will become tightly coupled. Design your services around team ownership, not around database tables.
Anti-Pattern #5: No Saga Pattern for Distributed Transactions
In a monolith, a multi-step business operation is a database transaction: it either all succeeds or all rolls back. In microservices, there's no distributed transaction. What happens when step 3 of 5 fails?
# Order placement flow across services:
1. ✅ Reserve inventory (Inventory Service)
2. ✅ Charge payment (Payment Service)
3. ❌ Create shipment (Logistics Service) ← FAILS!
4. ⬜ Send confirmation email (Notification Service)
5. ⬜ Update analytics (Analytics Service)
# Now what? Inventory is reserved, payment is charged,
# but shipment failed. You need compensation logic.
Saga Patterns
- Choreography-based saga: Each service emits events and listens for events from others. No central coordinator. Works well for simple flows (3-4 steps) but becomes unmaintainable at scale.
- Orchestration-based saga: A central orchestrator service coordinates the steps and handles compensation. More explicit, easier to reason about, better for complex flows.
- Every step needs a compensating action — "reverse inventory reservation," "refund payment," "cancel shipment"
- Compensating actions must be idempotent — safe to retry if the compensation itself fails
Anti-Pattern #6: The Death Star Architecture
When every service can call every other service, you get a dependency graph that looks like a death star. No one understands the system. A failure anywhere cascades everywhere.
The Fix: Service Mesh Layers
- Edge services (API Gateways, BFFs) — only these talk to external clients
- Aggregation services — compose data from multiple domain services
- Domain services — own business logic and data for one domain
- Infrastructure services — auth, config, logging — called by all but call no one
- Rule: Dependencies should flow downward only. Domain services should never call other domain services synchronously. Use events for cross-domain communication.
Anti-Pattern #7: The Testing Gap
Microservices break traditional testing strategies. Unit tests pass, but the system doesn't work because the contract between services is wrong.
| Test Type | Monolith | Microservices |
|---|---|---|
| Unit Tests | Same approach | Same approach — test business logic in isolation |
| Integration Tests | Test against real DB | Test against real DB + mock external services |
| Contract Tests | Not needed | CRITICAL — verify service-to-service agreements (Pact, Spring Cloud Contract) |
| E2E Tests | Single deployment | Need full environment with all services running — expensive, slow, flaky |
| Chaos Engineering | Rarely needed | Essential — network failures, latency injection, service crashes |
Anti-Pattern #8: Observability Debt
In a monolith, you grep the log file. In microservices, a single user request touches 8 services across 15 containers. Without proper observability, debugging is impossible.
The Three Pillars (Plus One)
- Distributed tracing (Jaeger, Zipkin, OpenTelemetry) — follow a request across all services with a single trace ID
- Centralized logging (ELK, Loki, Datadog) — aggregate logs from all services with correlation IDs
- Metrics (Prometheus, Grafana) — RED metrics (Rate, Errors, Duration) per service, per endpoint
- Service dependency mapping (Kiali, Backstage) — visualize which services call which, with latency on each edge
If you can't answer "why is this user's request slow?" within 5 minutes by looking at your dashboards, your observability is insufficient. Observability isn't optional in microservices — it's a prerequisite. Budget 15-20% of infrastructure costs for observability tooling.
When to Stay Monolith
Microservices are not inherently better than monoliths. They're a trade-off. Here's when a monolith is the right choice:
- Team size < 15 — you don't have enough people to own separate services
- Domain is unclear — you're still discovering your bounded contexts; splitting too early creates wrong boundaries
- Low scale requirements — if a single database server handles your load, microservices add complexity for no gain
- Startup phase — speed of iteration matters more than architectural purity; refactor when you've found product-market fit
- No DevOps maturity — if you can't do CI/CD, automated testing, container orchestration, and centralized logging, microservices will be a nightmare
The Maturity Checklist (Before Going Micro)
- ☐ Automated CI/CD pipeline with <15 min build-to-deploy
- ☐ Container orchestration (Kubernetes or equivalent)
- ☐ Centralized logging and monitoring
- ☐ Distributed tracing
- ☐ Service discovery and load balancing
- ☐ At least 3 independent teams that own separate business domains
- ☐ Clear bounded contexts identified through Domain-Driven Design workshops
- ☐ Experience with event-driven architecture
The Pragmatic Migration Path
If you decide microservices are right, don't do a big-bang rewrite. Use the Strangler Fig pattern:
- Start with the monolith — build modular code with clear internal boundaries (packages, modules)
- Identify the first service to extract — pick the module with the least coupling and highest independent change frequency
- Route traffic through a proxy — send requests to either the monolith or the new service based on feature flags
- Extract one service at a time — prove the pattern works before extracting the next
- Run in parallel — keep the monolith path live until confidence in the new service is high
- Retire monolith paths — remove old code only when the new service is proven in production
Architecture Review for Your Platform
Are you building microservices — or a distributed monolith? Our architecture assessments identify coupling, missing patterns, and the right boundaries for your team's scale.
Request an Architecture Review →