Skip to main content

Command Palette

Search for a command to run...

Architecture Patterns: How Systems Are Structured

Updated
11 min read
Architecture Patterns: How Systems Are Structured

Series: System Design · Architecture Patterns — Pillar 7 of 8

Systems Design

# Post What it covers
00 Architecture Patterns: How Systems Are Structured ← you are here Twenty patterns covering monoliths, microservices, events, resilience, deployment, and data processing. How to structure systems that survive growth.
01 Monolithic Architecture: The Default That Gets Abandoned Too Early Monoliths are fast to build and easy to operate. Learn when they're the right choice, when they break down, and how to know the difference.
02 Microservices: The Architecture You Earn, Not Choose Microservices enable independent scaling and team autonomy — but at significant cost. Learn what you actually get, what you pay, and when it's worth it.
03 Serverless: Pay for What You Use, Not What You Provision Serverless scales to zero and charges per invocation. Learn where it shines, where it fails, and how to design around cold starts and vendor lock-in.
04 Event-Driven Architecture: Decoupling Through Events Event-driven systems communicate via events rather than direct calls. Learn how producers, consumers, and event brokers work — and the consistency tradeoffs involved.
05 Message Queues: Decoupling Produce from Consume Message queues decouple producers and consumers, enable load levelling, and provide durability. Learn how they work and when to use Kafka vs SQS vs RabbitMQ.
06 Pub/Sub: Broadcasting Events to Multiple Consumers Pub/sub decouples publishers from subscribers through topics. Learn how it differs from message queues and when to use Kafka, SNS, or Google Pub/Sub.
07 CQRS: When Reads and Writes Need Different Models CQRS separates writes from reads so each can be optimised independently. Learn how it works, when it's worth the complexity, and when it isn't.
08 Event Sourcing: The Ledger, Not the Balance Event sourcing stores state as a sequence of events. Learn how it works, what you get (audit log, time travel), and what it costs (complexity, schema evolution).
09 The Saga Pattern: Distributed Transactions Without Locks The Saga pattern manages distributed transactions across services using compensating transactions. Learn choreography vs orchestration and when to use each.
10 The Outbox Pattern: Atomic Writes and Event Publishing The Outbox pattern solves the dual-write problem — publishing an event and writing to a database atomically. Learn how it works using CDC or polling.
11 The Circuit Breaker: Stopping Cascading Failures Circuit breakers prevent cascading failures by fast-failing calls to unhealthy dependencies. Learn the three states, how to configure them, and where to apply them.
12 The Bulkhead Pattern: Containing Failures Through Resource Isolation Bulkheads isolate thread pools and connections per dependency so one failure can't exhaust resources needed by others. Learn how to apply them in practice.
13 The Sidecar Pattern: Cross-Cutting Concerns Without Code Changes The sidecar pattern deploys a helper process alongside each service for logging, metrics, TLS, and service discovery — without modifying the service itself.
14 Service Mesh: A Programmable Network for Microservices A service mesh handles service-to-service traffic, mTLS, circuit breaking, and observability via a fleet of sidecar proxies. Learn how it works and when to use it.
15 Service Discovery: Finding Services in a Dynamic Environment Service discovery lets services find each other in dynamic environments. Learn client-side vs server-side discovery, health checks, and DNS vs registry approaches.
16 The Strangler Fig: Replacing a Legacy System Without Burning It Down The Strangler Fig replaces a legacy system incrementally by routing specific functionality to new implementations while the old system keeps running.
17 Backend for Frontend: One API Per Client Type BFF creates dedicated API backends per client type. Learn why one general API struggles to serve mobile and web well, and how BFF solves it.
18 ETL Pipelines: Moving Data from Operations to Analytics ETL moves data from operational systems into analytical stores. Learn how pipelines work, what ELT is, and how to design reliable data movement at scale.
19 Batch vs Stream Processing: How Fresh Do Your Answers Need to Be? Batch processes accumulate data then processes in bulk; streaming processes each event as it arrives. Learn the tradeoffs and when each is right.
20 MapReduce: Processing Petabytes in Parallel MapReduce processes massive datasets in parallel by splitting work into map and reduce phases. Learn how it works and why Spark has largely replaced it.
21 Architecture Patterns: Wrap-Up A recap of all 20 architecture patterns across decomposition, async communication, data patterns, resilience, and data processing. How they connect.

Architecture Patterns: How Systems Are Structured

The scenario

Your URL shortener started as a single Rails application on a single server. One codebase, one deployment, one database. You could deploy in a minute, reason about the whole system in your head, and debug anything by tailing one log file.

Two years later, different parts of the system have different scaling requirements. The redirect engine needs to handle a million requests per second and is latency-sensitive to the millisecond. The analytics pipeline processes billions of events per day and can tolerate minutes of lag. The billing system runs nightly reconciliation jobs against months of data. The notification service sends emails and Slack messages asynchronously. The team has grown — six squads, each wanting to deploy independently without coordinating with everyone else.

None of these requirements fit the same deployment shape. The redirect engine needs horizontal scaling and aggressive caching. Analytics needs streaming infrastructure. Billing needs batch processing windows. Notifications need asynchronous queues. Independent deployments need service boundaries.

Every decision you make about how to structure the system creates possibilities and closes others. Monolith or microservices? Synchronous calls or message queues? Stateful workflows or event sourcing? Tightly coupled or resilient with circuit breakers?

This pillar covers the patterns that define those decisions — not as prescriptions, but as tools with known tradeoffs.

