1 Monolith Architecture
The default choice for most projects — and that's a good thing.
What It Is
A monolith is a single deployable unit containing all application logic. The entire codebase — UI, business logic, data access — is compiled and deployed together.
When to Use It
Use When Recommended
- Your team is fewer than ~20 engineers
- You're building a new product / MVP
- Domain boundaries are unclear
- You want simple local development and debugging
- You need fast iteration speed
- Deployment simplicity matters (one artifact, one pipeline)
Don't Use When Caution
- Multiple teams need independent deployment cadences
- Parts of the system need radically different scaling profiles (e.g., CPU-heavy image processing vs I/O-heavy API)
- Regulatory/compliance requires strict service isolation
- You genuinely have 100+ engineers on one codebase and build times are measured in hours
The Modular Monolith
The best of both worlds: keep a single deployable but enforce strict module boundaries internally. Each module owns its data, exposes an internal API, and can be extracted to a service later if needed.
Trade-offs
| Advantage | Disadvantage |
|---|---|
| Simple deployment (1 artifact) | Scaling is all-or-nothing |
| Easy local development | Long build times as codebase grows |
| In-process calls (no network latency) | Technology lock-in (one language/framework) |
| Simple transactions (ACID) | Big-bang releases can be risky |
| Easy debugging/profiling | One bug can take down everything |
"If you can't build a well-structured monolith, what makes you think microservices are the answer?" — Simon Brown
When NOT to Migrate to Microservices
2 Microservices Architecture
Powerful but expensive — both in engineering effort and operational overhead.
What It Is
Independently deployable services, each owning its own data, communicating over the network (HTTP, gRPC, messaging). Each service is small enough to be owned by one team.
When It Genuinely Makes Sense
- Organizational alignment (Conway's Law) — You have multiple autonomous teams (6+ teams) that need to deploy independently without coordinating releases.
- Different scaling needs — Your search service needs 50 instances while your admin panel needs 2.
- Different technology requirements — ML model serving in Python, real-time API in Go, legacy integration in Java.
- Fault isolation — A failure in image processing must not take down checkout.
- You've already built a modular monolith — and you've identified the exact seams where services should be split.
The Costs Nobody Talks About
Network Latency
In-process function calls take nanoseconds. Network calls take milliseconds. A request that touches 5 services adds 5x network round trips. Multiply by retries and you have a latency problem.
Distributed Transactions
No more simple database transactions. You need sagas, compensating transactions, eventual consistency, and idempotency everywhere. This is genuinely hard to get right.
Operational Complexity
You need: service discovery, distributed tracing, centralized logging, health checks, circuit breakers, container orchestration, CI/CD per service, contract testing, API versioning.
Debugging
A bug that spans 3 services requires correlating logs across systems, understanding async message flows, and reasoning about eventual consistency states. Stack traces don't cross network boundaries.
Prerequisites Before Adopting
3 Service-Oriented Architecture (SOA)
The enterprise predecessor to microservices — still alive in large organizations.
What It Is
SOA organizes an application as a collection of services that communicate through an Enterprise Service Bus (ESB). Services tend to be larger than microservices and often share data through a common schema or canonical data model.
SOA vs Microservices
| Aspect | SOA | Microservices |
|---|---|---|
| Service size | Larger, business-domain scoped | Small, single-responsibility |
| Communication | ESB (smart pipes) | Dumb pipes, smart endpoints |
| Data | Often shared database | Database per service |
| Governance | Centralized | Decentralized |
| Protocol | SOAP/XML, WS-* | REST/gRPC, JSON/Protobuf |
| Deployment | Often coordinated | Independent |
When Enterprises Still Use It
Large organizations with existing ESB investments (MuleSoft, IBM Integration Bus, TIBCO) continue using SOA because: (1) migration cost is enormous, (2) it works for their integration needs, (3) centralized governance suits regulated industries. New greenfield projects rarely choose SOA over microservices.
4 Event-Driven Architecture
React to things that happened rather than asking for things.
Core Concepts
Event Sourcing
Store every state change as an immutable event. Current state = replay all events. Gives you a complete audit log and the ability to rebuild state at any point in time.
Hard Increases storage, requires snapshots for performance, eventual consistency.
CQRS
Command Query Responsibility Segregation — separate the write model (commands) from the read model (queries). Write to a normalized store, project into denormalized read views.
When Read/write patterns differ drastically. Don't use for simple CRUD.
Event Bus
A central channel (Kafka, RabbitMQ, EventBridge) where producers emit events and consumers subscribe. Decouples producers from consumers completely.
Enables Loose coupling, independent scaling, async processing.
Eventual Consistency Trade-offs
Strong Consistency
Every read returns the latest write. Simple to reason about. Requires synchronous coordination. Limits availability (CAP theorem).
Use for Financial transactions, inventory counts, user auth.
Eventual Consistency
Reads may return stale data temporarily. Higher availability and partition tolerance. Requires idempotent consumers and conflict resolution.
Use for Notifications, analytics, search indexes, recommendations.
When to Use Event-Driven
- Use: Workflows that span multiple services, audit logs, real-time data pipelines, notifications, decoupled integrations.
- Avoid: Simple CRUD apps, when you need immediate consistency, when your team is unfamiliar with async patterns.
5 Serverless Architecture
No servers to manage — but plenty of trade-offs to understand.
Two Flavors
FaaS Functions as a Service
AWS Lambda, Google Cloud Functions, Azure Functions. Deploy individual functions triggered by events (HTTP, queue, schedule). Auto-scales to zero.
BaaS Backend as a Service
Firebase, Supabase, AWS Amplify. Managed services for auth, database, storage, push notifications. Write frontend code, let the platform handle the backend.
Trade-offs
| Factor | Detail |
|---|---|
| Cold Starts | First invocation after idle: 100ms-5s depending on runtime (JVM worst, Node/Python best). Use provisioned concurrency for latency-sensitive paths — but that defeats the cost model. |
| Vendor Lock-in | Lambda + DynamoDB + API Gateway + Step Functions = deeply coupled to AWS. Mitigation: use frameworks like Serverless Framework or SST, keep business logic portable. |
| Cost Model | Cheap for spiky/low traffic. Expensive for sustained high throughput. At ~1M requests/day consistently, a container is cheaper. |
| Execution Limits | Max execution time (15 min Lambda), memory limits, payload size limits, concurrency limits. Not suitable for long-running processes. |
| Local Dev | Hard to replicate cloud triggers locally. Tools like SAM, LocalStack help but aren't perfect. |
| Observability | Distributed across many functions. Need structured logging and tracing (X-Ray, Datadog) from day one. |
When Serverless Makes Sense
6 Synchronous Communication
REST, gRPC, GraphQL — and when each one wins.
Comparison
| Aspect | REST | gRPC | GraphQL |
|---|---|---|---|
| Protocol | HTTP/1.1 (or 2) | HTTP/2 | HTTP (typically POST) |
| Serialization | JSON (text) | Protobuf (binary) | JSON (text) |
| Contract | OpenAPI / Swagger | .proto files | Schema (SDL) |
| Performance | Good | Best (binary, multiplexing) | Varies (can over/under fetch) |
| Streaming | SSE, WebSocket (separate) | Native (bidirectional) | Subscriptions (via WS) |
| Browser support | Native | Needs grpc-web proxy | Native |
| Caching | HTTP caching built-in | No HTTP caching | Complex (needs persisted queries) |
| Learning curve | Low | Medium | Medium-High |
| Best for | Public APIs, CRUD, web | Internal service-to-service | Frontend-driven APIs, mobile |
When Each Wins
REST Wins When
- Building public/external APIs
- HTTP caching is important
- Team is familiar with REST
- Simple CRUD operations
- Browser clients are primary
gRPC Wins When
- Service-to-service communication
- Low latency required
- Bidirectional streaming needed
- Strict typing via Protobuf
- Polyglot services (code gen)
GraphQL Wins When
- Multiple client types (mobile, web)
- Clients need flexible queries
- Avoiding over-fetching matters
- Aggregating multiple backends
- Rapid frontend iteration
API Versioning Strategies
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | /api/v2/users | Simple, explicit, cacheable | URL proliferation |
| Header | Accept: application/vnd.api+json;v=2 | Clean URLs | Hard to test in browser |
| Query param | /api/users?version=2 | Easy to switch | Can pollute caching |
| No versioning | Additive changes only | Simplest long-term | Breaking changes impossible |
7 Asynchronous Communication
Decouple producers from consumers with message queues and event streams.
Message Queues vs Event Streams
Message Queue
Point-to-point. Producer sends a message, one consumer processes it and the message is removed. Think: task distribution.
Pattern: Work queue, request-reply.
Examples: RabbitMQ, SQS, ActiveMQ.
Event Stream
Pub/sub with persistence. Events are appended to a log. Multiple consumers can read independently, replay from any offset. Think: event log.
Pattern: Event sourcing, CDC, fan-out.
Examples: Kafka, Redpanda, Pulsar.
Communication Patterns
Fire-and-Forget
Producer sends and moves on. No response expected. Best for notifications, logging, analytics events.
Simple No confirmation
Request-Reply (Async)
Producer sends a message with a correlation ID and reply-to queue. Consumer processes and replies. Good for distributed RPC over messaging.
Decoupled Complex
Backpressure
When consumers can't keep up, the system must handle it: drop messages, buffer, slow down producers, or scale consumers. Kafka handles this via consumer lag; RabbitMQ via queue limits.
Critical at scale
8 Message Brokers
RabbitMQ vs Kafka vs SQS vs NATS — a detailed comparison.
| Feature | RabbitMQ | Kafka | AWS SQS | NATS |
|---|---|---|---|---|
| Model | Queue + Exchange | Distributed log | Managed queue | Pub/Sub + Queue |
| Ordering | Per-queue FIFO | Per-partition | FIFO (optional, extra cost) | Per-subject (JetStream) |
| Persistence | Optional (durable queues) | Always (configurable retention) | 4-14 days | JetStream (file/memory) |
| Throughput | ~50K msg/s | Millions msg/s | ~3K msg/s (standard) | ~10M+ msg/s (core NATS) |
| Delivery | At-least-once, at-most-once | At-least-once, exactly-once (with transactions) | At-least-once | At-most-once (core), at-least-once (JS) |
| Consumer Groups | Competing consumers | Native | No (use multiple queues) | Queue groups |
| Replay | No (message deleted) | Yes (offset-based) | No | Yes (JetStream) |
| Protocol | AMQP, MQTT, STOMP | Custom binary | HTTP (AWS SDK) | Custom TCP |
| Ops Complexity | Medium | High (ZK/KRaft, partitions) | Zero (managed) | Low |
| Best For | Task queues, RPC, routing | Event streaming, CDC, analytics | Simple cloud queues, decoupling | Low-latency, IoT, edge |
Pub/Sub vs Queue Pattern
When to Pick Which
Pick RabbitMQ when
- You need complex routing (topic, header, fanout exchanges)
- Task distribution with acknowledgments
- Request-reply pattern over messaging
- Message priority support needed
- Moderate throughput is sufficient
Pick Kafka when
- You need event replay / reprocessing
- High throughput event streaming (100K+ events/s)
- Change data capture (CDC) pipelines
- Multiple consumers reading same events independently
- Event sourcing / audit trail
Pick SQS when
- You're on AWS and want zero ops overhead
- Simple decoupling between services
- Dead letter queue for failed messages
- You don't need replay or consumer groups
Pick NATS when
- Ultra-low latency required
- Edge / IoT deployments
- Simple pub/sub without persistence (core NATS)
- Lightweight footprint (single binary, no JVM)
9 Service Mesh
Infrastructure-layer networking for microservices — Istio, Linkerd, and when it's overkill.
What It Solves
mTLS (Mutual TLS)
Automatic encryption and identity verification between all services. Zero-trust networking without changing application code.
Observability
Automatic distributed tracing, metrics (latency, error rates, throughput) for every service-to-service call. No instrumentation code needed.
Traffic Management
Canary deployments, A/B testing, traffic shifting, header-based routing, fault injection for chaos testing.
Resilience
Retries, timeouts, circuit breakers, rate limiting — all configured via policies, not code.
Sidecar Proxy Pattern
Istio vs Linkerd
| Aspect | Istio | Linkerd |
|---|---|---|
| Proxy | Envoy (C++) | linkerd2-proxy (Rust) |
| Complexity | High (many CRDs, config options) | Low (opinionated, fewer knobs) |
| Resource usage | Higher | Lower |
| Features | Most comprehensive | Core features, simpler |
| Community | Large (Google, IBM) | Smaller (CNCF graduated) |
When It's Overkill
10 API Gateway
The front door to your backend services.
What It Does
Comparison
| Gateway | Type | Strengths | Weaknesses |
|---|---|---|---|
| Kong | Self-hosted / Cloud | Plugin ecosystem, Lua extensibility, PostgreSQL/Cassandra backing | Complex to operate at scale, Enterprise features paywalled |
| AWS API GW | Managed (AWS) | Zero ops, Lambda integration, WebSocket support | Vendor lock-in, cold starts, limited customization, per-request pricing |
| Nginx | Self-hosted | Battle-tested, fast, huge community | Config file-driven (less dynamic), limited API-specific features without Plus |
| Traefik | Self-hosted | Auto-discovery (Docker, K8s labels), Let's Encrypt built-in, Go | Less mature plugin ecosystem than Kong |
| Envoy | Self-hosted | gRPC-native, xDS dynamic config, foundation for Istio | Steep learning curve, YAML-heavy |
When You Need One vs Simple Reverse Proxy
A simple reverse proxy (Nginx/Caddy) suffices when: Single backend, basic routing, SSL termination is all you need.
11 Reverse Proxy vs Forward Proxy vs Load Balancer
Three related but distinct concepts.
Tool Comparison
| Tool | Primary Role | L4/L7 | Best For |
|---|---|---|---|
| Nginx | Reverse proxy + LB | Both | Web serving, reverse proxy, simple LB. Most popular. |
| HAProxy | Load balancer | Both (excels L4) | High-performance TCP/HTTP load balancing, health checks. |
| Envoy | Service proxy | L7 | Service mesh sidecar, gRPC, dynamic configuration, advanced observability. |
| Caddy | Reverse proxy | L7 | Auto HTTPS (Let's Encrypt), simple config, small projects. |
| AWS ALB/NLB | Managed LB | ALB=L7, NLB=L4 | AWS-native, zero ops, auto-scaling. |
12 Backend for Frontend (BFF)
Tailored backends for each client type.
When to Use BFF
- Use: When mobile and web need significantly different data shapes, payloads, or aggregation patterns. Mobile needs minimal payloads; web needs richer data.
- Use: When third-party consumers need a different API contract from your internal clients.
- Avoid: When a single API gateway with response transformation suffices. Don't create BFFs for every client just because the pattern exists.
BFF vs Single API Gateway
BFF Pattern
One backend per client type. Each BFF aggregates, transforms, and optimizes for its client. More code to maintain, but maximum flexibility.
Single API Gateway
One gateway for all clients. Simpler to operate, but all clients get the same API shape. Use field selection or GraphQL to let clients choose what they need.
13 GraphQL Federation
Compose multiple GraphQL services into one unified graph.
When Federation Makes Sense
Good Fit
- Multiple teams own different parts of the graph
- Frontend needs to query across domains in one request
- You already have GraphQL and want to split a monolith graph
- Schema governance is important
Avoid When
- You have fewer than 3 services (use a single GraphQL server)
- Team doesn't have GraphQL expertise
- REST aggregation with a BFF would be simpler
- Performance is critical (federation adds latency for cross-subgraph queries)
14 Caching
The most impactful performance optimization — and the hardest to get right.
Cache Layers
Caching Strategies
Cache-Aside (Lazy Loading) Most Common
App checks cache first. On miss, reads from DB and writes to cache. App manages both reads and writes. Simple, good for read-heavy workloads.
Risk: Cache and DB can drift. Use TTL to bound staleness.
Write-Through
Every write goes to cache AND database synchronously. Cache is always up-to-date. Higher write latency (two writes per operation).
Best for: Data that's read immediately after writing.
Write-Behind (Write-Back)
Write to cache immediately, asynchronously flush to database. Very fast writes. Risk of data loss if cache node fails before flush.
Best for: Write-heavy workloads where some data loss is acceptable.
Read-Through
Cache itself is responsible for loading from DB on miss (cache acts as the data source). Simplifies app code but requires cache library support.
Best for: When you want the cache layer to handle all data loading logic.
Redis vs Memcached vs Local Cache
| Feature | Redis | Memcached | Local (Caffeine/Guava) |
|---|---|---|---|
| Data structures | Rich (strings, hashes, lists, sets, sorted sets, streams) | Strings only | Key-value |
| Persistence | RDB + AOF | None | In-process (lost on restart) |
| Clustering | Redis Cluster | Client-side sharding | N/A (per-instance) |
| Pub/Sub | Yes | No | No |
| Latency | ~0.5ms (network) | ~0.5ms (network) | ~ns (in-process) |
| Use when | Shared cache, sessions, leaderboards, rate limiting | Simple distributed cache, large blob storage | Hot data, per-instance cache, no consistency needs |
Cache Invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
Strategies: TTL-based (set expiry, accept staleness window), Event-based (invalidate on write via pub/sub), Version-based (cache key includes version/hash). In practice, use TTL as a safety net combined with event-based invalidation for critical data.
When Caching Hurts
15 Database Selection Guide
Choosing the right database for the job — a comprehensive comparison.
| Type | Database | Data Model | Best Use Cases | Consistency | Scaling |
|---|---|---|---|---|---|
| Relational | PostgreSQL | Tables, rows, SQL | General purpose, complex queries, JSONB support, GIS | Strong (ACID) | Vertical + read replicas |
| Relational | MySQL | Tables, rows, SQL | Web apps, WordPress, mature ecosystem | Strong (ACID) | Vertical + read replicas |
| Document | MongoDB | JSON documents | Flexible schemas, content management, catalogs, prototyping | Tunable | Horizontal (sharding) |
| Key-Value | Redis | Key → value | Caching, sessions, leaderboards, rate limiting, pub/sub | Single-key atomic | Redis Cluster |
| Key-Value | DynamoDB | Key → value/doc | Serverless, known access patterns, high throughput | Eventual/Strong | Horizontal (managed) |
| Wide-Column | Cassandra | Column families | Time-series at scale, IoT, write-heavy, multi-DC | Tunable (eventual default) | Horizontal (linear) |
| Graph | Neo4j | Nodes + edges | Social networks, recommendations, fraud detection, knowledge graphs | ACID | Vertical (cluster for HA) |
| Time-Series | InfluxDB / TimescaleDB | Time-indexed points | Metrics, monitoring, IoT telemetry, financial ticks | Varies | Horizontal (TimescaleDB on PG) |
| Search | Elasticsearch | Inverted index | Full-text search, log analytics, autocomplete, geo search | Near real-time | Horizontal (sharding) |
Decision Diagram
16 Database Patterns
Patterns for scaling, consistency, and distributed data management.
Read Replicas
Connection Pooling
Database connections are expensive (TCP handshake, TLS, authentication). Connection pools maintain a set of reusable connections.
| Pooler | Level | Best For |
|---|---|---|
| HikariCP | Application (JVM) | Java/Kotlin apps. Fastest JVM pool. Default in Spring Boot. |
| PgBouncer | External proxy | PostgreSQL. Sits between app and DB. Essential for serverless (Lambda + PG). |
| ProxySQL | External proxy | MySQL. Read/write splitting, query caching, connection multiplexing. |
Database per Service vs Shared Database
Database per Service
Each microservice owns its database. Full autonomy, independent schema evolution, technology freedom. But: no cross-service joins, distributed transactions needed (sagas).
Microservices ideal
Shared Database
Multiple services read/write the same database. Simpler joins and transactions. But: tight coupling, schema changes affect all services, scaling bottleneck.
Monolith / early stage
Saga Pattern for Distributed Transactions
Choreography: simpler, but harder to understand the full flow as it's distributed across services. Orchestration: centralized logic, easier to reason about, but the orchestrator is a single point to maintain.
17 Object Storage & File Storage
S3, block storage, NFS — when to use what.
| Type | Examples | Access Pattern | Best For |
|---|---|---|---|
| Object Storage | S3, MinIO, GCS, Azure Blob | HTTP API (PUT/GET), flat namespace, immutable objects | Images, videos, backups, data lake, static assets. Infinite scale, cheap. |
| Block Storage | EBS, Azure Disk, Persistent Volumes | Mounted as disk, random read/write | Database volumes, OS disks, any app that needs a filesystem. Fixed size, fast I/O. |
| File Storage (NFS) | EFS, FSx, NFS servers | POSIX filesystem, shared mount | Shared filesystem across multiple servers, legacy apps, CMS uploads. Higher latency than block. |
CDN Integration Pattern
18 Circuit Breaker Pattern
Stop cascading failures before they take down your entire system.
State Machine
How It Works
- CLOSED: Requests flow normally. Failures are counted. If failure rate exceeds threshold (e.g., 50% of last 100 calls), circuit opens.
- OPEN: All requests are immediately rejected (fail-fast) with a fallback response. No calls to the downstream service. Timer starts.
- HALF-OPEN: After timeout, a limited number of test requests are allowed through. If they succeed, circuit closes. If they fail, circuit reopens.
Implementation
| Library | Language | Status |
|---|---|---|
| Resilience4j | Java/Kotlin | Active — recommended for JVM |
| Hystrix | Java | Maintenance mode — Netflix deprecated it |
| Polly | .NET | Active |
| opossum | Node.js | Active |
| Service mesh | Any | Istio/Linkerd provide circuit breaking at the proxy level (no library needed) |
When to Use
- Use: When calling external services, databases, or any dependency that can become slow or unavailable.
- Fits both: Works in monoliths (for external API calls) and microservices (for inter-service calls).
- Don't use: For local in-process calls, or when failure is not transient (e.g., auth errors, validation errors).
19 Retry & Timeout Patterns
Handling transient failures gracefully without making things worse.
Exponential Backoff with Jitter
Key Patterns
Idempotency Critical
If a request might be retried, the operation MUST be idempotent (same result if called multiple times). Use idempotency keys: client sends a unique key, server deduplicates. Essential for payment operations.
Timeout Cascades
Service A calls B calls C. If C is slow, B waits, A waits. Set timeouts at each level, with outer timeouts greater than inner ones. Example: A timeout = 5s, B timeout to C = 3s. This prevents thread pool exhaustion.
Bulkhead Pattern
Isolate different call types into separate thread pools or connection pools. A slow dependency in pool A doesn't exhaust resources for pool B. Like ship bulkheads — one leak doesn't sink the ship.
Retry Budget
Limit total retries across the system. "Retry at most 10% of original requests." Prevents retry storms where retries generate more load than original traffic, worsening an outage.
Timeout Guidelines
20 Rate Limiting
Protect your services from abuse and overload.
Algorithms Compared
| Algorithm | How It Works | Pros | Cons |
|---|---|---|---|
| Token Bucket | Bucket fills with tokens at a fixed rate. Each request consumes a token. Allows bursts up to bucket size. | Allows bursts, simple, memory efficient | Burst can overload downstream |
| Leaky Bucket | Requests enter a queue that drains at a fixed rate. Overflow is rejected. | Smooth output rate, no bursts | No burst tolerance, can cause queuing delay |
| Fixed Window | Count requests in fixed time windows (e.g., per minute). Reset at window boundary. | Simple to implement | Boundary problem: 2x burst at window edges |
| Sliding Window Log | Track timestamp of each request. Count requests in the sliding window. | Accurate, no boundary problem | Memory heavy (stores every timestamp) |
| Sliding Window Counter | Hybrid: use previous + current window counts with weighted average. | Accurate, memory efficient | Slightly approximate |
Where to Implement
Distributed Rate Limiting with Redis
INCR + EXPIRE (or Lua script for atomicity) with the key pattern rate:{user_id}:{window}. Each instance atomically increments the counter. When it exceeds the limit, return HTTP 429 with Retry-After header. Use sliding window counter for accuracy.
21 Logging, Metrics, Tracing
The three pillars of observability — you need all three.
Stack Comparison
| Component | Option A | Option B | Managed Alternative |
|---|---|---|---|
| Logging | ELK (Elasticsearch + Logstash + Kibana) | Grafana Loki + Promtail | Datadog, CloudWatch, Splunk |
| Metrics | Prometheus + Grafana | InfluxDB + Grafana | Datadog, New Relic, CloudWatch |
| Tracing | Jaeger | Zipkin | Datadog APM, AWS X-Ray, Honeycomb |
| Unified | OpenTelemetry (vendor-neutral SDK for logs, metrics, traces — the emerging standard) | ||
Best Practices
Structured Logging
Log as JSON, not free text. Include: timestamp, level, service, correlation_id, user_id, message. Makes logs searchable and parseable.
Correlation IDs
Generate a unique ID at the entry point (API Gateway). Pass it through every service call (header: X-Correlation-ID). Include it in every log line. Lets you trace a request across all services.
What to Monitor
The RED method: Rate (requests/sec), Errors (error rate), Duration (latency percentiles p50, p95, p99). Plus: saturation (CPU, memory, disk, connections).
22 Health Checks & Alerting
Know when things break before your users do.
Kubernetes Probes
| Probe | Purpose | On Failure | Example |
|---|---|---|---|
| Liveness | Is the process alive and not deadlocked? | Container is restarted | GET /health/live → 200 (always, unless stuck) |
| Readiness | Can this instance handle traffic? | Removed from load balancer (not restarted) | GET /health/ready → checks DB connection, cache, dependencies |
| Startup | Has the app finished initializing? | Other probes disabled until this passes | For slow-starting apps (JVM warmup, data loading) |
SLOs / SLIs / SLAs
SLI Indicator
A measurable metric. Example: "99.2% of requests completed in under 300ms over the last 28 days." The actual measurement.
SLO Objective
Internal target. "We aim for 99.9% availability." Triggers alerts and engineering priorities when at risk. More stringent than the SLA.
SLA Agreement
External contract with customers. "We guarantee 99.5% uptime or credits are issued." Has financial/legal consequences.
Alerting Strategy
23 Containers & Orchestration
Docker, Kubernetes — and when K8s is overkill.
Kubernetes Core Concepts
When Kubernetes Is Overkill
K8s Makes Sense When
- 10+ microservices that need orchestration
- Need auto-scaling, self-healing, rolling updates
- Multi-team, multi-service deployments
- You have (or can hire) Kubernetes expertise
- Running on multiple clouds or on-premises
K8s Is Overkill When
- 1-3 services (use Docker Compose or ECS)
- Small team without K8s experience
- Serverless/PaaS would be simpler (Heroku, Fly.io, Railway)
- You're managing more YAML than application code
Alternatives
| Tool | Complexity | Best For |
|---|---|---|
| Docker Compose | Low | Local dev, simple multi-container apps, small production deployments |
| AWS ECS | Medium | AWS-native container orchestration without K8s complexity |
| HashiCorp Nomad | Medium | Simpler orchestration, supports containers + non-containers, easy to operate |
| Docker Swarm | Low | Simple clustering, built into Docker. Limited features compared to K8s. |
| Fly.io / Railway | Very Low | Deploy containers with zero orchestration setup. PaaS simplicity. |
24 CI/CD Pipeline
Build, test, deploy — reliably and repeatedly.
Pipeline Stages
Deployment Strategies
Blue-Green
Run two identical environments. Deploy to the idle one (green), test it, then switch traffic. Instant rollback by switching back.
Fast rollback 2x resources
Canary
Route a small percentage (1-5%) of traffic to the new version. Monitor error rates and latency. Gradually increase if healthy. Roll back if issues detected.
Low risk Needs good observability
Rolling
Replace instances one at a time. Default in Kubernetes. No extra resources needed. Slower rollback (must roll forward or replace all).
Resource efficient Mixed versions during deploy
Feature Flags
Decouple deployment from release. Deploy code to production but keep it behind a flag. Enable for specific users (beta testers), percentages, or all users. Tools: LaunchDarkly, Unleash, Flagsmith, or simple config.
GitOps (ArgoCD, Flux)
25 Infrastructure as Code
Define infrastructure in version-controlled files, not ClickOps.
| Tool | Language | Cloud Support | State | Best For |
|---|---|---|---|---|
| Terraform | HCL (declarative) | Multi-cloud | Remote state file (S3, Terraform Cloud) | Most teams, multi-cloud, largest ecosystem of providers |
| Pulumi | TypeScript, Python, Go, etc. | Multi-cloud | Pulumi Cloud or self-managed | Teams that prefer real programming languages over DSLs |
| CloudFormation | JSON/YAML (declarative) | AWS only | Managed by AWS | AWS-only shops, tight AWS integration, no state management |
| CDK | TypeScript, Python, etc. | AWS (synths to CFN) | Managed by AWS | AWS users who want programming languages + CloudFormation |
| OpenTofu | HCL | Multi-cloud | Same as Terraform | Open-source Terraform fork (BSL license concerns) |
Drift Management
terraform plan (shows diff), scheduled drift detection. Prevent: lock down console access (read-only for most), require all changes through IaC pipeline, use policy-as-code (OPA, Sentinel) to enforce compliance.
26 Architecture Decision Records
Document the "why" behind decisions so future you isn't confused.
What Is an ADR?
A short document capturing an architectural decision: the context, the options considered, the decision made, and the consequences. Stored in version control alongside the code (typically docs/adr/).
Template
Status: Accepted
Date: 2025-01-15
Context:
We need a primary database for the application. Our data is highly relational (users, orders, products with many relationships). We need ACID transactions for payment processing. Team has strong SQL experience.
Decision:
We will use PostgreSQL as our primary database.
Alternatives Considered:
- MySQL: Similar capabilities, but PostgreSQL has better JSON support, better extension ecosystem (PostGIS, TimescaleDB).
- MongoDB: Flexible schema, but our data is relational. Would need to denormalize and lose referential integrity.
Consequences:
- (+) Strong consistency, ACID transactions
- (+) JSONB for semi-structured data when needed
- (+) Team expertise, large community
- (-) Vertical scaling primarily (read replicas for reads)
- (-) Need to manage schema migrations carefully
Tips
- Keep ADRs short (1 page max).
- Number them sequentially (ADR-001, ADR-002).
- Mark as Accepted, Superseded, or Deprecated — never delete.
- Write them when you make the decision, not months later.
- Include alternatives considered — this is the most valuable part.
27 When to Use What — Decision Matrix
Quick-reference decision trees for common architectural choices.
I Need Caching
I Need Async Communication
I Need a Database
I Need an API Style
I Need an API Gateway
Architecture Style Selection
Comprehensive Quick Reference
| I Need... | Default Choice | Consider Instead If... |
|---|---|---|
| A database | PostgreSQL | Key-value speed (Redis), documents (MongoDB), graph (Neo4j), time-series (TimescaleDB) |
| Caching | Redis | In-process only (Caffeine), simple blob cache (Memcached), static assets (CDN) |
| Message broker | SQS (AWS) / RabbitMQ (self-hosted) | Event streaming (Kafka), ultra-low-latency (NATS) |
| API style | REST | Internal + performance (gRPC), flexible frontend queries (GraphQL) |
| Container orchestration | Docker Compose (small) / ECS (AWS) | Multi-cloud / complex (Kubernetes), simpler alt (Nomad) |
| IaC | Terraform | AWS-only (CDK/CloudFormation), real languages (Pulumi) |
| CI/CD | GitHub Actions | Complex pipelines (GitLab CI), enterprise (Jenkins), GitOps (ArgoCD) |
| Observability | OpenTelemetry + Grafana stack | Managed (Datadog), AWS-native (CloudWatch + X-Ray) |
| API Gateway | Nginx (simple) / Kong (advanced) | AWS (API Gateway), K8s-native (Traefik), service mesh (Envoy) |
| Deployment strategy | Rolling (default in K8s) | Need instant rollback (blue-green), need gradual (canary) |