Microservices Anti-Patterns
IntermediateCommon pitfalls include distributed monolith (tight coupling), chatty services, shared databases, synchronous call chains, and under-invested observability infrastructure.
Overview
Microservices introduce distributed-systems complexity — and teams often replicate monolith problems in a distributed setting, gaining the cost without the benefit. The most common anti-patterns are: the Distributed Monolith (services tightly coupled through shared databases or synchronous call chains), Chatty Services (fine-grained services that require many round trips to complete one business operation), Shared Database (multiple services accessing the same schema), Synchronous Call Chains (deep chains of blocking HTTP calls that amplify latency and failure), and Nano-Services (services so small they create overhead without benefit). Recognising these patterns early prevents expensive refactoring.
Distributed Monolith and Shared Database
A Distributed Monolith is a system of "microservices" that cannot be deployed independently because they share a database, share a domain model library, or have synchronous circular dependencies. The shared database anti-pattern is the most common cause — multiple services writing to the same tables creates hidden coupling that makes schema changes risky and coordinated deployments necessary.
// ANTI-PATTERN: Shared Database — OrderService and InventoryService
// both access the same "shop" database directly
// OrderService
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// queries shop.orders table
}
// InventoryService — WRONG: directly queries shop.orders to check order status
@Repository
public interface OrderQueryRepository extends JpaRepository<Order, Long> {
// BAD: InventoryService depends on OrderService's database schema
@Query("SELECT o FROM Order o WHERE o.status = 'PLACED'")
List<Order> findPlacedOrders();
}
// SOLUTION: Each service owns its data; cross-service data via API or events
// OrderService exposes: GET /orders?status=PLACED
// InventoryService calls OrderService's API (or subscribes to order-placed events)
// Or better: InventoryService maintains its own read model
// populated by consuming "order-placed" Kafka events
@KafkaListener(topics = "order-placed")
public void onOrderPlaced(OrderPlacedEvent event) {
// InventoryService maintains its own copy of relevant order data
pendingOrderRepository.save(new PendingOrder(event.getOrderId(), event.getItems()));
}Chatty Services and Synchronous Call Chains
Chatty services require too many round trips to complete one user-facing operation — each adding network latency. Deep synchronous call chains (A→B→C→D) amplify failures: if D has 1% failure rate, the chain has 4% failure rate, and latency is additive. Solutions: aggregate at the API gateway (BFF pattern), use async events for non-critical paths, or batch multiple calls into one.
// ANTI-PATTERN: Chatty API requiring N+1 round trips
// GET /orders/1 → GET /customers/42 → GET /addresses/7 → GET /products/101
// Client makes 4 serial API calls to render one order page
// SOLUTION 1: BFF (Backend for Frontend) — gateway aggregates
@RestController
public class OrderDetailBff {
public OrderDetailResponse getOrderDetail(Long orderId) {
// Parallel calls (not serial)
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(() -> orderClient.getOrder(orderId));
CompletableFuture<Customer> customerFuture =
orderFuture.thenCompose(o ->
CompletableFuture.supplyAsync(() -> customerClient.getCustomer(o.getCustomerId())));
CompletableFuture.allOf(orderFuture, customerFuture).join();
return assemble(orderFuture.join(), customerFuture.join());
}
}
// ANTI-PATTERN: Deep synchronous call chain — A→B→C→D→E
// OrderService → InventoryService → WarehouseService → ShippingService → CarrierService
// Problems: 5x latency, 5x failure surface, tight coupling
// SOLUTION 2: Event-driven for non-blocking side effects
// OrderService publishes "order-placed" event
// InventoryService, WarehouseService, ShippingService independently react
// No direct coupling, no cascading failuresNano-Services and insufficient observability
Nano-services decompose too far — a single function or trivial domain gets its own service with full deployment overhead (CI/CD, observability, networking, team ownership). The test: can this service be independently developed, deployed, and scaled by a small team? If the team would rather deploy it together with another service, it is too small. Under-invested observability is the other common anti-pattern — without distributed tracing, centralized logs, and metrics, debugging production issues in a microservice system is exponentially harder than in a monolith.
// ANTI-PATTERN: Nano-Service — service that does one trivial thing
// "TaxCalculationService" with 3 endpoints and 50 lines of code
// Requires: its own database, CI/CD pipeline, Docker image, monitoring
// TEST: Is this a microservice or a function?
// - Does it have its own data store? → NO
// - Can a separate team own it? → Unlikely (too small)
// - Does it benefit from independent scaling? → NO
// VERDICT: merge into OrderService or make it a library
// ANTI-PATTERN: Missing observability
// Incident: "Checkout is slow" — but:
// - No distributed tracing → can't pinpoint which service is slow
// - No centralised logs → grep across 20 pods manually
// - No dashboards → no baseline to compare against
// - No alerting → users report issues before engineers know
// MINIMUM OBSERVABILITY CHECKLIST (per service):
// ✓ Structured JSON logs with traceId/spanId (Loki/ELK)
// ✓ RED metrics (rate, errors, duration) on every endpoint (Prometheus)
// ✓ Distributed tracing (Jaeger via Micrometer Tracing)
// ✓ Health probes (/actuator/health/liveness + /readiness)
// ✓ Alerting on P99 latency > SLO and error rate > threshold
// ✓ Service dependency map (Kiali or Zipkin service graph)Key Points to Remember
- 1Distributed Monolith: services that cannot be independently deployed due to shared DB, circular deps, or shared domain models
- 2Shared database: the most common coupling source — each service must own its data; share via API or events
- 3Chatty services: N serial round trips per user request — solve with BFF aggregation or batch APIs
- 4Deep synchronous chains (A→B→C→D): failure rate and latency multiply — break with async events for non-critical paths
- 5Nano-services: overhead without benefit — merge if it cannot be independently developed and owned by a small team
- 6Under-invested observability: the #1 operational pain — structured logs, RED metrics, tracing, and alerting are non-negotiable
Interview Questions
Sign in to ask AriaWhat is a Distributed Monolith and what are the symptoms that indicate you have one?
Why is the shared database anti-pattern harmful in microservices?
How does a deep synchronous call chain amplify failure rates compared to a single service?
How would you test whether a proposed service is too small (a nano-service)?
What is the minimum observability stack you would require for a microservice going to production?
Ask Aria about Microservices Anti-Patterns
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.