Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions _docs/00-setting-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ AI coding tools turn intent into code at a rate that wasn't possible before. Ask
one, in a sentence, for a microservices decomposition, a saga, a cache layer, a
strangler-fig wrapper, or a CQRS write side, and you get back something plausible
enough to compile, deploy, and run. The bottleneck has moved. It is no longer
typing; it is *evaluation* — telling a good answer from a confident, plausible,
typing; it is *evaluation*: telling a good answer from a confident, plausible,
wrong one.

That is what this book is about. The patterns here are not nostalgic
Expand All @@ -24,35 +24,35 @@ amplifier; the patterns decide which direction it amplifies.
Every concrete choice in this book applies four ideas. Keep
them in view:

- **The least-worst decision.** Every architecture choice is a set of trades —
- **The least-worst decision.** Every architecture choice is a set of trades:
latency for throughput, consistency for availability, simplicity for
flexibility. The job is not to find the option with no downside; it is to make
the trades visible and explicit.
- **Risk management.** You manage the *distribution* of failures, not their
existence. Failure doesn't go away; you choose where it lands and how it's
contained.
- **No free lunch.** Every pattern — a cache, a saga, eventing, decomposition —
has a cost: a stale-read window, a compensation path, a coordination tax,
operational complexity. The question is whether you are buying the right thing
for what you are paying.
- **No free lunch.** Every pattern, whether a cache, a saga, eventing, or
decomposition, has a cost: a stale-read window, a compensation path, a
coordination tax, operational complexity. The question is whether you are
buying the right thing for what you are paying.
- **No problem cannot be made worse.** A confidently-wrong intervention amplifies
the original pain. AI tooling makes confident interventions cheap,
which is exactly why this maxim is needed.

## Three layers of patterns literacy

"Pattern" spans three layers, and they build on each other. **Design patterns**
are code-level vocabulary — the Gang of Four: factory, strategy, observer,
repository. **Architecture patterns** describe how an application fits together —
are code-level vocabulary, the Gang of Four: factory, strategy, observer,
repository. **Architecture patterns** describe how an application fits together:
hexagonal, microservices, BFF, federation, event-driven. **System design
patterns** — the focus of this book — span services, data, and runtime: sagas,
patterns**, the focus of this book, span services, data, and runtime: sagas,
the outbox, CQRS, caching, the strangler fig, back-pressure, the bulkhead, L7
routing.

{% include excalidraw.html
file="00-patterns-literacy"
alt="Three stacked layers: design patterns (Gang of Four) at the bottom, architecture patterns in the middle, and system design patterns — the focus of this book — on top, with arrows showing each lower layer informing the next"
caption="Figure 0.1 — Each lower layer informs the one above it" %}
alt="Three stacked layers: design patterns (Gang of Four) at the bottom, architecture patterns in the middle, and system design patterns, the focus of this book, on top, with arrows showing each lower layer informing the next"
caption="Figure 0.1: Each lower layer informs the one above it" %}

The layers are not independent. You can't reason about a saga (system layer)
without knowing what a state machine and a compensation are (architecture and
Expand All @@ -62,19 +62,19 @@ what lets you ask an AI for the right thing and judge what it returns.

## The running example

Every concern in this book is designed against one small system — the same
Every concern in this book is designed against one small system: the same
example, re-examined from each angle. Anchor on it now; we return to this picture
many times.

{% include excalidraw.html
file="00-running-example"
alt="A client calls order-service over REST; order-service calls inventory, payment, and shipping over gRPC and emits an order.placed event to Kafka; a notification service consumes that event; a graphql-gateway composes reads across the internal services"
caption="Figure 0.2 — Five services and a gateway, each exposing only the protocols that fit its role" %}
caption="Figure 0.2: Five services and a gateway, each exposing only the protocols that fit its role" %}

The point of the picture is that **each service exposes only the protocols that
fit its role**, not a uniform surface:

- **order-service** owns the order lifecycle and exposes REST at the edge — the
- **order-service** owns the order lifecycle and exposes REST at the edge: the
one public, client-facing surface.
- **inventory**, **payment**, and **shipping** are internal. They are reached by
gRPC for low-latency service-to-service calls, and they contribute fields to
Expand All @@ -95,10 +95,10 @@ choice; then the synchronous surface (communications, composition); then data
ownership; then the asynchronous backbone (events, streams); then the operational
concerns (workflows and the platform planes); and finally anti-patterns as a
reflective close. The deep-dive appendices at the end go deeper on the questions
that always come up — protocols, versioning, WebSockets, sagas, and more — and
that always come up (protocols, versioning, WebSockets, sagas, and more) and
can be read on demand.

