design patterns
Microservices Architecture Patterns
Essential patterns for designing, implementing, and operating microservices-based systems.
Microservices Architecture Patterns
TL;DR
Microservices patterns solve specific distributed systems challenges: decomposition, data management, communication, and reliability. Choose patterns based on your actual problems, not because they're popular. Most systems need only a subset of these patterns.
Key Takeaways
- Decomposition patterns define service boundaries (by business capability, subdomain, or team)
- Data patterns address the distributed data challenge (database per service, saga, CQRS)
- Communication patterns handle service interaction (sync vs async, API gateway)
- Reliability patterns ensure system resilience (circuit breaker, bulkhead, retry)
- Start simple: adopt patterns incrementally as complexity demands
Why This Matters
Microservices introduce complexity that monoliths don't have: network failures, distributed transactions, eventual consistency, and operational overhead. Patterns provide proven solutions to these challenges. However, applying patterns prematurely or inappropriately creates unnecessary complexity. Understanding when and why to use each pattern is as important as understanding how.
Pattern Overload
Don't implement all patterns upfront. Start with the simplest architecture that works, then add patterns as specific problems emerge. Every pattern has a cost.
Pattern Categories
MICROSERVICES PATTERNS
├── DECOMPOSITION: How to break down the system
│ ├── By Business Capability
│ ├── By Subdomain (DDD)
│ └── Strangler Fig (migration)
│
├── DATA MANAGEMENT: How to handle distributed data
│ ├── Database per Service
│ ├── Saga Pattern
│ ├── CQRS
│ └── Event Sourcing
│
├── COMMUNICATION: How services talk
│ ├── API Gateway
│ ├── Service Mesh
│ ├── Async Messaging
│ └── Backend for Frontend (BFF)
│
└── RELIABILITY: How to stay resilient
├── Circuit Breaker
├── Bulkhead
├── Retry with Backoff
└── Health Check
Pattern Landscape
Decomposition Patterns
Problem
How do you define service boundaries that are stable and meaningful?
Solution
Identify business capabilities—what the business does—and create services around them.
BUSINESS CAPABILITIES (E-commerce Example)
├── Product Management
│ └── Services: Catalog, Inventory, Pricing
├── Order Management
│ └── Services: Orders, Fulfillment, Returns
├── Customer Management
│ └── Services: Customer Profile, Loyalty, Support
└── Payment Processing
└── Services: Payments, Invoicing, Refunds
When to Use
- Business capabilities are well-understood
- Organizational structure aligns with capabilities
- Need stable, long-lived service boundaries
Trade-offs
| Benefit | Cost |
|---|---|
| Stable boundaries | May not match technical concerns |
| Business alignment | Requires deep domain knowledge |
| Team ownership clarity | Cross-cutting concerns span services |
Conway's Law
Service boundaries often reflect team structure. Design both together. A service owned by multiple teams will struggle; a team owning multiple services will merge them.
Data Management Patterns
Problem
How do you ensure services are loosely coupled when they share data?
Solution
Each service owns its data exclusively. No direct database access across services.
DATABASE PER SERVICE
✓ CORRECT ✗ WRONG
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│Service A│ │Service B│ │Service A│ │Service B│
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼───────────▼────┐
│ DB A │ │ DB B │ │ Shared Database │
└─────────┘ └─────────┘ └──────────────────────┘
Implications
| Aspect | Approach |
|---|---|
| Data access | Via service API only |
| Joins | Done in application code |
| Transactions | Distributed (saga pattern) |
| Consistency | Eventually consistent |
| Schema changes | Service controls its own |
When to Use
- True service independence required
- Teams need autonomy over their data
- Different data storage needs per service
Polyglot Persistence
Database per service enables polyglot persistence: use PostgreSQL for transactional data, MongoDB for documents, Redis for caching, each where it fits best.
Communication Patterns
Problem
How do clients interact with multiple microservices efficiently?
Solution
Single entry point that routes requests, handles cross-cutting concerns.
API GATEWAY RESPONSIBILITIES
┌───────────────────┐
│ API Gateway │
│ │
Client ────────────→│ • Authentication │
│ • Rate limiting │
│ • Request routing │
│ • Response caching│
│ • Protocol transl.│
│ • Load balancing │
└─────────┬─────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Service A│ │Service B│ │Service C│
└─────────┘ └─────────┘ └─────────┘
Gateway Patterns
| Pattern | Use Case |
|---|---|
| Routing | Direct requests to appropriate service |
| Aggregation | Combine multiple service responses |
| Offloading | Handle auth, SSL, caching centrally |
| BFF | Client-specific gateways (see below) |
Implementation Options
AWS API Gateway, Azure API Management, Kong, Nginx, or custom. Cloud-native options reduce operational burden.
Reliability Patterns
Problem
A failing service causes cascading failures across the system.
Solution
Detect failures and stop calling the failing service temporarily.
CIRCUIT BREAKER STATES
┌───────────────────────────────────────────────┐
│ │
▼ │
┌─────────┐ failure threshold ┌─────────┐ │
│ CLOSED │ ─────────────────→ │ OPEN │ │
│(normal) │ │ (fail) │ │
└─────────┘ └────┬────┘ │
▲ │ │
│ timeout ▼ │
│ ┌──────────┐ │
│ success │HALF-OPEN │ │
└─────────────────────────│ (test) │─────────┘
└──────────┘ failure
Configuration Parameters
CIRCUIT BREAKER CONFIG
├── Failure threshold: 5 failures in 60 seconds
├── Success threshold: 3 successes to close
├── Timeout: 30 seconds before half-open
├── Fallback: Cached response, default value, error
└── Monitoring: Track state changes, failure rates
Implementation Example
// Pseudocode
class CircuitBreaker {
state = 'CLOSED'
failures = 0
async call(fn) {
if (this.state === 'OPEN') {
if (this.timeoutExpired()) {
this.state = 'HALF_OPEN'
} else {
return this.fallback()
}
}
try {
const result = await fn()
this.onSuccess()
return result
} catch (error) {
this.onFailure()
throw error
}
}
}Quick Reference Card
┌─────────────────────────────────────────────────────────────┐
│ MICROSERVICES PATTERNS CHEAT SHEET │
├─────────────────────────────────────────────────────────────┤
│ │
│ DECOMPOSITION │
│ ───────────────────────────────────────────────────────── │
│ By Capability → Stable business-aligned boundaries │
│ By Subdomain → DDD bounded contexts │
│ Strangler Fig → Incremental migration │
│ │
│ DATA MANAGEMENT │
│ ───────────────────────────────────────────────────────── │
│ DB per Service → Loose coupling, service autonomy │
│ Saga → Distributed transactions │
│ CQRS → Separate read/write optimization │
│ Event Sourcing → Audit trail, temporal queries │
│ │
│ COMMUNICATION │
│ ───────────────────────────────────────────────────────── │
│ API Gateway → Single entry, cross-cutting concerns │
│ BFF → Client-specific backends │
│ Async Messaging → Loose coupling, resilience │
│ │
│ RELIABILITY │
│ ───────────────────────────────────────────────────────── │
│ Circuit Breaker → Fail fast, prevent cascade │
│ Bulkhead → Isolate failures │
│ Retry + Backoff → Handle transient failures │
│ │
├─────────────────────────────────────────────────────────────┤
│ RULE: Start simple. Add patterns when problems emerge. │
└─────────────────────────────────────────────────────────────┘
Related Topics
- Twelve-Factor App - Cloud-native design principles
- Quality Attributes - Architectural quality concerns
- Cloud Architecture - Cloud-specific patterns
Sources
- microservices.io - Chris Richardson's pattern catalog
- Building Microservices - Sam Newman
- Domain-Driven Design - Eric Evans
- Release It! - Michael Nygard