LearnAWS
AWS13. Architecture Patterns·EXPERT·6 min read

32. Architecture Patterns

Architecture Patterns

TL;DR

  • Every pattern in this category — microservices, CQRS, Saga, circuit breakers, bulkheads — is a deliberate trade-off, not an inherently “correct” modern architecture to adopt by default.
  • Strangler Fig de-risks monolith-to-services migrations by incrementally routing functionality behind a facade instead of a big-bang rewrite.
  • CQRS separates read and write models at the cost of eventual consistency; Saga replaces distributed transactions with compensating actions at the cost of design complexity.
  • Circuit breakers stop calls to a known-failing dependency; bulkheads isolate resource pools per dependency — complementary, not interchangeable.
  • Any pattern adopted without a named cost alongside its named benefit hasn’t actually been evaluated.
Caller Service B Service C (down) Service D breaker: OPEN
fig 1 — a circuit breaker fails fast on the known-bad path to Service C while Service B and D keep serving normally; a bulkhead ensures Service C's exhausted connection pool can't also starve B and D

Core Concepts

Microservices and event-driven architecture

Trade a simpler, single-deployable monolith for independently deployable, independently scalable services — at the real cost of distributed systems complexity: network calls where function calls used to be, eventual consistency where a single database transaction used to be, and a harder operational and debugging story. Worth it when team boundaries and independent deployment cadence justify it, not by default.

Strangler Fig

De-risks the migration from monolith to services by incrementally routing specific pieces of functionality from the old system to new services behind a facade, while the whole system stays working throughout. The alternative — a big-bang rewrite where nothing works until everything’s done — is a common failure mode that stalls real migrations for a year or more, leaving the system in a half-working, half-old, half-new state worse than either endpoint.

CQRS (Command Query Responsibility Segregation)

Separates the read model from the write model — valuable when read and write patterns are different enough (a write-heavy transactional core, a read-heavy denormalized reporting need) that a single shared model strains under both. The real cost, always: eventual consistency between the two models, and the synchronization complexity to keep the read model updated. Adopting CQRS without acknowledging that trade-off — reports sometimes a few seconds stale — leads to confused users asking why a just-completed action isn’t showing up yet.

Saga

Solves distributed transactions across services without a traditional two-phase-commit: each step in a multi-service transaction has a corresponding compensating action, and if a later step fails, earlier steps are reversed via their compensating actions in order. This gives eventual consistency across services at the cost of real design complexity — every step needs a correctly-implemented compensating action, and partial-failure states need to be genuinely handled, not hand-waved.

Hexagonal architecture (ports and adapters)

Isolates core business logic from external concerns (databases, APIs, messaging) behind defined interfaces — valuable for testability and for swapping infrastructure without touching business logic, at the cost of more upfront structure and indirection than a simpler, more directly-coupled design.

Serverless

An architectural pattern choice with its own trade-offs (operational simplicity vs. cold starts and execution limits), not just a deployment target.

Bulkhead and Circuit Breaker

The resilience patterns most directly relevant to a services architecture’s failure modes, solving genuinely different problems that are easy to conflate. A circuit breaker watches calls to a specific dependency, and after detecting repeated failures, “opens” — failing fast without even attempting the call, instead of every request hanging until a slow timeout. This protects the caller from wasting resources on a dependency known to be failing, and gives the downstream service room to recover without being hammered by retries. A bulkhead isolates resources — thread pools, connection pools — per dependency, so a slow or failing call to one dependency can’t exhaust the resources needed for calls to a different, healthy dependency. They’re complementary: a circuit breaker stops calls to a known-bad dependency; a bulkhead stops a bad dependency from starving calls to good ones even before the circuit breaker has tripped.

CQRS vs Saga vs Traditional Distributed Transaction

Aspect CQRS Saga Two-phase commit
Problem solved Read/write model mismatch under one schema Multi-service transaction without a shared DB transaction Atomic commit across services
Consistency model Eventual, between read and write models Eventual, via compensating actions on failure Strong, atomic
Real cost Synchronization complexity, stale reads Every step needs a correct compensating action Tight coupling, poor availability, rarely practical across services
When it fits Divergent read/write patterns at scale Multi-service workflows needing rollback semantics Rarely — avoided in distributed/microservice designs

Common Pitfalls

  • Pitfall: A big-bang microservices rewrite stalls in a half-migrated state. Why: Nothing works end-to-end until the entire rewrite is complete. Fix: Use Strangler Fig — route functionality incrementally behind a facade, keeping the system working throughout.
  • Pitfall: CQRS or Saga adopted without designing for their real cost. Why: Eventual consistency and compensating-action complexity aren’t hand-waved details — they’re core design requirements. Fix: Explicitly design the eventual-consistency window and every compensating action, including partial-failure cases.
  • Pitfall: No circuit breaker on a known-flaky dependency. Why: Every request waits for a slow timeout, tying up resources and risking cascading failure upstream. Fix: Add a circuit breaker that opens after repeated failures and fails fast.
  • Pitfall: Circuit breakers present but no bulkheads. Why: A failing dependency’s exhausted connection pool still starves calls to unrelated, healthy dependencies sharing the same pool. Fix: Isolate resource pools (threads, connections) per dependency.
  • Pitfall: Patterns adopted because they’re associated with sophisticated engineering. Why: No specific problem in the actual system justified the added complexity. Fix: Require an explicit statement of the problem solved and the cost incurred before adopting any pattern.

Interview Questions

  • “A monolith-to-microservices migration stalled for a year in a half-working state. What would you have done differently?” — tests whether Strangler Fig is understood as the risk-reduction answer, not just a term.
  • “Explain the difference between a circuit breaker and a bulkhead with a concrete failure scenario where you’d want both.” — tests understanding of the distinct failure-propagation paths each addresses.
  • “A team wants to adopt CQRS. What questions do you ask before agreeing it’s the right call?” — tests whether the candidate interrogates the actual read/write pattern mismatch and names the eventual-consistency cost explicitly.
  • “A distributed transaction spans four microservices and step three fails after steps one and two succeeded. How do you handle rollback without a shared database transaction?” — tests understanding of the Saga pattern and compensating actions.
🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →
Related in 13. Architecture Patterns
29. SaaS Architecture
EXPERT
35. Enterprise Decision Framework
EXPERT