Microservices patterns

Microservices Patterns

Microservices Design Patterns - Complete Guide

Microservices Design Patterns

Complete pattern catalog for building production-grade microservices

Quick Summary (30 seconds):
Microservices patterns fall into five categories: Decomposition (how to split), Integration (how services talk), Data Management (how data is handled), Observability (how to monitor), and Cross-cutting (shared concerns).

1. Decomposition Patterns

How to break down your monolith into microservices the right way.

Decompose by Business Capability
Split based on business functions.
Examples:
  • E-Commerce: Order, Payment, Inventory, Shipping
  • Banking: Account, Transaction, Loan, Customer
Pro: Aligns with business structure
Con: May cause chatty APIs if domains are coupled
Decompose by Subdomain (DDD)
Identify Bounded Contexts and define Ubiquitous Language.
Examples:
  • Insurance: Policy, Claims, Underwriting, Billing
Pro: Most aligned with business logic
Con: Requires deep domain expertise
Decompose by Verb/Use Case
Split by actions: Create, Update, Delete, Query.
Examples:
  • OrderWrite Service, OrderRead Service
Pro: Optimized scaling per operation
Con: Complex consistency management

2. Integration Patterns

How services communicate and discover each other.

API Gateway

Single entry point for all clients, handling routing, authentication, rate limiting, and response aggregation.

Request Flow:
Client -> API Gateway ->
    Auth Service (validate token)
    Order Service (get orders)
    Payment Service (get payments)
    Response Aggregation -> Client
Pro: Centralized cross-cutting concerns
Con: Potential single point of failure
Service Discovery

Services dynamically find each other without hardcoded addresses. Essential for cloud environments.

Service Registration (on startup):
Payment Service -> Service Registry: "I am at 10.0.0.5:8080"

Service Discovery (when needed):
Order Service -> Service Registry: "Where is Payment Service?"
Service Registry -> Order Service: "Payment Service at 10.0.0.5:8080"
When to use: Dynamic environments like Kubernetes, cloud auto-scaling
Circuit Breaker

Prevents cascading failures when a downstream service is failing.

Three states of Circuit Breaker:

CLOSED -> Calls succeed normally
    (after 5 failures)
OPEN -> Calls fail immediately (no timeout wait)
    (after timeout period)
HALF-OPEN -> Allow test call to check recovery
When to use: Prevent cascading failures, protect from slow dependencies
Retry with Backoff

Handle transient failures with smart retry logic.

Exponential Backoff Strategy:
Attempt 1: immediate
Attempt 2: wait 100ms
Attempt 3: wait 200ms
Attempt 4: wait 400ms
Attempt 5: wait 800ms -> then fail
When to use: Transient failures like network blips, temporary timeouts

3. Data Management Patterns

Database per Service

Each microservice has its own private database. No sharing!

Order Service -> order_db (PostgreSQL)
Payment Service -> payment_db (PostgreSQL)
User Service -> user_db (MongoDB)
Analytics Service -> analytics_db (Cassandra)
Pro: Independent scaling, choose best database per service
Con: Distributed transactions across services
Saga Pattern (Choreography)

Decentralized coordination where services publish events that trigger actions in other services.

Order Created --event--> Payment Service
                              (Payment Success)
                        Inventory Service --event--> Shipping Service
                        (Stock Reserved)          (Create Shipment)
Pro: Decentralized, scalable, no single point of failure
Con: Hard to debug, no central view of transaction
Saga Pattern (Orchestration)

Centralized coordinator that tells each service what to do and when.

Orchestrator -> Order Service (create order)
             -> Payment Service (process payment)
             -> Inventory Service (reserve stock)
             -> Shipping Service (create shipment)
             [If any fails, send compensating commands]
Pro: Central visibility, easier to manage complex workflows
Con: Orchestrator can become bottleneck
CQRS (Command Query Responsibility Segregation)

Separate models for writes (commands) and reads (queries). Optimize each independently.

Command Model (Write)              Query Model (Read)
       OrderWrite Service --event--> OrderRead Service
(optimized for updates)        (denormalized for fast queries)
When to use: Different read/write performance requirements, complex domain models

4. Observability Patterns

Distributed Tracing
Track a request across multiple services with a single trace ID.
Trace ID: abc-123
Gateway (5ms) -> Order (50ms) -> Payment (200ms)
Tools: Jaeger, Zipkin, OpenTelemetry
Health Check API
GET /health
{
  "status": "UP",
  "components": {
    "database": {"status": "UP"},
    "redis": {"status": "UP"}
  }
}
Use: Kubernetes liveness/readiness probes
Log Aggregation
Centralize logs from all services for searching and analysis.
Tools: ELK Stack (Elasticsearch, Logstash, Kibana), Loki, Splunk
Metrics Collection
Collect and visualize system metrics (latency, throughput, error rates).
Tools: Prometheus, Grafana, Datadog

5. Cross-cutting Patterns

Sidecar Pattern
Deploy auxiliary components alongside your main service container.
Main Container + Logging Sidecar + Monitoring Sidecar + Config Sidecar
Pro: Decouples cross-cutting concerns
Con: Increased resource usage
Ambassador Pattern
Proxy that handles external communication for a service (retry, circuit breaker, monitoring).
Example: Kubernetes service mesh (Istio, Linkerd)
Externalized Configuration
Configuration stored outside the service code, loaded at runtime.
Config Server -> Service reads config on startup/refresh
Tools: Spring Cloud Config, Consul, etcd

Pattern Selection Decision Tree

Q1: How to split the monolith?
    -> Simple business logic? Use Decompose by Business Capability
    -> Complex domain? Use Decompose by Subdomain (DDD)

Q2: How do services communicate?
    -> Need immediate response? Use Sync (API Gateway)
    -> Can wait? Use Async (Event-Driven)

Q3: How to handle service failures?
    -> Use Retry + Circuit Breaker + Timeout + Fallback

Q4: How to manage distributed transactions?
    -> Simple workflow? Use Saga Choreography
    -> Complex workflow? Use Saga Orchestration

Q5: Read vs Write performance issues?
    -> Use CQRS + Event Sourcing

Q6: Need to share cross-cutting concerns?
    -> Use Sidecar pattern

Q7: How to find services dynamically?
    -> Use Service Discovery

Pattern Summary Table

CategoryPatternWhen to Use
DecompositionBusiness CapabilitySimple business logic, clear boundaries
DecompositionSubdomain (DDD)Complex domain, domain experts available
IntegrationAPI GatewayMultiple clients, need centralized auth/routing
IntegrationCircuit BreakerPrevent cascading failures
Data ManagementDatabase per ServiceAlways - non-negotiable for true microservices
Data ManagementSagaDistributed transactions across services
Data ManagementCQRSDifferent read/write performance needs
ObservabilityDistributed TracingDebugging requests across service boundaries
Cross-cuttingSidecarShared concerns across services
Expert Insight:
"Database per service is non-negotiable. If services share a database, you don't have microservices - you have a distributed monolith with extra network calls. The Circuit Breaker pattern is your first line of defense against cascading failures."

Key Takeaways

  • Decomposition first - get your boundaries right before building
  • API Gateway is the entry point for all external traffic
  • Circuit Breakers are mandatory for production systems
  • Database per Service - no exceptions
  • Saga for distributed transactions, CQRS for read/write separation
  • Observability (tracing, logs, metrics) is not optional
  • Sidecar pattern for infrastructure concerns
Final Advice:
"Patterns are solutions to recurring problems, not mandatory prescriptions. The best architecture selectively applies patterns based on actual needs, not theoretical elegance. Start simple, add patterns only when you feel the pain they solve."