Verified by Garnet Grid

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 SizeService CountRatio
5-10 devs3-8 services1-2 services per dev
10-25 devs8-20 services~1 service per dev
25-50 devs15-40 servicesTeam-aligned services
50+ devsDomain-count servicesBounded 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.

EntityOwner ServiceRead AccessWrite Access
CustomersCustomer ServiceAll (via API)Customer Service only
OrdersOrder ServiceCustomer, ShippingOrder Service only
ProductsCatalog ServiceOrder, SearchCatalog Service only
PaymentsPayment ServiceOrder (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:

  1. Traces — Follow a request across all services (Jaeger, Zipkin)
  2. Metrics — Latency, error rate, throughput per service (Prometheus)
  3. 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

SignalMonolithMicroservices
Team size < 10
Product is evolving rapidly
You need independent scalingConsider
Teams can’t deploy independentlyConsider
Different services need different tech stacksConsider
You have DevOps/platform team capacityRequired

:::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. :::