---
*Verification status: conceptual chapter — no runnable code. The patterns named
*Verification status: conceptual chapter; no runnable code. The patterns named
here are defined and applied in their own chapters, where the code ships with a
runnable example per language.*
54 changes: 27 additions & 27 deletions _docs/01-cloud-native-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
title: "Cloud-Native Principles"
order: 1
part: "Foundations & the system"
description: "Cloud-native as a set of runtime properties, not a stack of tools — the six pillars, leaning on the platform, design-first contracts, and the twelve-factor app reframed by who owns each factor."
description: "Cloud-native as a set of runtime properties, not a stack of tools: the six pillars, leaning on the platform, design-first contracts, and the twelve-factor app reframed by who owns each factor."
duration: 17 minutes
---

Before any pattern, the principles that justify them. "Cloud-native" is not a
list of products you adopt; it is a set of **runtime properties** a system has —
list of products you adopt; it is a set of **runtime properties** a system has,
and every later choice in this book either earns or spends one of them.

## Six pillars

A cloud-native service exhibits six runtime properties — and the platform exists to
A cloud-native service exhibits six runtime properties, and the platform exists to
make each of them the default:

{% include excalidraw.html
file="01-six-pillars"
alt="A central hub labelled Cloud-native API on Kubernetes radiates spokes to six surrounding pillars: Disposable (fast start, clean stop, no local state), Resilient (timeouts, retries, circuit breakers, graceful degradation), Automatable (declarative config, GitOps and CRDs, self-healing), Observable (traces, metrics, logs, health, RED/USE signals), Elastic (scale to load, scale to zero with KEDA), and Loosely coupled (contracts not calls, async where it fits, independent deploy). A note reads: cloud-native is a set of runtime properties an API must exhibit to thrive on Kubernetes, not a checklist of tools."
caption="Figure 1.1 — Cloud-native in six pillars: the runtime properties an API must exhibit on Kubernetes" %}
caption="Figure 1.1: Cloud-native in six pillars: the runtime properties an API must exhibit on Kubernetes" %}

Read each as a behaviour you design toward, not a product you install:

- **Disposable** — twelve-factor: fast start, clean stop, no local state, so the
- **Disposable**: twelve-factor: fast start, clean stop, no local state, so the
platform can move or restart it at will.
- **Observable** — it emits traces, metrics, and logs, and exposes health probes.
- **Resilient** — timeouts, retries, circuit breakers, graceful degradation.
- **Elastic** — it scales to load, and to zero.
- **Automatable** — declarative config, GitOps, self-healing.
- **Loosely coupled** — contracts not calls, async where it fits.
- **Observable**: it emits traces, metrics, and logs, and exposes health probes.
- **Resilient**: timeouts, retries, circuit breakers, graceful degradation.
- **Elastic**: it scales to load, and to zero.
- **Automatable**: declarative config, GitOps, self-healing.
- **Loosely coupled**: contracts not calls, async where it fits.

These six are the rubric for every pattern in this book. Hold each design decision up
to them.
Expand All @@ -46,11 +46,11 @@ default; your job is to stay out of its way and use it.
## Design-first: the contract precedes the code

Design-first is the cultural prerequisite for everything that follows. The
contract — OpenAPI, `.proto`, AsyncAPI — is agreed and registered *before* code
contract (OpenAPI, `.proto`, AsyncAPI) is agreed and registered *before* code
is written, owned by the producing domain, and gated in CI. Consumers code
against the contract and its generated stubs, never against your database. That
is what delivers independent deployability: the contract *is* the
service boundary. Skip it and you get distributed coupling — the worst of both
service boundary. Skip it and you get distributed coupling: the worst of both
worlds.

## The twelve-factor app, revisited
Expand All @@ -62,47 +62,47 @@ Here are all twelve, one line each.
{% include excalidraw.html
file="01-twelve-factors"
alt="A grid of the twelve factors, each with a number, name, and one-line summary: 01 codebase (one repo, many deploys), 02 dependencies (declare and isolate), 03 config (read from the environment), 04 backing services (treat as attached resources), 05 build/release/run (keep the stages separate), 06 processes (stateless, share-nothing), 07 port binding (export a service via a port), 08 concurrency (scale out with processes), 09 disposability (fast start, graceful stop), 10 dev/prod parity (keep environments alike), 11 logs (treat as event streams), 12 admin processes (run as one-off tasks)."
caption="Figure 1.2 — The twelve factors at a glance" %}
caption="Figure 1.2: The twelve factors at a glance" %}

The useful way to read it today is not to recite all twelve, but to split them by
**who owns each one** — the few you still write, versus the many the platform now
**who owns each one**: the few you still write, versus the many the platform now
provides built in.

{% include excalidraw.html
file="01-twelve-factor-ownership"
alt="Two columns: the factors you still write per service (config from the environment, stateless processes, fast clean start and stop, logs as event streams, health and readiness probes) versus the factors the platform provides (build/release/run, concurrency and scaling, disposability, port binding, backing services, retries and timeouts)"
caption="Figure 1.3 — The twelve factors, split by who owns them" %}
caption="Figure 1.3: The twelve factors, split by who owns them" %}

