Building resilient, scalable distributed systems requires choosing the right architectural patterns for your specific challenges. This guide provides a quick reference to help you select the most appropriate pattern based on your problem domain, with links to detailed explanations of each pattern.
Pattern Selection Quick Reference
Use this table to quickly identify which pattern addresses your specific challenge:
| Your Challenge | Recommended Pattern | When to Use |
|---|---|---|
| Service calls timing out | Asynchronous Request-Reply | Operations take longer than HTTP timeout limits |
| Service keeps failing | Circuit Breaker | Prevent cascading failures from unavailable services |
| Temporary network glitches | Retry | Handle transient failures that resolve quickly |
| One service affecting others | Bulkhead | Isolate resources to contain failures |
| Multi-service business transaction | Saga | Coordinate long-lived work across services with compensations |
| Need to undo multi-step work | Compensating Transaction | Roll back partial work across services with semantic reversals |
| Bursty traffic overwhelms service | Queue-Based Load Leveling | Smooth spikes via a buffer between producer and consumer |
| Atomic local-write + event publish | Outbox | Guarantee event publication alongside a local DB transaction |
| API throttling errors | Rate Limiting | Control request rate to throttled services |
| Legacy system integration | Anti-Corruption Layer | Protect clean architecture from legacy systems |
| Slow query performance | Materialized View | Pre-compute complex queries for faster reads |
| Frequent read of same data | Cache-Aside | Load data on demand into a cache to relieve read pressure |
| Need fast lookups by non-key field | Index Table | Create secondary indexes over data stores for query patterns |
| Read/write workloads diverge | CQRS | Separate read and write models for scalability and clarity |
| Large message payloads | Claim Check | Reduce message size by storing data externally |
| Migrating legacy systems | Strangler Fig | Gradually replace legacy with modern systems |
| Cross-cutting concerns | Sidecar | Add functionality without modifying applications |
| Database scalability | Sharding | Distribute data across multiple databases |
| Multiple API calls | Gateway Aggregation | Combine multiple backend calls into one |
| Multiple service versions or endpoints | Gateway Routing | Route requests to multiple services via a single endpoint |
| Event distribution | Publisher-Subscriber | Decouple event producers from consumers |
| Service health monitoring | Health Endpoint Monitoring | Proactively detect service failures |
| Need a single coordinator | Leader Election | Coordinate distributed work by electing one instance as leader |
| Configuration changes frequently | External Configuration Store | Centralize configuration outside the deployment package |
| Authentication across services | Federated Identity | Centralize authentication and authorization |
Pattern Categories
Architecture patterns can be grouped by the problems they solve:
🛡️ Resilience Patterns
Patterns that help systems handle failures gracefully: Circuit Breaker: Prevents cascading failures by temporarily blocking calls to failing services. Like an electrical circuit breaker, it “trips” when failures exceed a threshold, allowing the system to fail fast and recover gracefully. Retry: Automatically retries failed operations to handle transient failures. Uses strategies like exponential backoff to avoid overwhelming already-stressed services. Bulkhead: Isolates resources into separate pools to prevent one failing component from consuming all resources. Named after ship compartments that contain flooding. Saga: Coordinates long-lived business transactions across services with forward and compensating actions. Pairs with Bulkhead: Bulkhead isolates failures within a service; Saga coordinates recovery across services. [Compensating Transaction]: Undoes the work performed by a sequence of steps that together form an eventually consistent operation. The foundation under Saga: each Saga step needs a semantic reversal in case later steps fail. [Queue-Based Load Leveling]: Decouples a task from a service it calls by introducing a queue as a buffer. Smooths intermittent heavy loads so the consumer can process at its own pace, preventing throttling and improving availability. [Outbox]: Stores outgoing messages in an “outbox” table inside the same local transaction as the business write, then a separate process reads the outbox and publishes to the message bus. Eliminates the dual-write problem when Saga events must be emitted atomically with state changes.
Combining Resilience Patterns
These patterns work best together: Retry handles transient failures, Circuit Breaker prevents overwhelming failing services, and Bulkhead contains the blast radius of failures. Saga complements them when work spans multiple services — orchestrating multi-step transactions that survive partial failure. Queue-Based Load Leveling absorbs bursty traffic upstream of fragile dependencies, while Outbox guarantees that Saga events are emitted reliably alongside the local DB transaction.
⚡ Performance Patterns
Patterns that optimize system performance and responsiveness: Asynchronous Request-Reply: Decouples long-running operations from immediate responses, preventing timeouts and improving user experience. Materialized View: Pre-computes and stores query results to avoid expensive computations at read time. Ideal for complex aggregations and reports. [Cache-Aside]: Loads data on demand into a cache from the underlying data store; the application reads from the cache first, falls back to the store on miss, and repopulates the cache. The most common read-side optimization — typically used before considering Materialized View. [Index Table]: Creates secondary indexes over fields that data-store queries frequently reference but that the primary key does not cover. Faster to build and maintain than Materialized View when the query pattern is selective. [CQRS]: Separates read and write operations into different models, often with different storage. Reads scale independently of writes, and read models can be denormalized for query speed at the cost of eventual consistency on the write side. Claim Check: Reduces message payload size by storing large data externally and passing only a reference. Improves messaging system performance and reduces costs. Sharding: Distributes data across multiple databases to improve scalability and performance. Each shard handles a subset of the total data.
🔄 Integration Patterns
Patterns that facilitate communication between systems: Anti-Corruption Layer: Provides a translation layer between systems with different semantics, protecting your clean architecture from legacy system quirks. Gateway Aggregation: Combines multiple backend service calls into a single request, reducing client complexity and network overhead. [Gateway Routing]: Routes requests to one of multiple services through a single endpoint. Pairs naturally with Gateway Aggregation and Gateway Offloading to compose a complete API gateway. Publisher-Subscriber: Enables asynchronous event-driven communication where publishers don’t need to know about subscribers. Federated Identity: Delegates authentication to external identity providers, enabling single sign-on across multiple systems.
🎯 Operational Patterns
Patterns that improve system operations and management: Rate Limiting: Controls the rate of requests sent to services to avoid throttling errors and optimize throughput. Health Endpoint Monitoring: Exposes health check endpoints for proactive monitoring and automated recovery. Sidecar: Deploys helper components alongside applications to handle cross-cutting concerns like logging, monitoring, and configuration. [Leader Election]: Elects one instance among a set of distributed task instances to act as the leader, which then coordinates the work of the others. Used when a single coordinator is required (for example, scheduling or partition assignment) but no instance is permanently designated. [External Configuration Store]: Moves configuration information out of the application deployment package into a centralized location. Lets configuration change without redeployment and supports environment-specific values, feature flags, and secrets.
🏗️ Migration Patterns
Patterns that support system modernization: Strangler Fig: Gradually replaces legacy systems by incrementally migrating functionality to new implementations. Named after a fig tree that grows around and eventually replaces its host.
Decision Flowchart: Choosing the Right Pattern
Use this flowchart to navigate to the most appropriate pattern for your situation:
Pattern Comparison Matrix
Compare patterns across key dimensions:
Pattern Combinations
Many real-world systems combine multiple patterns for comprehensive solutions:
Resilient Microservices Stack
These four — Circuit Breaker, Retry, Bulkhead, and Health Endpoint — are the canonical “every synchronous service-to-service call is wrapped by these” set. Each addresses a distinct failure mode the others cannot, and they layer in the order shown below:
| Pattern | Failure mode it addresses | Layer |
|---|---|---|
| Health Endpoint | Operating blind — cannot tell a transient blip from a chronic outage | Detection (feeds the others) |
| Circuit Breaker | Cascading failures — one slow downstream takes down the whole mesh | Protection (gate before the call) |
| Retry | Transient blips — network jitter, GC pauses, brief overload | Recovery (wraps the call) |
| Bulkhead | Resource starvation — one noisy neighbor exhausts shared resources | Isolation (resource ceiling per downstream) |
Read the diagram inside-out: Bulkhead caps the resource ceiling for this downstream; the Circuit Breaker decides whether to proceed; Retry handles per-call blips inside the closed circuit; Health Endpoint, polled by monitoring, feeds the signal that drives the Circuit Breaker’s state machine.
- Circuit Breaker: Prevents cascading failures
- Retry: Handles transient failures
- Bulkhead: Isolates resources
- Health Endpoint: Enables monitoring
High-Performance API Gateway
These three — Rate Limiting, Gateway Aggregation, and Async Request-Reply — form the canonical “edge of a high-throughput public API” set. Each addresses a distinct concern the others cannot: rejection vs composition vs latency relief. They layer along the request flow as shown below:
| Pattern | Failure mode it addresses | Position in the call flow |
|---|---|---|
| Rate Limiting | Backend overload — a misbehaving client or burst saturates upstream services | Gate (reject before doing work) |
| Gateway Aggregation | Chattery clients — N round trips per screen, painful on mobile and flaky networks | Composition (fan out and merge) |
| Async Request-Reply | Blocking — one slow downstream call stalls the thread and ties up a client connection | Latency relief (202 + poll token for slow calls) |
Read the diagram top-to-bottom: Rate Limiting is the outermost gate — reject first, never let hostile traffic reach the composition layer. Gateway Aggregation then fans one client request out to N backend calls and merges the results. If any aggregated call would exceed the latency budget, Async Request-Reply hands back a polling token instead of blocking the client.
- Rate Limiting: Rejects traffic before it reaches the aggregation layer
- Gateway Aggregation: Fans out and merges, so clients make one round trip
- Async Request-Reply: Returns 202 + a token for calls that would block too long
Legacy System Modernization
These three — Federated Identity, Strangler Fig, and Anti-Corruption Layer — form the canonical “migrate away from a legacy monolith without a big-bang rewrite” set. Each addresses a distinct risk: unified login before redirect, the redirect itself, and the translation between old contracts and a new domain. They layer along the request flow as shown below:
| Pattern | Failure mode it addresses | Position in the call flow |
|---|---|---|
| Federated Identity | Per-system re-login — every migrated subsystem invents its own auth, users tire out | Boundary (single auth entry at the edge) |
| Strangler Fig | Big-bang rewrite risk — old system still serves production while new code lands piecemeal | Router (per-feature traffic split) |
| Anti-Corruption Layer | Legacy contamination — new domain models absorb every quirk of old contracts | Translation (legacy model → new domain) |
Read the diagram top-to-bottom: Federated Identity establishes a single auth boundary so users cross from old to new without re-logging-in for each subsystem. Strangler Fig then routes per feature — migrated paths hit the new service directly; unmigrated paths still hit the legacy system but pass through the Anti-Corruption Layer first, which translates legacy contracts into the new domain before the new service ever sees them. The new service never speaks legacy protocols directly.
- Federated Identity: Single auth boundary across old and new systems
- Strangler Fig: Per-feature routing lets migration proceed gradually
- Anti-Corruption Layer: Translates legacy contracts into the new domain model
Resilient Distributed Transactions
These three — Outbox, Saga, and Circuit Breaker — form the canonical “multi-service transaction that survives partial failure without dropping events” set. Each addresses a distinct hazard: atomic local write, cross-service coordination, and per-step protection. They layer along the temporal flow as shown below:
| Pattern | Failure mode it addresses | Position in the flow |
|---|---|---|
| Outbox | Dual-write anomaly — DB committed but event never published (or vice versa), state and bus diverge | Write phase (event stored atomically with the local DB commit) |
| Saga | Cross-service ACID impossibility — no global transaction across services, partial failures leave dangling state | Coordination (forward steps + semantic reversals) |
| Circuit Breaker | Saga-step overload — a slow participant blocks the orchestrator and stalls every subsequent step | Per-step protection (cap blast radius of each call) |
Read the diagram top-to-bottom: the local DB transaction writes business state and the outbox row in a single atomic commit, so state and pending events can never disagree. The Outbox Relay then reads the outbox and publishes to the broker. The Saga Orchestrator consumes those events and issues the next step; the Circuit Breaker guards each step call so a failing participant cannot stall the entire saga.
- Outbox: Guarantees event publication alongside the local DB transaction
- Saga: Coordinates multi-service transactions with forward and compensating actions
- Circuit Breaker: Caps blast radius of each saga step’s downstream call
Pattern Selection Criteria
Consider these factors when choosing patterns:
System Requirements
Functional Requirements
- Availability: How much downtime is acceptable?
- Performance: What are your latency requirements?
- Scalability: How much growth do you expect?
- Consistency: What consistency guarantees do you need?
Technical Constraints
Technical Factors
- Existing infrastructure: What systems are already in place?
- Team expertise: What patterns does your team know?
- Technology stack: What frameworks and libraries are available?
- Budget: What resources can you allocate?
Operational Considerations
Operations
- Monitoring: Can you observe the pattern’s behavior?
- Maintenance: How complex is ongoing maintenance?
- Testing: Can you effectively test the implementation?
- Documentation: Is the pattern well-documented?
Common Anti-Patterns
Avoid these common mistakes when applying patterns:
Pattern Misuse
Over-engineering: Don’t apply complex patterns to simple problems. Start simple and add patterns as needed. Pattern stacking: Avoid combining too many patterns without clear justification. Each pattern adds complexity. Ignoring trade-offs: Every pattern has costs. Consider performance overhead, operational complexity, and maintenance burden. Cargo cult implementation: Don’t copy patterns without understanding why they work. Adapt patterns to your specific context. For a comprehensive guide to code-level anti-patterns like God Objects, Cargo Cult Programming, and Copy-Paste Programming, see Software Development Anti-Patterns.
Getting Started
Follow this approach when implementing patterns:
1. Identify the Problem
Clearly define the challenge you’re trying to solve:
- What symptoms are you experiencing?
- What are the root causes?
- What are your success criteria?
2. Research Patterns
Use this guide to identify candidate patterns:
- Review the quick reference table
- Follow the decision flowchart
- Read detailed pattern articles
3. Evaluate Options
Compare patterns against your requirements:
- Implementation complexity
- Operational overhead
- Team expertise
- Budget constraints
4. Start Small
Begin with a pilot implementation:
- Choose a non-critical component
- Implement the pattern
- Monitor and measure results
- Iterate based on learnings
5. Scale Gradually
Expand successful implementations:
- Document lessons learned
- Train team members
- Apply to additional components
- Refine based on experience
Complete Pattern Index
Here’s the complete list of patterns covered in this series:
- Rate Limiting Pattern (January) - Control request rates to throttled services
- Anti-Corruption Layer Pattern (February) - Protect architecture from legacy systems
- Retry Pattern (March) - Handle transient failures gracefully
- Claim Check Pattern (April) - Reduce message payload sizes
- Materialized View Pattern (May) - Pre-compute complex queries
- Strangler Fig Pattern (June) - Gradually migrate legacy systems
- Sidecar Pattern (July) - Add functionality via helper components
- Sharding Pattern (August) - Distribute data for scalability
- Gateway Aggregation Pattern (September) - Combine multiple API calls
- Publisher-Subscriber Pattern (October) - Event-driven communication
- Health Endpoint Monitoring Pattern (November) - Proactive health checks
- Federated Identity Pattern (December) - Centralized authentication
- Circuit Breaker Pattern (January) - Prevent cascading failures
- Bulkhead Pattern (March) - Isolate resources to contain failures
- Asynchronous Request-Reply Pattern (April) - Handle long-running operations
- Saga Pattern (July) - Coordinate multi-service transactions with compensations
- Compensating Transaction - Undo work performed by a sequence of steps that together form an eventually consistent operation
- Queue-Based Load Leveling - Use a queue as a buffer between a task and a service to smooth intermittent heavy loads
- Outbox - Store outgoing messages in an outbox table inside the local transaction; a separate process publishes them reliably
- Cache-Aside - Load data on demand into a cache from the underlying data store
- Index Table - Create secondary indexes over fields that data-store queries frequently reference
- CQRS - Separate read and write operations into different models to scale them independently
- Gateway Routing - Route requests to one of multiple services through a single endpoint
- Leader Election - Coordinate distributed work by electing one instance as the leader
- External Configuration Store - Move configuration information out of the application deployment package to a centralized location
Additional Resources
Books
- “Cloud Design Patterns” by Microsoft - Comprehensive pattern catalog
- “Release It!” by Michael Nygard - Production-ready software patterns
- “Building Microservices” by Sam Newman - Microservices architecture patterns
- “Domain-Driven Design” by Eric Evans - Strategic design patterns
Online Resources
Practice
Learning by Doing
The best way to learn patterns is through hands-on practice:
- Build sample applications implementing each pattern
- Contribute to open-source projects using these patterns
- Conduct architecture reviews with your team
- Share knowledge through blog posts and presentations
Conclusion
Architecture patterns are powerful tools for solving common distributed systems challenges. This quick reference guide helps you:
- Quickly identify the right pattern for your problem
- Compare patterns across multiple dimensions
- Understand relationships between patterns
- Avoid common pitfalls in pattern application
- Plan your learning journey through the pattern catalog Remember: patterns are guidelines, not rigid rules. Adapt them to your specific context, measure their impact, and iterate based on results. Start with simple patterns like Retry and Health Endpoint Monitoring, then gradually adopt more complex patterns as your system evolves.
Comments
Please accept the "Functionality" cookie category to view and post comments.
Comments failed to load. You can try again or view the discussion directly on GitHub.
View on GitHub