Event Sourcing plus CQRS plus Materialized View (Fowler's bliki; Event Sourcing popularised by Greg Young, CQRS by Greg Young and Udi Dahan) is the book's golden combo for read-scale, and its heaviest skip-if. The write model becomes an append-only log of events; the read model is projected from it. For the right workload, read-heavy, audit-hungry, with real contention on the write side, it's the pattern that pays for itself.
For an application that creates, reads, updates, and deletes rows, it's a tax with no return. You inherit event versioning, projection rebuilds, eventual consistency between write and read, and a mental model every new hire has to absorb before they can ship a form. A table and a few stored procedures do the same job and fit in your head. Most CRUD apps should stay CRUD.
Event Sourcing is a loan against future read-scale. Don't take it out to store a contact form.
The book's multitenancy default is the dense end of the spectrum: one database, one schema, a tenant_id column, and Row-Level Security so the database itself enforces isolation (PostgreSQL RLS). It's the cheapest tenancy that's still a real guarantee.
Database-per-tenant sits at the far end: maximum isolation, and a multiplied operational cost. Every migration, backup, connection pool, and monitoring dashboard now exists once per customer. You take that on when a contract or a regulator requires physical isolation, or when one tenant's load genuinely poisons the others. Escalate to schema-per-tenant first if you only need customisation. Walk the spectrum as far as you're forced, and no further.
A service mesh (Istio, Linkerd, and the like) gives you mTLS, traffic shaping, retries, and per-call telemetry across a fleet of services, applied uniformly without touching application code. At fleet scale, that uniformity is the whole point. At three services, it's a control plane, a sidecar per pod, and a new operational surface for problems you didn't have.
You can get retries and timeouts from a resilience library (Polly), tracing from OpenTelemetry, and TLS from your ingress, all without standing up a mesh. The honest threshold is dozens of services and a platform team to run the thing. If you're counting your services on one hand, a sidecar per pod is the Ambassador pattern doing one job, not a mesh.
Sharding (Azure Cloud Design Patterns) splits one logical dataset across many physical stores to get past the limits of a single one. It is also one of the hardest things to undo once it's in, because every query and every join now has to know which shard its data lives on.
The trap is sharding for a scale you're forecasting rather than feeling. A single well-indexed Postgres instance, with a read replica when reads outpace it, carries most applications comfortably long past the point teams assume they've outgrown it. Shard when one node is measurably your ceiling and vertical scaling and replicas are exhausted, not when a slide deck predicts you'll get there.
Factory Method is not on this list. It earns its honorable-mention slot in the object chapter: it's the creational glue that picks the right CourierMatchingStrategy at runtime, nearest or fastest or cheapest, without the caller knowing which. Abstract Factory, its bigger sibling (GoF, 1994), builds whole families of related objects behind an interface. It's the right tool when you genuinely swap an entire family at once, a full set of platform-specific widgets, say.
Most of the time it shows up as speculative generality: a factory of factories standing in front of a problem that had exactly one implementation. The cost is layers of indirection a reader has to walk through to find the one concrete class that ever runs. Add the abstraction the day you have the second family. Until then, new is allowed.
Indirection is not a virtue. It's a bet that the future will need it, and most of those bets lose.
Appendix C lists every source behind these calls, and behind the patterns we did keep.
Download the full PDF for free?
Free download — no account required