Architecture Patterns Quick Reference

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 ChallengeRecommended PatternWhen to Use
Service calls timing outAsynchronous Request-ReplyOperations take longer than HTTP timeout limits
Service keeps failingCircuit BreakerPrevent cascading failures from unavailable services
Temporary network glitchesRetryHandle transient failures that resolve quickly
One service affecting othersBulkheadIsolate resources to contain failures
Multi-service business transactionSagaCoordinate long-lived work across services with compensations
Need to undo multi-step workCompensating TransactionRoll back partial work across services with semantic reversals
Bursty traffic overwhelms serviceQueue-Based Load LevelingSmooth spikes via a buffer between producer and consumer
Atomic local-write + event publishOutboxGuarantee event publication alongside a local DB transaction
API throttling errorsRate LimitingControl request rate to throttled services
Legacy system integrationAnti-Corruption LayerProtect clean architecture from legacy systems
Slow query performanceMaterialized ViewPre-compute complex queries for faster reads
Frequent read of same dataCache-AsideLoad data on demand into a cache to relieve read pressure
Need fast lookups by non-key fieldIndex TableCreate secondary indexes over data stores for query patterns
Read/write workloads divergeCQRSSeparate read and write models for scalability and clarity
Large message payloadsClaim CheckReduce message size by storing data externally
Migrating legacy systemsStrangler FigGradually replace legacy with modern systems
Cross-cutting concernsSidecarAdd functionality without modifying applications
Database scalabilityShardingDistribute data across multiple databases
Multiple API callsGateway AggregationCombine multiple backend calls into one
Multiple service versions or endpointsGateway RoutingRoute requests to multiple services via a single endpoint
Event distributionPublisher-SubscriberDecouple event producers from consumers
Service health monitoringHealth Endpoint MonitoringProactively detect service failures
Need a single coordinatorLeader ElectionCoordinate distributed work by electing one instance as leader
Configuration changes frequentlyExternal Configuration StoreCentralize configuration outside the deployment package
Authentication across servicesFederated IdentityCentralize 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:

graph TD Start[What's your challenge?] --> Q1{Service<br/>availability?} Q1 -->|Failing repeatedly| CB[Circuit Breaker] Q1 -->|Temporary failures| Retry[Retry Pattern] Q1 -->|One affects others| Bulkhead[Bulkhead] Q1 -->|Multi-service coordination| SagaNode[Saga] Q1 -->|Need to undo work| CT[Compensating Transaction] Q1 -->|Bursty traffic| QBLL[Queue-Based Load Leveling] Q1 -->|Atomic write + publish| OB[Outbox] Q1 -->|Performance| Q2{What type?} Q2 -->|Long operations| Async[Asynchronous Request-Reply] Q2 -->|Slow queries| MV[Materialized View] Q2 -->|Frequent reads| CA[Cache-Aside] Q2 -->|Lookups by field| IT[Index Table] Q2 -->|Read/write diverge| CQRSNode[CQRS] Q2 -->|Large messages| CC[Claim Check] Q2 -->|Database scale| Shard[Sharding] Q1 -->|Integration| Q3{What need?} Q3 -->|Legacy system| ACL[Anti-Corruption Layer] Q3 -->|Multiple calls| GA[Gateway Aggregation] Q3 -->|Multiple endpoints| GR[Gateway Routing] Q3 -->|Event distribution| PubSub[Publisher-Subscriber] Q3 -->|Authentication| FI[Federated Identity] Q1 -->|Operations| Q4{What aspect?} Q4 -->|Throttling| RL[Rate Limiting] Q4 -->|Monitoring| HEM[Health Endpoint] Q4 -->|Cross-cutting| Sidecar[Sidecar] Q4 -->|Single coordinator| LE[Leader Election] Q4 -->|Config changes| ECS[External Config Store] Q1 -->|Migration| SF[Strangler Fig] style CB fill:#ff6b6b style Retry fill:#ff6b6b style Bulkhead fill:#ff6b6b style SagaNode fill:#ff6b6b style CT fill:#ff6b6b style QBLL fill:#ff6b6b style OB fill:#ff6b6b style Async fill:#51cf66 style MV fill:#51cf66 style CA fill:#51cf66 style IT fill:#51cf66 style CQRSNode fill:#51cf66 style CC fill:#51cf66 style Shard fill:#51cf66 style ACL fill:#4dabf7 style GA fill:#4dabf7 style GR fill:#4dabf7 style PubSub fill:#4dabf7 style FI fill:#4dabf7 style RL fill:#ffd43b style HEM fill:#ffd43b style Sidecar fill:#ffd43b style LE fill:#ffd43b style ECS fill:#ffd43b style SF fill:#a78bfa

Pattern Comparison Matrix

Compare patterns across key dimensions:

{ "title": { "text": "Pattern Complexity vs Impact" }, "tooltip": { "trigger": "item", "formatter": "{b}<br/>Complexity: {c0}<br/>Impact: {c1}" }, "xAxis": { "type": "value", "name": "Implementation Complexity", "min": 0, "max": 10 }, "yAxis": { "type": "value", "name": "System Impact", "min": 0, "max": 10 }, "series": [{ "type": "scatter", "symbolSize": 20, "data": [ {"name": "Retry", "value": [2, 7]}, {"name": "Circuit Breaker", "value": [4, 8]}, {"name": "Bulkhead", "value": [5, 8]}, {"name": "Compensating Transaction", "value": [5, 8]}, {"name": "Queue-Based Load Leveling", "value": [5, 7]}, {"name": "Outbox", "value": [4, 7]}, {"name": "Rate Limiting", "value": [6, 7]}, {"name": "Anti-Corruption Layer", "value": [7, 9]}, {"name": "Async Request-Reply", "value": [6, 8]}, {"name": "Materialized View", "value": [5, 7]}, {"name": "Cache-Aside", "value": [3, 7]}, {"name": "Index Table", "value": [4, 7]}, {"name": "CQRS", "value": [8, 9]}, {"name": "Claim Check", "value": [3, 6]}, {"name": "Strangler Fig", "value": [8, 9]}, {"name": "Sidecar", "value": [4, 6]}, {"name": "Sharding", "value": [9, 9]}, {"name": "Gateway Aggregation", "value": [5, 7]}, {"name": "Gateway Routing", "value": [4, 7]}, {"name": "Pub-Sub", "value": [6, 8]}, {"name": "Health Endpoint", "value": [2, 6]}, {"name": "Leader Election", "value": [7, 7]}, {"name": "External Config Store", "value": [3, 6]}, {"name": "Federated Identity", "value": [7, 8]}, {"name": "Saga", "value": [7, 9]} ], "label": { "show": true, "position": "top", "formatter": "{b}" } }] }

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:

PatternFailure mode it addressesLayer
Health EndpointOperating blind — cannot tell a transient blip from a chronic outageDetection (feeds the others)
Circuit BreakerCascading failures — one slow downstream takes down the whole meshProtection (gate before the call)
RetryTransient blips — network jitter, GC pauses, brief overloadRecovery (wraps the call)
BulkheadResource starvation — one noisy neighbor exhausts shared resourcesIsolation (resource ceiling per downstream)
flowchart TB Caller[Caller] Caller --> Bulkhead[Bulkhead<br/>caps thread pool<br/>per downstream] Bulkhead --> Breaker{Circuit Breaker<br/>open / half-open / closed} Breaker -->|closed| Retry[Retry loop<br/>exponential backoff<br/>idempotent] Retry --> Call[Service call] Breaker -->|open| FailFast[Fail fast] Health[Health Endpoint<br/>probed by monitoring] Health -.feeds state.-> Breaker style Bulkhead fill:#c8e6c9 style Breaker fill:#fff9c4 style Retry fill:#e1f5ff style Health fill:#f5f5f5

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:

PatternFailure mode it addressesPosition in the call flow
Rate LimitingBackend overload — a misbehaving client or burst saturates upstream servicesGate (reject before doing work)
Gateway AggregationChattery clients — N round trips per screen, painful on mobile and flaky networksComposition (fan out and merge)
Async Request-ReplyBlocking — one slow downstream call stalls the thread and ties up a client connectionLatency relief (202 + poll token for slow calls)
flowchart TB Client[Client] Client --> RL[Rate Limiting<br/>per-client cap<br/>reject early] RL --> GA[Gateway Aggregation<br/>fan-out N calls<br/>merge to 1 response] GA -->|fast call<br/>sync| Sync[Sync handler<br/>immediate response] GA -->|slow call<br/>> threshold| Async[Async Request-Reply<br/>202 + polling token<br/>claim-check style] Sync --> Backends[Backends] Async --> Backends style RL fill:#ffcdd2 style GA fill:#c8e6c9 style Async fill:#e1f5ff

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:

PatternFailure mode it addressesPosition in the call flow
Federated IdentityPer-system re-login — every migrated subsystem invents its own auth, users tire outBoundary (single auth entry at the edge)
Strangler FigBig-bang rewrite risk — old system still serves production while new code lands piecemealRouter (per-feature traffic split)
Anti-Corruption LayerLegacy contamination — new domain models absorb every quirk of old contractsTranslation (legacy model → new domain)
flowchart TB User[User / Client] User --> FID[Federated Identity<br/>unified auth boundary<br/>SSO + token exchange] FID --> SF[Strangler Fig<br/>per-feature routing<br/>old path vs new path] SF -->|migrated feature| NewSvc[New Microservice<br/>clean domain] SF -->|unmigrated feature| Legacy[Legacy System] Legacy --> ACL[Anti-Corruption Layer<br/>translate legacy model<br/>to new domain] ACL --> NewSvc style FID fill:#fff9c4 style SF fill:#c8e6c9 style ACL fill:#f8bbd0 style NewSvc fill:#e1f5ff style Legacy fill:#f5f5f5

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:

PatternFailure mode it addressesPosition in the flow
OutboxDual-write anomaly — DB committed but event never published (or vice versa), state and bus divergeWrite phase (event stored atomically with the local DB commit)
SagaCross-service ACID impossibility — no global transaction across services, partial failures leave dangling stateCoordination (forward steps + semantic reversals)
Circuit BreakerSaga-step overload — a slow participant blocks the orchestrator and stalls every subsequent stepPer-step protection (cap blast radius of each call)
flowchart TB Tx[Local DB Tx<br/>business write +<br/>outbox row appended] Tx --> Relay[Outbox Relay<br/>poll or log-tail<br/>publish to bus] Relay --> Bus[Message Broker] Bus --> Orch[Saga Orchestrator] Orch --> CB{Circuit Breaker<br/>closed / half / open} CB -->|closed| Step[Saga Step Call<br/>to participant service] CB -->|open| FailFast[Fail fast<br/>mark step retryable] Step --> SDb[(Participant DB<br/>own commit)] SDb -.emits next event.-> Bus style Tx fill:#fff9c4 style Relay fill:#f8bbd0 style Orch fill:#c8e6c9 style CB fill:#e1f5ff

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:

  1. Rate Limiting Pattern (January) - Control request rates to throttled services
  2. Anti-Corruption Layer Pattern (February) - Protect architecture from legacy systems
  3. Retry Pattern (March) - Handle transient failures gracefully
  4. Claim Check Pattern (April) - Reduce message payload sizes
  5. Materialized View Pattern (May) - Pre-compute complex queries
  6. Strangler Fig Pattern (June) - Gradually migrate legacy systems
  7. Sidecar Pattern (July) - Add functionality via helper components
  8. Sharding Pattern (August) - Distribute data for scalability
  9. Gateway Aggregation Pattern (September) - Combine multiple API calls
  10. Publisher-Subscriber Pattern (October) - Event-driven communication
  11. Health Endpoint Monitoring Pattern (November) - Proactive health checks
  12. Federated Identity Pattern (December) - Centralized authentication
  13. Circuit Breaker Pattern (January) - Prevent cascading failures
  14. Bulkhead Pattern (March) - Isolate resources to contain failures
  15. Asynchronous Request-Reply Pattern (April) - Handle long-running operations
  16. Saga Pattern (July) - Coordinate multi-service transactions with compensations
  17. Compensating Transaction - Undo work performed by a sequence of steps that together form an eventually consistent operation
  18. Queue-Based Load Leveling - Use a queue as a buffer between a task and a service to smooth intermittent heavy loads
  19. Outbox - Store outgoing messages in an outbox table inside the local transaction; a separate process publishes them reliably
  20. Cache-Aside - Load data on demand into a cache from the underlying data store
  21. Index Table - Create secondary indexes over fields that data-store queries frequently reference
  22. CQRS - Separate read and write operations into different models to scale them independently
  23. Gateway Routing - Route requests to one of multiple services through a single endpoint
  24. Leader Election - Coordinate distributed work by electing one instance as the leader
  25. 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.