Interactive reference
The Periodic Table of Software Architecture
October 12, 2026 · architecture, patternsAlёna Batitskaia's Periodic Table of the HTML Elements made a sprawling spec feel small enough to hold in your head. Software architecture could use the same treatment. Its vocabulary is spread across books, conference talks, and cloud vendor docs, and the same idea often goes by three different names.
This table collects 92 of the principles, patterns, and practices I reach for most, grouped into families. Every element has a short entry: what it is, when it earns its place, and what it will cost you. Hover to preview, click to read, and use the compounds further down to see how elements combine.
The table
How to read an element
Hover over any element
- Nº
- Reading order, left to right and top to bottom.
- Bars
- Complexity cost: how much it adds to a system, from one to five.
- Colour
- Its family. Use the legend above to filter.
Click to open · Arrow keys to move
Click for the full entry
Principles
The forces every other element is built from.
Architectural Styles
The overall shape of a system.
Domain & Modularity
Where to draw boundaries, and what to call things.
Data
How state is stored, shaped, and moved.
Integration
How the parts of a system talk to each other.
Resilience & Scale
Staying up, and staying fast, under stress.
Observability
Knowing what the system is actually doing.
Security
Keeping the wrong people and code out.
Delivery & Evolution
Getting change into production safely.
Practices
How architecture work itself gets done.
Reading the table
The layout borrows from chemistry on purpose. Columns are families. Principles sit on the far left, the way the most reactive elements do: they combine with everything. Architectural styles, domain modelling, and data fill the middle. Integration, resilience, observability, and security line the right edge, and security takes the noble-gas column because a well-secured system should be about as reactive as helium.
Rows get heavier as you go down. Within most families, the top elements are cheap to adopt and the bottom ones carry real weight. A monolith sits above microservices, read replicas above sharding, a timeout above graceful degradation. The bars on each tile make that explicit, but treat them as a rough guide: cost depends on your team as much as on the pattern.
The two detached rows are series that act on everything else, like the lanthanides and actinides at the bottom of a real periodic table. Delivery practices decide how change reaches production. Architecture practices decide how decisions get made in the first place. The dashed tiles are the ones made of people and conversations rather than code.
Common compounds
Elements rarely appear alone. These are combinations that show up again and again, and they are stable because each element covers a weakness of another. Select one to light it up on the table.
All elements
Every entry, grouped by family, if you'd rather read than click.
Principles
The forces every other element is built from.
Separation of ConcernsEach part of a system should address one distinct concern.
Split a system so that each part deals with a single, identifiable aspect: presentation, business rules, persistence, transport. When concerns are separated, you can understand, change, and replace one without reasoning about all the others. Almost every other element on this table is a specific way of applying it.
- Reach for it when
- Always. The question is never whether to separate concerns, but which ones and along which lines.
- Watch out for
- Splitting along the wrong axis. Technical layers separate code that always changes together when change actually happens per feature.
Complexity cost: Minimal · Related
AbstractionExpose what something does, and hide how it does it.
A good abstraction gives callers a simpler model than the thing underneath: a repository instead of SQL, a payment port instead of a vendor SDK. Both sides can evolve independently as long as the contract holds.
- Reach for it when
- A detail is likely to change, has more than one implementation, or would otherwise leak complexity into many callers.
- Watch out for
- Abstractions built before you have two real cases tend to guess wrong. A leaky or premature abstraction costs more than the duplication it removed.
Complexity cost: Minimal · Related
Single ResponsibilityA module should have one reason to change: it answers to one actor.
Robert C. Martin's framing is about people, not functions. When two groups of stakeholders drive changes into the same code, their changes collide. At architectural scale the same rule tells you how to split services and teams.
- Reach for it when
- Deciding where code belongs, or working out why a module keeps changing for unrelated reasons.
- Watch out for
- Reading it as "do one tiny thing" produces fragmented code that has to be read across dozens of files to follow a single flow.
Complexity cost: Minimal · Related
EncapsulationKeep state and the rules that protect it together, behind a boundary.
Callers interact through a defined interface and cannot reach in to change internals directly, so invariants are enforced in one place. At system scale it means a service owns its data and nobody else writes to its tables.
- Reach for it when
- Any state has rules about which values are valid or which transitions are allowed.
- Watch out for
- A getter and setter for every field is not encapsulation. It is a public struct with extra steps.
Complexity cost: Minimal · Related
Dependency InversionMake details depend on policies, not the other way round.
High-level business rules define the interfaces they need, and infrastructure such as databases, queues, and HTTP clients implements them. Source dependencies then point inward toward the core, so the most important code does not change when a vendor or framework does.
- Reach for it when
- You want to test business logic without infrastructure, or you expect the infrastructure to change.
- Watch out for
- Wrapping every class in an interface "for DI" adds indirection without inverting anything. Invert at the boundaries that matter.
Complexity cost: Low · Related
High CohesionThings that change together belong together.
A cohesive module has parts that are closely related in purpose, so a typical change touches one place. Cohesion is the other half of coupling: maximise relatedness inside a boundary and minimise dependencies across it.
- Reach for it when
- Deciding module, service, or team boundaries. Look at what actually changes together in your version history.
- Watch out for
- Grouping by technical type, such as all controllers or all repositories, looks tidy but scatters every feature across the codebase.
Complexity cost: Minimal · Related
Composition over InheritanceBuild behaviour by combining small parts rather than extending hierarchies.
Inheritance couples a child to every detail of its parent and fixes the relationship at compile time. Composition assembles behaviour from independent pieces that can be swapped, tested, and reused. The idea scales up: systems assembled from services and plug-ins rather than extended frameworks.
- Reach for it when
- You are tempted to add a base class to share code, or a hierarchy keeps growing sideways.
- Watch out for
- Composition still needs a coherent design. A soup of tiny collaborators wired together at random is no easier to follow than a deep hierarchy.
Complexity cost: Minimal · Related
Loose CouplingParts should know as little about each other as possible.
Coupling is the degree to which a change in one place forces a change somewhere else. It comes in many forms: shared code, shared data, synchronous calls, shared deployments, shared assumptions about timing. Architecture is largely the practice of choosing which coupling to accept.
- Reach for it when
- Every boundary decision. Ask what has to change on the other side when this side changes.
- Watch out for
- Decoupling is never free. Every indirection costs latency, operational effort, or understanding, so couple deliberately where it is cheap.
Complexity cost: Minimal · Related
You Aren't Gonna Need ItDo not build for requirements you do not have yet.
Speculative flexibility costs something today, in code, complexity, and attention, for a benefit that may never arrive. When the real requirement shows up it usually looks different from the guess. Build the simplest thing that meets today's need and keep it easy to change.
- Reach for it when
- You hear "we might need" or "just in case" in a design discussion.
- Watch out for
- YAGNI is not a reason to ignore known requirements or one-way-door decisions. Some things, like security and partitioning keys, are far cheaper to design in early.
Complexity cost: Minimal · Related
SimplicityChoose the simplest design that solves the actual problem.
Complexity is the dominant cost in software. Every moving part has to be understood, operated, and debugged by someone, eventually at three in the morning. A simple design is not a naive one; it is what remains after removing everything the problem does not demand. Often summarised as KISS: keep it simple.
- Reach for it when
- Comparing options. Ask which one has fewer concepts, fewer moving parts, and fewer ways to fail.
- Watch out for
- Simple is not the same as easy or familiar. The simple design often takes more thinking up front.
Complexity cost: Minimal · Related
ReversibilityPrefer decisions that are cheap to undo, and spend your care on the ones that are not.
Some decisions are two-way doors that are easy to walk back through. Others, like data models, public APIs, and partition keys, are one-way doors. Much of architecture is telling the two apart: move fast on the first kind and slow down, or defer, on the second.
- Reach for it when
- Planning any significant decision. Ask what it would cost to reverse a year from now.
- Watch out for
- Treating every decision as irreversible paralyses a team. Treating irreversible ones casually creates years of migration work.
Complexity cost: Low · Related
Architectural Styles
The overall shape of a system.
MonolithOne deployable unit containing the whole application.
Everything runs in one process and ships together. Calls are in memory, transactions are local, and there is one thing to deploy and monitor. For most products, at most stages, this is the right place to start.
- Reach for it when
- Early products, small teams, and domains you do not yet understand well enough to split.
- Watch out for
- Without internal boundaries it decays into a big ball of mud. At that point the problem is the mud, not the monolith.
Complexity cost: Minimal · Related
Modular MonolithA single deployable with strongly enforced internal module boundaries.
The application is split into modules aligned with business capabilities, each with a public API, private internals, and often its own schema. You keep the operational simplicity of one deployment and get most of the design benefits of services. A module with clean boundaries can be extracted later if it ever needs to be.
- Reach for it when
- You want clear ownership and boundaries without paying the distributed-systems tax.
- Watch out for
- Boundaries that exist only as conventions erode. Enforce them with build rules, architecture tests, or separate assemblies.
Complexity cost: Low · Related
Layered ArchitectureOrganise code into horizontal layers, each depending only on the one below.
The classic stack of presentation, business logic, and data access. Each layer has a clear technical role, and the structure is familiar to almost every developer. It is easy to start with and easy to explain.
- Reach for it when
- CRUD-heavy applications where most changes really are technical rather than driven by the domain.
- Watch out for
- Features cut through every layer, so small changes touch many files, and business logic leaks into whichever layer is convenient.
Complexity cost: Minimal · Related
Hexagonal ArchitectureThe application core talks to the outside world only through ports, with adapters at the edges.
Alistair Cockburn's Ports and Adapters pattern puts business logic in the centre and defines ports for everything it needs or offers. Adapters for HTTP, databases, queues, and tests plug into those ports, so the core can run and be tested without any infrastructure at all.
- Reach for it when
- The business logic is valuable enough to protect from framework and infrastructure churn, and you want fast, isolated tests.
- Watch out for
- Ceremony. A thin CRUD service does not need a port for every table.
Complexity cost: Low · Related
Clean ArchitectureConcentric layers with business rules at the centre and dependencies pointing inward.
A family that includes Onion and Clean Architecture: entities and use cases in the middle, interface adapters around them, frameworks and drivers on the outside. Like Hexagonal, it keeps the core independent of delivery mechanisms and storage.
- Reach for it when
- Long-lived systems whose business rules will outlast their frameworks.
- Watch out for
- Rigid folder templates and mapping between near-identical models at every ring. Apply the dependency rule, not the template.
Complexity cost: Low · Related
MicrokernelA minimal core system extended by independent plug-ins.
The core provides the essential runtime and a set of extension points, and features arrive as plug-ins that register against them. IDEs, browsers, and many product platforms work this way. It suits products where customers or third parties add capability without changing the core.
- Reach for it when
- Per-customer customisation or third-party extension is a core requirement.
- Watch out for
- The plug-in contract is a public API you will support for years. Version it carefully.
Complexity cost: Moderate · Related
Pipes and FiltersProcess data through a chain of independent steps, each transforming its input.
Each filter performs one transformation and passes its output down a pipe to the next. Filters can be reordered, reused, and scaled independently. Unix pipelines, ETL jobs, compilers, and stream processors all have this shape.
- Reach for it when
- The work is naturally a sequence of transformations over a stream or batch of data.
- Watch out for
- A poor fit for interactive workflows that branch or call back. Error handling across stages needs deliberate design.
Complexity cost: Low · Related
Service-Based ArchitectureA handful of coarse-grained services, often sharing a database.
The pragmatic middle ground between a monolith and microservices: typically four to twelve domain services, deployed separately, frequently against one shared database. You get deployment independence and fault isolation for a fraction of the operational cost of fine-grained services. Classic SOA, with its enterprise service bus, is the more centralised ancestor.
- Reach for it when
- You need some independent deployability but not the full microservices operating model.
- Watch out for
- The shared database couples services more tightly than their separate deployments suggest. Schema changes still need coordination.
Complexity cost: Moderate · Related
MicroservicesSmall, independently deployable services, each owning a business capability and its data.
Each service is built, deployed, and scaled on its own by a team that owns it end to end. The payoff is organisational: many teams can ship without coordinating releases. The price is that you now run a distributed system, with network failures, eventual consistency, and a serious operations burden.
- Reach for it when
- Many teams need to ship independently, and you can afford the platform and operational maturity it takes.
- Watch out for
- The distributed monolith: services that must be deployed together because they share data or call each other synchronously everywhere. Also, starting here before you understand the domain.
Complexity cost: Very high · Related
Event-Driven ArchitectureComponents communicate by producing and reacting to events instead of calling each other.
When something happens, a component publishes an event, and anything interested reacts. Producers and consumers are decoupled in time and in knowledge, which makes the system easy to extend and tolerant of individual consumer failures.
- Reach for it when
- Many parts of the system react to the same business facts, or you need strong decoupling and high throughput.
- Watch out for
- The flow is no longer visible in the code; it emerges at runtime. Debugging, ordering, and eventual consistency all get harder.
Complexity cost: High · Related
ServerlessRun code as managed, event-triggered functions without managing servers.
The platform handles provisioning, scaling, and patching, and you pay per invocation. Functions are triggered by HTTP requests, queue messages, file uploads, or schedules. Operational overhead drops sharply for spiky or low-volume workloads.
- Reach for it when
- Event-driven glue, spiky traffic, and teams that want as little operations work as possible.
- Watch out for
- Cold starts, execution limits, vendor lock-in, harder local testing, and bills that overtake containers at steady high volume.
Complexity cost: Moderate · Related
Cell-Based ArchitecturePartition the whole system into isolated, identical cells, each serving a subset of customers.
Each cell is a complete, independent copy of the stack, and a thin routing layer assigns customers to cells. A bad deployment or failure only affects the customers in one cell, which caps the blast radius, and capacity grows by adding cells.
- Reach for it when
- Large multi-tenant systems where blast radius and predictable scaling matter more than cost.
- Watch out for
- A significant operational investment: cell routing, moving customers between cells, and features that need data from more than one cell.
Complexity cost: Very high · Related
Domain & Modularity
Where to draw boundaries, and what to call things.
Ubiquitous LanguageOne shared vocabulary between domain experts and the code, used everywhere.
Terms from the business appear unchanged in conversations, documentation, and code: class names, method names, events. When the language is shared, misunderstandings surface early and the model in the code stays recognisable to the people who own the domain.
- Reach for it when
- Always, within a bounded context. It pays off most where the domain is complex or terms are easily confused.
- Watch out for
- A word can legitimately mean different things in different parts of the business. Do not force one global definition; that is what bounded contexts are for.
Complexity cost: Minimal · Related
Bounded ContextAn explicit boundary within which one model and its language are consistent.
Large domains cannot share a single model. "Customer" means something different to sales, billing, and support. A bounded context draws a line around one model, and translation happens explicitly at that line. Contexts are the strongest candidates for module, service, and team boundaries.
- Reach for it when
- The domain is big enough that terms start to mean different things to different groups.
- Watch out for
- Drawing boundaries around nouns or database tables instead of around language and behaviour.
Complexity cost: Low · Related
AggregateA cluster of objects changed as one unit, with a single root that guards its invariants.
An aggregate defines a consistency boundary. Everything inside it changes in one transaction through the root; anything outside is referenced by identity and updated eventually. Small aggregates keep contention low and make the rules explicit.
- Reach for it when
- You need to enforce business invariants that span several related objects.
- Watch out for
- Large aggregates cause lock contention and slow loads. If two things do not need to be consistent immediately, they probably belong in different aggregates.
Complexity cost: Low · Related
Context MapA map of the bounded contexts and the relationships between them.
A context map shows which contexts exist, who depends on whom, and what kind of relationship applies: partnership, customer–supplier, conformist, anti-corruption layer, shared kernel. It makes political and technical dependencies visible on the same page.
- Reach for it when
- Several teams or systems interact, and integration pain keeps catching people by surprise.
- Watch out for
- A map drawn once and never updated. It is only useful if it reflects reality.
Complexity cost: Low · Related
Anti-Corruption LayerA translation layer that keeps a foreign model from leaking into yours.
When integrating with a legacy system or a third-party API, the ACL translates their concepts into your model and back. Your domain stays clean, and when the external system changes, only the translation layer has to follow.
- Reach for it when
- Integrating with a system whose model you do not control, or do not want to adopt.
- Watch out for
- It is real code that needs real maintenance, and it becomes a bottleneck if every change on either side requires work there.
Complexity cost: Low · Related
Domain EventsRecord that something meaningful happened in the domain, named in the past tense.
OrderPlaced, PaymentFailed, ShipmentDispatched. Domain events capture business facts in the ubiquitous language and let other parts of the system react without the originator knowing about them. They are the building blocks of event-driven integration and event sourcing.
- Reach for it when
- Other parts of the system care that something happened, or the history of changes has business value.
- Watch out for
- Commands in disguise, such as SendEmail, and events that expose internal state. Both couple consumers tightly to the producer.
Complexity cost: Low · Related
Core DomainFind the part of the business that sets you apart, and invest there.
Strategic domain-driven design splits the problem into core, supporting, and generic subdomains. The core gets your best people and carefully built models. Supporting subdomains get simpler solutions, and generic ones, such as payments, email, or identity, are bought or outsourced.
- Reach for it when
- Deciding where to spend design effort, and what to build versus buy.
- Watch out for
- Everyone believes their area is core. Be honest: if a competitor could buy the same thing off the shelf, it is not.
Complexity cost: Low · Related
Open Host ServiceOffer a documented, stable protocol that any consumer can integrate with.
Instead of building a custom integration for every consumer, a context publishes a general-purpose API, usually paired with a published language such as a documented schema. Consumers adapt to it, and the provider evolves it through versioning.
- Reach for it when
- Many consumers need to integrate with the same context.
- Watch out for
- A public protocol is hard to change. Design and version it as a product.
Complexity cost: Moderate · Related
Vertical SlicesOrganise code by feature, each slice owning everything from request to storage.
Each use case lives in one place with its own handler, data access, and model, instead of being spread across shared layers. Slices can use whatever approach suits them, and adding a feature means adding code rather than changing shared abstractions.
- Reach for it when
- Feature-heavy applications where most changes happen per use case.
- Watch out for
- Duplication between slices. Extract shared domain logic deliberately, once it has actually emerged.
Complexity cost: Low · Related
Data
How state is stored, shaped, and moved.
Database per ServiceEach service owns its data store, and nobody else touches it directly.
Private data is what makes services independently deployable. Schemas can change without coordinating with other teams, and each service can pick storage that fits its access patterns. Everyone else gets the data through APIs or events.
- Reach for it when
- Services are owned by different teams and need to evolve independently.
- Watch out for
- Queries and transactions that span services become distributed problems. Joins turn into API calls, and consistency becomes eventual.
Complexity cost: Moderate · Related
Cache-AsideRead from the cache first; on a miss, load from the source and fill the cache.
The application manages the cache explicitly. Hot data is served from memory, which takes load off the database and cuts latency. It is the most common caching pattern because it is simple and fails safe: if the cache is down, the source still works.
- Reach for it when
- Read-heavy data that can tolerate being slightly stale.
- Watch out for
- Invalidation. Decide up front how stale is acceptable and how entries expire, and watch for stampedes when a hot key expires.
Complexity cost: Low · Related
Read ReplicasCopy data to secondary nodes that serve reads.
The primary handles writes and replicates them to one or more replicas, which absorb read traffic and act as failover targets. It is the first step most systems take when one database can no longer keep up with reads.
- Reach for it when
- Reads far outnumber writes and slightly delayed reads are acceptable.
- Watch out for
- Replication lag. A user who writes and then immediately reads from a replica may not see their own change.
Complexity cost: Low · Related
Command Query Responsibility SegregationUse separate models for changing data and for reading it.
Commands go through a model built to enforce rules. Queries read from models built for display, often denormalised and sometimes in a different store. Each side can be shaped and scaled for its own job.
- Reach for it when
- Read and write needs differ sharply in shape or volume, or a rich domain model makes queries awkward.
- Watch out for
- Two models to maintain and, when the stores are separate, eventual consistency between them. Overkill for plain CRUD.
Complexity cost: Moderate · Related
Materialized ViewPrecompute query results into a read-optimised form.
Instead of joining and aggregating on every read, build the answer ahead of time and update it as the source data changes. Views can live in the same database or be projected into a search index, cache, or document store.
- Reach for it when
- Expensive queries run far more often than the underlying data changes.
- Watch out for
- Views go stale and have to be rebuildable from the source. Plan for rebuilds from day one.
Complexity cost: Low · Related
ShardingSplit data horizontally across multiple databases by a partition key.
Each shard holds a subset of the rows, for example by tenant or customer ID range, so write load and storage spread across machines. It is how systems scale writes beyond what one database can handle.
- Reach for it when
- A single database can no longer handle the write volume or data size, and the data has a natural partition key.
- Watch out for
- Cross-shard queries and transactions get hard. A bad shard key creates hot spots, and resharding later is painful.
Complexity cost: High · Related
Event SourcingStore state as the sequence of events that produced it.
Instead of overwriting rows, append every change as an immutable event and derive current state by replaying them. You get a complete audit trail, the ability to reconstruct state at any point in time, and the option to build new read models from old history.
- Reach for it when
- Audit and history are core requirements, or the domain is naturally a sequence of events, as in finance, logistics, and workflow.
- Watch out for
- Event schemas are forever, so versioning is unavoidable, and every query needs a projection. The learning curve is steep, and it is very hard to remove once adopted.
Complexity cost: Very high · Related
Change Data CaptureStream row-level changes out of a database's transaction log.
Tools such as Debezium read the database's write-ahead log and publish every insert, update, and delete as an event. Other systems can react to changes without the source application knowing, which makes CDC useful for migrations, cache invalidation, and feeding analytics.
- Reach for it when
- You need changes from a database you cannot or do not want to modify, or a reliable relay for an outbox.
- Watch out for
- Raw table changes expose your internal schema to consumers. Publish curated events where you can.
Complexity cost: Moderate · Related
Data MeshTreat analytical data as a product owned by domain teams, on a self-serve platform.
Zhamak Dehghani's answer to the central data lake bottleneck. Domain teams publish their analytical data as discoverable, documented products. A platform team provides the tooling, and federated governance sets the shared standards.
- Reach for it when
- A large organisation where a central data team has become the bottleneck for analytics.
- Watch out for
- It is an organisational change before it is a technical one. Without mature domain teams and real platform investment, you get fragmentation, not a mesh.
Complexity cost: Very high · Related
Integration
How the parts of a system talk to each other.
Request–ReplyA caller sends a request and waits for the response.
The default interaction style: HTTP, gRPC, a function call over the network. It is easy to reason about because the caller gets an answer or an error straight away. The cost is temporal coupling. Both sides must be up at the same time, and latency adds up along every hop of a call chain.
- Reach for it when
- The caller needs the answer to continue, as with reads and user-facing queries.
- Watch out for
- Long synchronous chains between services multiply latency and failure probability. Three services at 99.9% availability give you 99.7% at best.
Complexity cost: Minimal · Related
API GatewayA single entry point that routes external requests to internal services.
The gateway handles edge concerns once: routing, authentication, TLS termination, rate limiting, request shaping. Clients see one stable API while the services behind it can be split, merged, or moved.
- Reach for it when
- Several backend services are exposed to external clients and you want edge concerns handled consistently.
- Watch out for
- Business logic creeping into the gateway turns it into a shared bottleneck that every team has to coordinate on.
Complexity cost: Low · Related
Message QueueProducers put work on a queue; consumers take it off when they are ready.
A queue decouples sender and receiver in time. The producer does not need the consumer to be up, and the consumer works at its own pace. Each message is handled by one consumer, which makes queues a natural fit for distributing work.
- Reach for it when
- Work can happen later, must survive a consumer outage, or arrives in bursts that need smoothing.
- Watch out for
- You now own delivery semantics: duplicates, poison messages, ordering, and dead letters. A queue hides a growing backlog until it suddenly doesn't.
Complexity cost: Low · Related
Backends for FrontendsA dedicated backend API for each kind of client.
Instead of one general-purpose API that tries to suit web, mobile, and partners, each client gets a thin backend shaped to its needs and owned by the team that builds the client. It aggregates and adapts calls to the services behind it.
- Reach for it when
- Clients have meaningfully different data needs, release cycles, or teams.
- Watch out for
- Logic duplicated across BFFs. Keep them thin: composition and shaping only, no core business rules.
Complexity cost: Low · Related
Publish–SubscribePublishers broadcast messages to a topic, and every subscriber gets a copy.
Publishers do not know who is listening, so new consumers can be added without changing the producer. That makes pub/sub the backbone of event-driven systems. Each subscriber processes independently, at its own pace.
- Reach for it when
- Several parts of the system need to react to the same thing happening.
- Watch out for
- Invisible dependencies. When anyone can subscribe, nobody knows what breaks when an event's shape changes. Treat events as public contracts.
Complexity cost: Low · Related
Idempotent ConsumerProcessing the same message twice has the same effect as processing it once.
Most messaging systems guarantee at-least-once delivery, so duplicates will happen. An idempotent consumer detects repeats, usually by recording processed message IDs, or makes operations naturally idempotent, for example by setting a value instead of incrementing it.
- Reach for it when
- Consuming from any queue or stream, and in any API that clients may retry.
- Watch out for
- The deduplication record must be written atomically with the side effect. Otherwise you have only moved the problem.
Complexity cost: Low · Related
Event StreamingAn append-only, replayable log of events that each consumer reads at its own position.
Unlike a queue, a stream such as Kafka keeps events after they have been read. Consumers track their own offsets, new consumers can replay history, and ordering is preserved within a partition. A stream works both as an integration backbone and as a source for derived data.
- Reach for it when
- Several consumers need the same events, replay matters, or you are building real-time pipelines.
- Watch out for
- Operational weight, partition design, and schema evolution. A stream retains your mistakes as faithfully as your data.
Complexity cost: Moderate · Related
Transactional OutboxWrite the message to an outbox table in the same transaction as the state change, then publish it.
A database and a message broker cannot share a transaction. The outbox pattern writes the state change and the outgoing message to the database atomically, and a separate relay publishes outbox rows to the broker. You get reliable at-least-once publishing without distributed transactions.
- Reach for it when
- A state change must reliably produce an event or message.
- Watch out for
- Consumers will see duplicates, so pair it with idempotent consumers. The relay is one more process to run and monitor.
Complexity cost: Moderate · Related
SagaRun a multi-step business transaction as local transactions with compensating actions.
When a business process spans several services, there is no single ACID transaction to lean on. A saga runs each step locally and, when a step fails, runs compensating actions to undo the earlier ones. Steps are coordinated by choreography, where services react to each other's events, or by orchestration, where a coordinator directs each step.
- Reach for it when
- A business process has to keep data consistent across services that each own their data.
- Watch out for
- Compensations are business logic, not rollbacks, and some actions, like sending an email, cannot be undone. Other processes can see the intermediate states.
Complexity cost: High · Related
Process OrchestrationA central coordinator drives a long-running workflow, step by step.
The orchestrator holds the process state, calls each participant, handles timeouts and retries, and decides what happens next. Durable workflow engines such as Temporal and Camunda persist that state, so a process survives crashes and can run for days.
- Reach for it when
- Complex, long-running processes where seeing and controlling the flow in one place matters.
- Watch out for
- The orchestrator slowly absorbs logic that belongs in the services. Workflow engines are significant infrastructure in their own right.
Complexity cost: High · Related
Resilience & Scale
Staying up, and staying fast, under stress.
TimeoutNever wait forever. Every remote call needs an upper bound.
Without a timeout, one slow dependency holds threads, connections, and memory until the caller falls over too. A timeout turns an unknown wait into a known failure that the rest of the system can handle. Retries, circuit breakers, and fallbacks all depend on it.
- Reach for it when
- Every network call, lock acquisition, and queue read.
- Watch out for
- Timeouts need a budget across the whole call chain. An outer timeout shorter than the sum of the inner ones guarantees wasted work.
Complexity cost: Minimal · Related
Load BalancingSpread incoming work across multiple instances.
A load balancer distributes requests so no single instance is overwhelmed, and takes failed instances out of rotation. It is what makes horizontal scaling and zero-downtime deployments possible. Strategies range from round robin to least-connections and consistent hashing.
- Reach for it when
- More than one instance serves the same workload, for capacity or for availability.
- Watch out for
- Sticky sessions and local state undermine it. Instances behind a balancer should be stateless or keep their state elsewhere.
Complexity cost: Low · Related
Retry with BackoffTry again after a transient failure, waiting longer each time.
Many failures are momentary: a dropped connection, a leader election, a brief overload. Retrying with exponential backoff and random jitter recovers from them without hammering a dependency that is already struggling.
- Reach for it when
- The failure is plausibly transient and the operation is idempotent.
- Watch out for
- Retries at every layer multiply. Three layers with three retries each turn one failure into 27 calls, and a retry storm can knock over a service that was recovering.
Complexity cost: Low · Related
Rate LimitingCap how many requests a client can make in a given period.
Rate limits protect shared capacity from runaway clients, abuse, and accidental loops. Token bucket and sliding window are the usual algorithms, applied per client, per key, or per tenant, typically at the edge.
- Reach for it when
- A resource is shared between clients who should not be able to starve each other.
- Watch out for
- Limits that clients cannot see. Return clear signals such as a Retry-After header, or clients will retry immediately and make things worse.
Complexity cost: Low · Related
Circuit BreakerStop calling a failing dependency for a while, then check whether it has recovered.
Popularised by Michael Nygard in Release It! After enough failures the breaker opens, and calls fail fast without touching the dependency. That gives the dependency room to recover and frees the caller's resources. After a cool-down, a trial request goes through, and success closes the circuit again.
- Reach for it when
- Calling remote dependencies that can fail or slow down, especially ones shared by many callers.
- Watch out for
- Badly tuned thresholds either trip on normal noise or never trip at all. Pair it with a fallback so an open circuit still does something useful.
Complexity cost: Low · Related
AutoscalingAdd or remove capacity automatically, based on demand.
Instances, containers, or functions scale out when load rises and back in when it falls, driven by signals such as CPU, queue depth, or request rate. You pay for what you use and ride out spikes without anyone getting paged.
- Reach for it when
- Load is variable and the workload is stateless or can be partitioned.
- Watch out for
- Scaling is not instant. Cold starts and provisioning lag mean you still need headroom, and downstream dependencies like databases may not scale with you.
Complexity cost: Moderate · Related
BulkheadIsolate resources so a failure in one area cannot sink the whole system.
Named after the watertight compartments in a ship's hull. Give each dependency or tenant its own thread pool, connection pool, or set of instances, so exhaustion in one place stays contained.
- Reach for it when
- One slow dependency or noisy tenant could otherwise consume resources everyone shares.
- Watch out for
- Partitioning lowers overall utilisation. Size the compartments from real traffic data.
Complexity cost: Moderate · Related
BackpressureLet a slow consumer tell producers to slow down.
When a downstream stage cannot keep up, unbounded buffering eventually exhausts memory. Backpressure pushes the constraint upstream through bounded queues, pull-based consumption, or explicit flow control, so the system degrades gracefully instead of collapsing.
- Reach for it when
- Producers can outpace consumers, as in streaming pipelines and high-throughput services.
- Watch out for
- Somewhere at the edge you still have to decide what happens when the system is full: wait, shed, or reject.
Complexity cost: Moderate · Related
Graceful DegradationWhen part of the system fails, keep serving a reduced but useful experience.
Show cached recommendations when the recommendation service is down. Accept the order now and take payment later. Hide a non-essential widget instead of failing the page. Decide which features are essential, and what the fallback is for everything else.
- Reach for it when
- Some functionality is critical and some is nice to have, which describes nearly every user-facing product.
- Watch out for
- Fallback paths rarely run, so they rot. Exercise them on purpose, for example with chaos experiments.
Complexity cost: Moderate · Related
Load SheddingUnder overload, deliberately reject some work to protect the rest.
A system at capacity that accepts everything ends up serving nothing well. Load shedding drops low-priority or excess requests early, while it is still cheap, so the remaining requests succeed and latency stays bounded for the work that matters most.
- Reach for it when
- Overload is possible and some requests matter more than others.
- Watch out for
- Shedding needs priorities. Without them you may drop the checkout and keep the analytics beacon.
Complexity cost: Moderate · Related
Observability
Knowing what the system is actually doing.
Structured LoggingEmit logs as machine-readable events with consistent fields.
Write log entries as key–value data rather than free text, with a timestamp, severity, correlation ID, and the context that explains the event. Structured logs can be queried and aggregated, not just grepped.
- Reach for it when
- From the first line of code. Adding structure later is expensive.
- Watch out for
- Logging everything is expensive and buries the signal. Never log secrets or personal data.
Complexity cost: Minimal · Related
MetricsNumeric time series that show how the system behaves over time.
Counters, gauges, and histograms: request rate, error rate, latency percentiles, saturation. Metrics are cheap to store and fast to query, which makes them the backbone of dashboards and alerting.
- Reach for it when
- You need to know whether something is wrong right now, or how a value trends over weeks.
- Watch out for
- High-cardinality labels such as user IDs explode storage costs. Averages hide the tail, so use percentiles.
Complexity cost: Minimal · Related
Health ChecksEndpoints that report whether an instance is alive and ready for traffic.
Orchestrators and load balancers poll health endpoints to decide when to restart an instance and when to send it traffic. Separating liveness ("restart me") from readiness ("don't send me work yet") prevents a whole class of self-inflicted outages.
- Reach for it when
- Anything running behind a load balancer or an orchestrator.
- Watch out for
- Readiness checks that call a shared dependency can pull the entire fleet out of rotation the moment that dependency blips.
Complexity cost: Minimal · Related
Distributed TracingFollow a single request as it travels through every service it touches.
A trace ID is propagated across process boundaries, and each service records spans with timing and metadata. The assembled trace shows where the time went and where the errors happened. OpenTelemetry has made this vendor-neutral.
- Reach for it when
- Requests cross more than a couple of services, and "which one is slow?" has become hard to answer.
- Watch out for
- Context propagation breaks silently across async boundaries and third-party hops, and sampling decisions limit what you can debug later.
Complexity cost: Low · Related
SLOs & Error BudgetsSet explicit reliability targets, and use the margin to decide when to ship and when to stabilise.
A service level objective defines "good enough", for example 99.9% of requests answered in under 300 ms. The remaining 0.1% is the error budget. While budget remains, teams ship freely; once it is spent, reliability work comes first. Reliability stops being an argument and becomes a number.
- Reach for it when
- Teams argue about whether reliability or features come first, or alerting is noisy and ignored.
- Watch out for
- Objectives must reflect what users actually experience. Too many SLOs, or targets nobody acts on, add ceremony without value.
Complexity cost: Moderate · Related
Security
Keeping the wrong people and code out.
Least PrivilegeEvery user, service, and process gets the minimum access it needs, and nothing more.
Grant permissions narrowly and for as short a time as practical. A compromised component can then only do the damage its own permissions allow. It applies equally to database accounts, cloud IAM roles, API tokens, and humans with production access.
- Reach for it when
- Designing any identity, role, or credential, especially service accounts that tend to collect broad permissions over time.
- Watch out for
- Permissions drift upward as people add access to fix urgent problems and never remove it. Review and expire grants on a schedule.
Complexity cost: Low · Related
Defense in DepthLayer independent controls so no single failure exposes the system.
Assume any one control will eventually fail: the firewall gets misconfigured, a dependency ships a CVE, a password leaks. Stack network segmentation, authentication, authorization, input validation, encryption, and monitoring so an attacker has to get past several of them.
- Reach for it when
- Protecting anything valuable. It is the baseline posture for production systems.
- Watch out for
- Layers that share the same weakness, such as one credential or one vendor, are not independent.
Complexity cost: Low · Related
Secrets ManagementStore credentials centrally, hand them out at runtime, and rotate them often.
Keys, passwords, and certificates live in a dedicated vault, not in source control, container images, or a .env file on someone's laptop. Applications fetch them at runtime using their own identity, and every access is audited. Short-lived, automatically rotated credentials limit the damage of a leak.
- Reach for it when
- Any system that authenticates to anything.
- Watch out for
- The vault becomes a critical dependency. Plan for its availability, and for how a service gets its very first credential.
Complexity cost: Low · Related
Federated IdentityDelegate authentication to a trusted identity provider.
Users and services authenticate once against an identity provider using standards such as OpenID Connect, OAuth 2.0, or SAML, and applications trust the resulting tokens. Applications stop storing passwords, and policies like MFA are enforced in one place.
- Reach for it when
- More than one application needs to know who the user is.
- Watch out for
- The identity provider is now critical infrastructure. Validate tokens properly: issuer, audience, expiry, and signature.
Complexity cost: Low · Related
Zero TrustNever trust a request because of where it comes from. Verify every one.
The network perimeter no longer defines what is safe. Every request, internal or external, is authenticated, authorised, and encrypted based on identity and context. Service-to-service traffic uses mutual TLS or signed tokens.
- Reach for it when
- Your systems span clouds, offices, and remote work, which today describes most systems.
- Watch out for
- It is an end state you reach incrementally, not a product you buy. Start by giving every workload an identity.
Complexity cost: Moderate · Related
Threat ModelingSystematically ask what can go wrong from an attacker's point of view, before you build.
Map the system, its data flows, and its trust boundaries, then walk through likely threats with a framework such as STRIDE. The output is a prioritised list of mitigations that get designed in rather than bolted on.
- Reach for it when
- Designing a new system, adding a trust boundary, or starting to handle sensitive data.
- Watch out for
- Doing it once, as a compliance document. Revisit the model whenever the architecture changes.
Complexity cost: Moderate · Related
Delivery & Evolution
Getting change into production safely.
Continuous DeliveryKeep software releasable at all times through an automated pipeline.
Every change goes through the same automated build, test, and deployment pipeline, so releasing becomes a routine business decision rather than a risky event. Small, frequent changes are easier to verify, debug, and roll back than large ones.
- Reach for it when
- Always aim for it. Deployment frequency and lead time are among the strongest predictors of delivery performance.
- Watch out for
- A pipeline is only as trustworthy as its tests. Slow or flaky pipelines push people back toward big batches.
Complexity cost: Moderate · Related
Infrastructure as CodeDefine infrastructure in version-controlled, reviewable code.
Networks, servers, databases, and permissions are declared with tools like Terraform, Pulumi, or Bicep and applied automatically. Environments become reproducible, changes get reviewed like application code, and drift becomes visible.
- Reach for it when
- Any infrastructure you would need to recreate, audit, or replicate across environments.
- Watch out for
- Manual changes in the cloud console bring drift right back. State files and secrets in IaC need careful handling.
Complexity cost: Low · Related
Immutable InfrastructureNever modify running servers. Replace them with new ones built from a known image.
Changes are made by building a new image or container and redeploying, never by patching in place. Every instance is identical and reproducible, and rolling back means redeploying the previous version.
- Reach for it when
- Deploying to cloud or container platforms, where it is close to the default.
- Watch out for
- State has to live outside the instance, and debugging by logging into a box gets harder, which pushes you toward better observability.
Complexity cost: Low · Related
SidecarDeploy a helper process next to each service to handle cross-cutting concerns.
The sidecar runs alongside the application, in the same pod or on the same host, and handles proxying, mTLS, telemetry, or configuration without changes to application code. Service meshes are built out of sidecars.
- Reach for it when
- Services written in different languages all need the same infrastructure capability.
- Watch out for
- Extra resources and latency for every instance, and one more component to upgrade across the fleet.
Complexity cost: Moderate · Related
Blue–Green DeploymentRun two production environments and switch traffic from old to new in one step.
Deploy the new version to the idle environment, verify it, then flip the router. If anything goes wrong, flip back. Rollback is instant because the old version is still running.
- Reach for it when
- You need near-zero-downtime releases and fast, low-risk rollback.
- Watch out for
- Database schema changes must work with both versions at once, and running two environments costs money.
Complexity cost: Moderate · Related
Canary ReleaseRoll a change out to a small slice of traffic first, and widen it only while the metrics stay healthy.
A small percentage of users, or one region, gets the new version while its metrics are compared against the current one. Problems surface with a limited blast radius, and the rollout can halt automatically on a regression.
- Reach for it when
- A change carries risk that only real production traffic will reveal.
- Watch out for
- You need good metrics and enough traffic for the comparison to mean anything, plus sticky routing so users get a consistent experience.
Complexity cost: Moderate · Related
Feature FlagsSeparate deploying code from releasing a feature.
New behaviour ships behind a runtime toggle that can be switched per user, segment, or percentage. Teams can merge unfinished work to trunk, test in production, and switch off a misbehaving feature without a deployment.
- Reach for it when
- You want release decisions to be independent of deployments, or you want to run experiments.
- Watch out for
- Every flag is a branch in your code. Remove flags once a feature is fully rolled out, or the combinations become untestable.
Complexity cost: Low · Related
Branch by AbstractionReplace a component incrementally behind an abstraction while still shipping from trunk.
Introduce an abstraction over the component you want to replace, move callers onto it, build the new implementation behind it, switch over, and delete the old one. The system stays releasable at every step, with no long-lived branch.
- Reach for it when
- Replacing a library, data store, or subsystem that much of the code depends on.
- Watch out for
- The temporary abstraction has to be removed at the end, or it becomes permanent indirection.
Complexity cost: Low · Related
Strangler FigReplace a legacy system gradually by routing its functionality to a new one, piece by piece.
Martin Fowler named it after the vines that slowly grow around a host tree. Put a facade in front of the legacy system, build new capabilities beside it, and redirect traffic route by route until the old system can be switched off. Value ships continuously instead of after a big-bang rewrite.
- Reach for it when
- Modernising a system too large or risky to rewrite in one go, which is nearly all of them.
- Watch out for
- Migrations stall at 80% and leave you running both systems indefinitely. Plan and fund the last mile.
Complexity cost: Moderate · Related
Platform EngineeringBuild an internal platform that gives product teams self-service paved roads.
A platform team offers pipelines, environments, observability, and infrastructure as a product with a self-service interface. Product teams get speed and consistency without each one having to become infrastructure experts.
- Reach for it when
- Many teams keep solving the same infrastructure problems, and cognitive load is slowing them down.
- Watch out for
- A platform built without treating teams as its customers becomes a mandatory obstacle. Start with the thinnest viable platform.
Complexity cost: High · Related
Practices
How architecture work itself gets done.
Architecture Decision RecordsRecord each significant decision with its context, options, and consequences.
An ADR is a short document, kept next to the code, that captures one decision: what was decided, why, what else was considered, and what it costs. Over time the log explains how the system got its shape, so newcomers don't reopen settled questions and teams know when a decision's context has changed.
- Reach for it when
- Any decision that is hard to reverse, or that someone will one day ask "why did we do it this way?" about.
- Watch out for
- Writing them afterwards as paperwork. The value is in the thinking the format forces while you decide.
Complexity cost: Minimal · Related
C4 ModelDiagram software at four zoom levels: context, containers, components, and code.
Simon Brown's approach gives architecture diagrams a consistent vocabulary and a clear audience for each level. Most teams get the bulk of the value from the first two: how the system fits into its environment, and which deployable units it is made of.
- Reach for it when
- Explaining a system to anyone, from executives at the context level to new developers at the container and component levels.
- Watch out for
- Diagrams that drift away from reality. Prefer diagrams as code, kept and reviewed alongside the system.
Complexity cost: Minimal · Related
Quality Attribute ScenariosTurn vague "-ilities" into concrete, testable statements.
"It must be scalable" means nothing. "When traffic doubles during a sale, p99 checkout latency stays under 500 ms" can be designed for and tested. Each scenario names a stimulus, an environment, and a measurable response, and together they set the architecture's priorities.
- Reach for it when
- Gathering requirements that shape the architecture, or when stakeholders disagree about what matters.
- Watch out for
- Trying to maximise every quality. They trade off against each other, so rank them.
Complexity cost: Low · Related
Trade-off AnalysisMake the costs of every option explicit. There are no best practices, only trade-offs.
Every architectural choice improves some qualities at the expense of others. Methods such as ATAM give this structure, but the core habit is simple: for each option, write down what you gain, what you give up, and which quality scenarios it serves.
- Reach for it when
- Every significant decision. If an option seems to have no downside, you have not found it yet.
- Watch out for
- Analysis paralysis. Time-box the analysis, and scale the effort to how hard the decision is to reverse.
Complexity cost: Low · Related
Fitness FunctionsAutomated checks that keep verifying architectural characteristics.
From Building Evolutionary Architectures: encode architectural intent as tests that run in the pipeline. Dependency rules between modules, latency budgets, bundle size limits, and security scans are typical examples. The architecture is then protected by the build, not just by review.
- Reach for it when
- You have architectural rules that matter and keep getting broken by accident.
- Watch out for
- Noisy or slow checks get switched off. Start with a few that guard what you care about most.
Complexity cost: Low · Related
Event StormingA collaborative workshop that maps a business process as a timeline of domain events.
Alberto Brandolini's format puts domain experts and engineers in front of a long wall with sticky notes. Events go up first, then commands, actors, policies, and hotspots. It quickly surfaces shared language, hidden complexity, and natural boundaries.
- Reach for it when
- Starting on a new domain, untangling an existing one, or looking for bounded contexts.
- Watch out for
- Without the real domain experts in the room, it turns into engineers guessing together.
Complexity cost: Low · Related
Team TopologiesDesign team structures and interaction modes on purpose, with cognitive load as the main constraint.
Matthew Skelton and Manuel Pais describe four team types (stream-aligned, platform, enabling, and complicated-subsystem) and three ways for teams to interact. Architecture and team design are treated as one problem, because each shapes the other.
- Reach for it when
- Team boundaries and system boundaries are misaligned, or teams are overloaded.
- Watch out for
- Renaming existing teams is not adopting it. The point is less cognitive load and fewer handoffs.
Complexity cost: Moderate · Related
Inverse Conway ManeuverShape the organisation so that it produces the architecture you want.
Conway's Law observes that systems mirror the communication structure of the organisations that build them. The inverse maneuver uses that on purpose: draw team boundaries to match the system boundaries you want, and the architecture follows.
- Reach for it when
- A target architecture keeps failing to materialise because the team structure pulls against it.
- Watch out for
- Reorganisations are expensive and disruptive. Understand the domain boundaries before redrawing team lines.
Complexity cost: High · Related
Evolutionary ArchitectureTreat guided, incremental change as a first-class design goal.
Accept that requirements and technology will change, and that the architecture has to change with them. Small increments, reversible decisions, fitness functions, and deliberate coupling let a system evolve without big rewrites.
- Reach for it when
- Long-lived systems in changing environments, which is most of them.
- Watch out for
- Evolvability is not unlimited flexibility. Decide along which dimensions the system must be able to change, and protect those.
Complexity cost: Moderate · Related
Chaos EngineeringRun controlled experiments that inject failure, to build confidence in resilience.
Form a hypothesis about steady state, inject a realistic fault such as a killed instance or added latency, and see whether the system holds. Pioneered at Netflix, it finds weaknesses on your schedule rather than during an outage.
- Reach for it when
- You have resilience mechanisms you have never seen work, and enough observability to measure the impact.
- Watch out for
- Start outside production with a small blast radius. Chaos without observability is just an outage.
Complexity cost: High · Related
No table like this is complete, and the placement is opinionated. You could argue that sharding belongs with resilience, or that hexagonal and clean architecture are one element. That argument is the point. Architecture is choosing which forces matter for your system, and a shared vocabulary makes the choosing easier to talk about.