Twenty concepts across five themes:

  • System decomposition — how to divide (or not divide) a system into services: Monolithic, Microservices, Serverless
  • Asynchronous communication — how services communicate without blocking each other: Event-Driven Architecture, Message Queues, Pub/Sub
  • Data and workflow patterns — how to model state, commands, queries, and multi-service workflows: CQRS, Event Sourcing, Saga, Outbox
  • Resilience patterns — how services stay healthy when their dependencies fail: Circuit Breaker, Bulkhead, Sidecar, Service Mesh, Service Discovery
  • Evolution and data processing — how to change systems safely and process data at scale: Strangler Fig, Backend for Frontend, ETL, Batch vs Stream, MapReduce

TL;DR: There is no universally correct architecture — only tradeoffs that match or mismatch your constraints. A monolith is faster to build and easier to operate; microservices enable independent scaling and deployment at the cost of distributed systems complexity. Asynchronous patterns (queues, pub/sub) decouple producers from consumers but add eventual consistency and operational overhead. CQRS and Event Sourcing are powerful but require significant commitment and are easy to over-apply. Circuit breakers and bulkheads make services resilient to partial failures — in microservices, they're not optional. The Strangler Fig is how you safely migrate a legacy system without a risky big-bang rewrite.


What this pillar covers

System decomposition

Monolithic Architecture — one codebase, one deployable unit. Simple, fast to build, easy to test. Becomes painful when the team is large and the codebase grows, not before. Most systems should start here.

Microservices — the system is decomposed into independently deployable services, each owning its data. Enables team autonomy and independent scaling. Introduces distributed systems complexity (network failures, eventual consistency, distributed tracing) that must be paid for up front.

Serverless — functions deployed without managing servers, billed per invocation, scaling to zero when idle. Excellent for event-driven, infrequent, or bursty workloads. Poor fit for latency-sensitive hot paths or long-running processes.


Asynchronous communication

Event-Driven Architecture — services communicate by publishing and consuming events rather than making synchronous calls. Decouples producers from consumers — the producer doesn't know who's listening. Enables high throughput and resilience at the cost of eventual consistency and harder debuggability.

Message Queues — a producer sends messages to a queue; a consumer reads from it at its own pace. Point-to-point: one consumer per message. Enables load levelling, retry logic, and decoupling of produce and consume rates.

Pub/Sub — a publisher broadcasts events to a topic; multiple subscribers each receive a copy. One-to-many: every subscriber gets every event. The foundation of event-driven architectures at scale.


Data and workflow patterns

CQRS (Command Query Responsibility Segregation) — separate the write model (commands that change state) from the read model (queries that return data). Enables independent optimisation of each side. Often combined with Event Sourcing.

Event Sourcing — store state as a sequence of events rather than as the current value. Every state change is appended as a new event; current state is computed by replaying events. Provides a complete audit log and enables temporal queries, at the cost of query complexity and event schema evolution challenges.

Saga — a pattern for managing distributed transactions across multiple services. Instead of a single ACID transaction, a saga is a sequence of local transactions, each publishing events. If a step fails, compensating transactions undo previous steps.

Outbox Pattern — ensures that a database write and a message publish happen atomically by writing the message to a database "outbox" table in the same transaction, then relaying it to the message broker from the outbox.


Resilience patterns

Circuit Breaker — when calls to a dependency start failing, the circuit "opens" and fast-fails subsequent calls rather than waiting for timeouts. Prevents a failing service from dragging down its callers.

Bulkhead — isolates resources (threads, connections, memory) per dependency so that a slow or failing dependency can't exhaust resources needed by other dependencies.

Sidecar — a helper process deployed alongside each service instance, handling cross-cutting concerns (logging, metrics, TLS, service discovery) so the service itself doesn't have to.

Service Mesh — a network layer of sidecar proxies that handles service-to-service communication: traffic routing, load balancing, mTLS, circuit breaking, and observability — transparently, without application code changes.

Service Discovery — the mechanism by which services find each other's network locations in a dynamic environment where instances start, stop, and change IP constantly.


Evolution and data processing

Strangler Fig — incrementally replace a legacy system by routing specific functionality to a new implementation while the legacy system continues running. Low risk, no big-bang rewrite.

Backend for Frontend (BFF) — create a dedicated backend per client type (mobile, web, third-party API) rather than one general-purpose API. Each BFF is optimised for its client's specific data needs.

ETL Pipelines — Extract, Transform, Load: move data from operational systems into a data warehouse or analytics store, transforming it for analytical queries.

Batch vs Stream Processing — batch processes accumulate data and process it in chunks; stream processing handles each event as it arrives. The right choice depends on how fresh the results need to be and the volume of data.

MapReduce — a programming model for processing large datasets in parallel across a distributed cluster: map phase (process each record independently), reduce phase (aggregate results).


The URL shortener at this stage

Entering Pillar 7, the URL shortener has a full data layer (Pillar 4), a caching layer (Pillar 5), and an infrastructure layer (Pillar 6). What it doesn't have is a clear architectural pattern. The application code itself — how services are structured, how they communicate, how failures are handled, how the analytics pipeline is built — is still undefined.

By the end of this pillar, the URL shortener will have a complete architecture: a hybrid monolith + selective microservices, with event-driven analytics, saga-based link creation, outbox-pattern event publishing, circuit breakers on all external dependencies, and a Strangler Fig plan for migrating the legacy redirect engine to a high-performance Rust service.


→ Next: Monolithic Architecture — the right default, and when it stops being right

Systems Design

Part 1 of 50

Understanding these system design concepts is essential for architects, developers, and engineers to create scalable, reliable, and maintainable software systems that meet the needs of businesses.

More from this blog

Cloud Tuned

729 posts

Your starting point for anything cloud: AWS, Azure, GCP, Serverless, Architecture, Hybrid Cloud, Systems Design and other Information Technology topics.