The left column is your job; the right column is the platform's. The whole point
of a cloud-native service is to do the left column well and trust the right
column — which is exactly what this code does.
column, which is exactly what this code does.

## Twelve factors, mapped to our system

Grouped by what each factor *asks of you*, the twelve land on concrete choices in
the running example — and most resolve to a platform default rather than code you
the running example, and most resolve to a platform default rather than code you
write.

{% include excalidraw.html
file="01-factors-mapped"
alt="Five rows mapping groups of factors to the system: build and ship becomes built once, run anywhere (one repo, pinned requirements, one image per release); configure becomes config from the environment (pydantic-settings, per-profile, secrets injected); run on the platform becomes disposable, stateless, scaled (KEDA scale-out and to-zero, fast start/stop, behind Istio); attach resources becomes backing services as swappable URLs (Postgres, Kafka via Strimzi, Apicurio, never in-process); operate becomes signals and admin off the hot path (OpenTelemetry to LGTM, admin as Jobs and CronJobs). Each row carries a section reference."
caption="Figure 1.4 — The twelve factors grouped by ask, mapped to where each lives in the system" %}
caption="Figure 1.4: The twelve factors grouped by ask, mapped to where each lives in the system" %}

Five groups cover all twelve. *Build & ship* (codebase, dependencies, build/release/run,
parity) becomes one repo and one image per release. *Configure* (config) is read from
the environment. *Run on the platform* (stateless processes, concurrency, disposability,
port binding) is the runtime's job — KEDA scales it, Istio fronts it, the platform
port binding) is the runtime's job: KEDA scales it, Istio fronts it, the platform
disposes of it. *Attach resources* (backing services) makes Postgres, Kafka, and the
schema registry swappable URLs, never libraries compiled in. And *operate* (logs, admin
processes) pushes telemetry and one-off tasks off the request hot path, into
OpenTelemetry and Kubernetes Jobs. The section tags on each row point to where that
group is treated in depth — most of the twelve are now adopted, not authored.
group is treated in depth: most of the twelve are now adopted, not authored.

## A twelve-factor service, ready for the platform

Two factors dominate the code you write: **config from the environment**
(factor III) and **health and readiness probes** the platform calls. Here it is
in each stack — pick your tab.
in each stack: pick your tab.

{% include codetabs.html langs="Spring Boot|Quarkus|.NET|Python|C++|Go" %}

Expand Down Expand Up @@ -273,11 +273,11 @@ The same two ideas appear in every tab:
variables, .NET reads `ConnectionStrings__Default` through its configuration
(env vars override `appsettings.json`), Python binds a `BaseSettings` class to env
vars, and C++ and Go read `std::getenv` and `os.Getenv`. One
image, many environments — factor III, and the thing that lets the same
image, many environments: factor III, and the thing that lets the same
artifact run in dev, staging, and production unchanged.
2. **Two probes, not one.** *Liveness* answers "is the process up?" — if it fails,
2. **Two probes, not one.** *Liveness* answers "is the process up?" If it fails,
the platform restarts the pod. *Readiness* answers "are my dependencies
reachable?" — if it fails, the platform stops routing traffic to this instance
reachable?" If it fails, the platform stops routing traffic to this instance
but leaves it running. Conflating the two is a classic mistake: a readiness
blip should drain traffic, not trigger a restart loop. The probe reads the
**status code**, not the body: Kubernetes counts any answer from 200 to 399 as
Expand All @@ -288,19 +288,19 @@ The same two ideas appear in every tab:
framework routes to the same pair, and Kubernetes calls them the same way.

Notice what *isn't* here: no retry logic, no scaling code, no port-binding
plumbing. Those are the right column of Figure 1.3 — the platform's job.
plumbing. Those are the right column of Figure 1.3: the platform's job.

### Cross-check it yourself

With the service running, the probes are just HTTP. Hit liveness and readiness
with `curl` and confirm liveness returns healthy while the database is down but
readiness does *not* — that difference is the whole point. `curl -i
readiness does *not*. That difference is the whole point. `curl -i
localhost:8080/readyz` should flip from `200` ready to `503` down as you stop the
database and back as you start it, without the process ever restarting.

The code is in [`examples/01-cloud-native-principles/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/01-cloud-native-principles/). The run script there builds
and runs it; its `README.md` covers what it does and how to drive it.

---
*Verification status: verified — [`examples/01-cloud-native-principles/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/01-cloud-native-principles/) passes 13/13 checks in each of the six languages on Docker Engine (2026-10-13)
*Verification status: verified; [`examples/01-cloud-native-principles/`](https://github.com/patterncatalyst/cloud-native-design-patterns/tree/main/examples/01-cloud-native-principles/) passes 13/13 checks in each of the six languages on Docker Engine (2026-10-13)
(health probes, config from environment, CRUD, readiness flip on dependency failure).*
Loading
Loading