← the whole book on one page

Chapter 1: Foundations of Modern Software Architecture

“All architecture is design but not all design is architecture.” — Grady Booch

Every discipline reaches a moment when it stops being a bag of techniques and becomes a way of seeing. Before that moment, its practitioners collect tools and argue about which one is best; after it, they look at a problem and see the forces pulling against one another inside it. For software architecture, that moment comes when we stop asking “which technology should I use?” and start asking “what am I trading away?” The first question can be answered from a product catalog. The second can be answered only by looking at the particular system in front of us, because every choice that buys one quality, such as speed, simplicity or resilience, pays for it with some other quality. Learning to ask that second question, and to answer it honestly, is what this book is about.

Encore, our companion for all 15 chapters, sells tickets to live concerts. It is a modest business with an immodest problem, and the problem has three parts. First, when a major artist announces a tour, Encore’s traffic rises 500× in minutes, so a system sized for an ordinary afternoon would collapse just when it matters most. Second, every seat must be sold exactly once, even when thousands of fans click on the same few rows in the same second; selling a seat twice means two people arrive at one chair, and selling it zero times means turning away a fan who wanted it. Third, the bots arrive before the fans do: automated scripts that can request seats far faster than any person can type, and that will grab the best seats if nothing stops them. Each part on its own is a familiar engineering problem. What makes Encore a good teacher is that it faces all three at once, in the same few minutes, so that every fix for one part puts pressure on the other two. That is exactly the kind of trade-off the rest of this chapter teaches us to see.


1.1 What Software Architecture Is Today

Why the building metaphor fails

For most of our field’s short history, we have explained software architecture by comparing it to the architecture of buildings. The story goes like this: the architect draws the plans, the builders follow them, and the finished structure stands for decades. It is a comforting story, because it promises that careful thought at the start will settle things for good. But it misleads us at exactly the point that matters most, which is what happens after the plans are drawn. A building does not have to double its occupancy overnight because an artist announced a tour, yet that is an ordinary Tuesday for Encore. Nor do a building’s residents tear out walls and add floors every week while still living inside it, yet that is how software teams work: the system stays in service, carrying real traffic, while we change it underneath.

Still, one lesson from buildings does carry over, and it is a good one. It comes from Stewart Brand’s How Buildings Learn (1994). Brand built on the architect Frank Duffy’s idea of shearing layers: a building is not one thing but a stack of layers, each changing at its own rate. The site hardly ever changes. The structure changes rarely. The interior walls change every few years, and the furniture changes constantly. Trouble starts when a fast layer is bound to a slow one. If the desks are bolted to the load-bearing walls, then rearranging an office becomes a construction project. Software has the same layering, and the same trouble. Encore’s database technology and its split into services are slow layers, while the wording on a checkout page is a fast layer that changes weekly. What we call the architecture of a system is simply its slowest layers: the parts that are most expensive to move, and that we would therefore least like to move. The one difference so far is speed: every layer of software moves faster than its counterpart in a building. Where the metaphor finally breaks is in its sense of time. A cathedral is eventually finished, and the builders go home. A software system is never finished, so its architecture cannot be a final plan. It has to be designed to absorb change for as long as the system lives.

So if an architecture is not a set of finished drawings, what is it? Modern practice describes it as the combination of four things, shown in the table below with an Encore example of each:

Dimension What it captures Encore example
Architectural characteristics The “-ilities” the system must exhibit, such as scalability, availability, and security Survive a 500× on-sale spike
Architectural decisions The rules and constraints every team must follow, with the reasons for them “Seat inventory is owned by exactly one service”
Logical components The building blocks of the system and what each is responsible for Seat Inventory, Order, Bot Screening
Architectural style The overall shape the components live in, such as a monolith or microservices To be decided in Chapter 2

We need all four, because each one is hollow without the others. Consider what happens when one goes missing. Suppose Encore adopts a microservices style and builds its components, but never records its decisions. Within a year, a well-meaning team adds a second service that also writes seat state, because nobody wrote down that seat inventory must have exactly one owner, and the same seat is sold twice during an on-sale. Now imagine the opposite: a beautifully kept log of decisions for a system nobody has actually built. It describes intentions, not a system. Characteristics tell us what the system must be good at, decisions tell us the rules that protect those qualities, components give the rules something to govern, and the style arranges the components into a whole. Take any one away, and the other three stop making sense.

Architecture and design as a spectrum

Much effort has gone into drawing a firm line where architecture ends and design begins, and none of the lines has held. The reason is that there is no line to find. Every decision sits somewhere on a spectrum, with the most architectural decisions at one end and the most local design choices at the other. So the useful question is not “is this architecture or design?” but “how far toward the architectural end does this decision sit?” The answer tells us how much care the decision deserves.

The architecture–design spectrum A horizontal gradient bar from "more architectural" (left) to "more design" (right), with markers at: choice of style, sync vs. async, library choice, class naming. more architectural more design choice of style sync vs. async library choice class naming

Figure 1.1 — The architecture–design spectrum, from choice of style to class naming. The further left a decision sits, the more it deserves analysis and a written record.

Three questions place any decision on the spectrum. First, how strategic is it: does it shape many parts of the system and many teams, or just one corner of the code? Second, how costly is it to reverse: if we turn out to be wrong, can we undo it in an afternoon, or will it take months and a migration? Third, how significant are its trade-offs: does choosing it mean giving up something we care about, or is the downside trivial? The more strongly a decision scores on these questions, the further left it sits, toward the architectural end.

Let us place two Encore decisions using these questions. The first is how Order should tell the rest of the system that a purchase happened. It could send each interested service its own copy through a dedicated queue, or publish a single message to a shared topic that any service can subscribe to. This choice is architectural on all three counts. It is strategic, because every service that cares about orders will be built around it. It becomes hard to reverse, because once 10 consumers depend on the messages arriving a certain way, changing the mechanism means changing all 10 at once. And it is full of trade-offs, which Section 1.5 works through in detail. The second decision is what to name the OrderPlaced class. That is design. If the name proves wrong, an afternoon of refactoring repairs it, nobody outside the team notices, and so the right amount of deliberation is about a minute. Notice what really separates the two decisions. It is not their subject matter or how technical they sound. It is the price of being wrong.

The two laws

This book rests on two laws set down by Mark Richards and Neal Ford in Fundamentals of Software Architecture. They are short enough to memorize, and we will return to them throughout the book:

First Law of Software Architecture (Richards & Ford). Everything in software architecture is a trade-off. If you think you have found something that isn’t, you haven’t yet identified the trade-off.

Second Law of Software Architecture (Richards & Ford). Why a decision was made is more important than how it was implemented.

Why should the why matter more than the how? Because the two are preserved very differently. The how is always recoverable: it is written in the code, and anyone can read it. The why is not in the code at all. It lives only in people’s heads and in whatever they took the trouble to write down, and people move on. The conviction behind the second law is older than the law itself. In 1992, when Dewayne Perry and Alexander Wolf set out foundations for the study of software architecture, they described an architecture as three things: a set of elements, the form those elements take, and the rationale for them. Of those three, rationale is the one our industry has most consistently failed to keep. The second edition of Richards and Ford’s book adds a third law, which Figure 1.1 has already shown us: most architecture decisions are not either-or choices but sit somewhere on a spectrum between two extremes.

The two laws shape how this book works. The first law is why it teaches analysis instead of handing out verdicts. Anyone who tells you that microservices are simply better (or simply worse) is describing only half of a trade-off: the half they happen to like. The missing half is usually the one you will end up paying for. So instead of asking “which is best?”, we will ask “what does each option cost, and which costs can Encore afford?” The second law is why Section 1.4 insists on writing decisions down. Memory is a poor archive. Picture a clever design in Encore’s seat-holding logic, built to solve a race condition that once sold the same seat twice. Six months later, the engineer who built it has moved teams, and nobody remembers the race condition. To a newcomer, the clever design now looks exactly like needless complexity, and someone well-meaning will set about “simplifying” it, bringing the original bug back with them. A written rationale is what stops that from happening.

Encore, our running example

Before we go further, let us meet the system we will be designing throughout the book, seen from the outside. Encore has two kinds of users. Fans browse concert listings, join an on-sale when tickets are released, pick their seats, and pay. Event organizers create events and watch sales dashboards as tickets sell. Encore also relies on two kinds of outside help: payments run through an external provider, and tickets are delivered to fans’ phones. Everything inside the box, meaning which services exist, how they talk and where data lives, is still undecided. Deciding it, one trade-off at a time, is the work of the chapters ahead.

Encore's system context Fans browse, queue and buy, and organizers manage events, all through Encore, a single system whose insides are not yet decided; Encore charges cards through a payment provider and issues tickets to mobile wallets. BROWSES · QUEUES · BUYS MANAGES EVENTS CHARGES CARDS ISSUES TICKETS Fan finds events, buys tickets Organizer creates events, tracks sales Encore ticketing platform Payment Provider card processing Mobile Wallets ticket delivery People The system Outside party Interaction Call to an outside party

Figure 1.2 — Encore’s system context: two kinds of users, two external dependencies, and one system whose internals are still undecided. The diagram leaves out a great deal on purpose; Section 1.4 explains why that restraint is a strength.

The architect, then and now

The role of the architect has changed as much as our idea of architecture. The architect of the old caricature sat above the team, produced thick documents and polished diagrams, handed them down, and moved on to the next project before the consequences arrived. That architect never had to watch an on-sale fail at 9:00 on a Friday because of a decision made on paper months earlier. The modern architect works inside the team. They stay close enough to the code to feel the effects of their own decisions, and senior enough to own the trade-offs that cross team boundaries, such as which service owns seat state, or how much latency the payment path may add. To return to our building metaphor one last time: the role has moved from the drawing office to the building site, and it is better for the move, because the people making the decisions now see what those decisions cost.

Increasingly, that architect thinks in platforms: systems whose users are not fans or organizers but other engineering teams, who build their own products on top of what the platform provides. Platform architecture is where this book ends, and every chapter between here and there adds a piece of it. Along the way, keep hold of a conviction I have come to value above any technique. Systems are built by people, out of decisions, and both the people and the decisions outlive any particular version of the code.

Recap. Software architecture is not a finished plan but the slowest-changing layers of a system that is never finished. It combines four things (characteristics, decisions, components, and style), and none of the four stands alone. Architecture and design are not separated by a wall; they differ in degree, and we place a decision on the spectrum by how strategic it is, how costly it is to reverse, and how much its trade-offs matter. Two laws guide everything that follows: everything is a trade-off, and why a decision was made matters more than how it was implemented.

Exercise 1.1. Take a system you know. Write down two of its decisions: one that sits at the far architectural end of the spectrum and one from the far design end. For each, answer the three questions from this section (how strategic, how costly to reverse, how significant the trade-offs). Then, for the architectural decision, name what was traded away to get it.


1.2 Architectural Characteristics

The vocabulary of “-ilities”

Requirements tell us what a system must do: let a fan search for a concert, pick a seat, pay, and receive a ticket. Architectural characteristics tell us what the system must be while it does those things: available, scalable, secure, testable, affordable. The industry calls them “-ilities,” after the suffix most of them share. The distinction is easy to state and surprisingly easy to lose sight of, because the two kinds of requirement fail in different ways. Imagine Encore’s checkout on a quiet Tuesday afternoon, with a few hundred fans browsing. Every feature works: search returns results, seats can be selected, payments go through. Now imagine the evening a top artist’s tour goes on sale and traffic jumps to 500 times its normal level. The features have not changed, but if the system cannot absorb the load, fans see errors, seats go unsold, and the business has failed on the one evening that matters most. Functional requirements were met on both days; what was missing on the second was a characteristic. To keep the many characteristics manageable, this book sorts them into four families, adapted from Richards and Ford (who group them as operational, structural, cloud, and cross-cutting):

Family Concern Examples
Operational How the system behaves while running availability, elasticity, performance, recoverability
Structural How the code sustains change modularity, maintainability, testability, deployability
Process How teams and the system co-evolve agility, upgradeability, learnability
Cross-cutting Constraints spanning everything security, privacy, accessibility, cost, sustainability

Older literature tends to underplay two members of the cross-cutting family, and both deserve a place in the first sketch of any design. The first is security. Security is largely a matter of boundaries: which components may talk to which, where data is checked, and who is trusted with what. If we draw a system’s boundaries without security in mind and try to add it later, we find that the boundaries are in the wrong places, and moving them means reworking the design. It is rather like adding fire stairs to a building whose floors are already poured: possible, but only by cutting through structure that was never meant to be cut.

The second is cost. In a data center you own, hardware is bought up front and a design decision rarely changes the bill. In the cloud, every architectural decision shows up on the monthly invoice: an extra replica, a chatty call between services, a log retained for a year. An architect who ignores cost has therefore not avoided the decision about how much the system will cost to run. They have only handed it to the finance department, which will make it later, with less understanding of the trade-offs, and less gently.

Listening for characteristics

Nobody hands the architect a list of characteristics. They are buried in what the business says, usually in words that sound nothing like “-ilities,” and so the architect’s first skill is translation. The work resembles a physician’s. The patient describes symptoms (“I get tired climbing stairs”), and the physician listens for the condition behind them. The resemblance holds further than we might like, because founders, like patients, are eloquent about what hurts and rarely know its cause. A founder will tell you that last year’s big on-sale was a disaster; it falls to the architect to work out whether the underlying condition was capacity, contention for the same seats, or something else entirely. Here is what Encore’s founders say, and what an architect hears in each sentence:

The founders say… The architect hears…
“When a top artist drops tickets, traffic is 500× normal for 10 minutes.” Elasticity: how fast the system can scale, as well as how far
“If we’re down during an on-sale, it’s front-page news.” Availability, precisely when load is worst
“We can never sell the same seat twice.” Data integrity: consistency where it counts
“Bots buy out shows before real fans get in.” Security and fairness, a composite worth naming
“We’re a team of nine and we ship weekly.” Deployability, testability, simplicity
“Margins are thin; the platform can’t eat them.” Cost as a standing constraint
“Last on-sale went wrong and we heard about it from fans on social media first.” Observability: knowing before customers tell you

A few of these translations are worth a closer look, because the words the architect chooses carry more meaning than they first appear to. The first row says elasticity, not merely scalability, and the difference is speed. A spike that lasts 10 minutes is over before a system that takes 15 minutes to add capacity has finished scaling, so for Encore the question is how quickly capacity arrives, not only how much of it there can eventually be. The second row ties availability to load: being up on a quiet Tuesday is easy, and it is the on-sale evening, when demand is at its highest, that the founders actually care about. The fourth row names a composite. Stopping bots is a security concern, but the founders’ complaint is really that real fans lose out, and naming fairness as part of the characteristic keeps that goal in view when we later design queues and screening.

Once we have heard a characteristic, we need to state it in a form we can check. An adjective cannot be tested: “highly available” is a hope, and no test will ever fail because the system fell short of a hope. So we write each driving characteristic as a measurable scenario, in the style of the quality-attribute scenarios developed at the Software Engineering Institute (SEI) at Carnegie Mellon University: “During a 500× on-sale, 99.9% of admitted fans complete checkout within 3 s.” Notice the three parts of that sentence. The stimulus is the event that puts the system under pressure (the on-sale); the conditions describe the state the system is in when it happens (500 times normal traffic); the response is the measurable result we promise (99.9% of admitted fans checking out within 3 s). A sentence like that can be turned into a test, a dashboard, or an alert, and Chapter 11 builds Encore’s service-level objectives (SLOs), the reliability targets the team commits to, from scenarios like it.

Listening also means noticing what nobody said. Nobody at Encore asked for portability across cloud providers, and nobody needs the seat map to render in 4 ms. Fred Brooks, in “No Silver Bullet” (1986), gives us a useful way to think about such temptations. He separated the essential complexity of a problem, the difficulty that comes with the problem itself, from the accidental complexity we bring to it through our tools and choices. Encore’s 500× spike is essential: it comes from how fans behave when a popular tour goes on sale, and no design can make it go away. Portability that nobody requested would be accidental complexity of the purest kind, because the only reason it would exist is that we decided to build it. Every characteristic we add costs design effort, testing, and operational care, so a characteristic with no business sentence behind it is overengineering, however diligent and responsible it feels to the engineer adding it.

The discipline of choosing few

Every characteristic we commit to trades against the others, and this is why the list must be short. Consider two examples. Elasticity complicates testability, because a system that adds and removes capacity as load changes behaves differently at every moment, and that behavior is hard to reproduce on a developer’s machine, where there is no spike and nothing to scale. Availability complicates cost. Five nines of availability (99.999%, or about five minutes of downtime a year) makes cost climb steeply, because each extra nine is bought with redundancy: more replicas, more locations, more standby capacity that sits idle most of the time, waiting for the failure it exists to absorb. When every characteristic pulls against others like this, a system designed to deliver 20 of them delivers none of them well. This is simply the first law, compounded: each characteristic brings its own trade-offs, and with 20 of them the trade-offs multiply until no design can satisfy them all. An orchestra in which every instrument plays at full volume makes only noise; we hear the melody because the other parts agree to play more quietly and accompany it.

So we pick seven or fewer: the characteristics that would sink the business if we got them wrong. The rest we let be adequate, which is not the same as neglected (to continue the analogy, the accompaniment must still play in tune). For Encore, the seven are elasticity, availability-under-peak, data integrity, security and fairness, deployability, cost, and observability. Testability and simplicity do not get their own places on the list, but they survive inside deployability, because a team of nine cannot ship weekly unless the system is simple enough to understand and its changes can be tested quickly.

Everything else, such as internationalization, portability, and offline support, is explicitly not driving the architecture. Writing that down matters as much as the list itself. Suppose that a year from now someone asks why Encore’s services are tied so closely to one cloud provider. If the decision not to optimize for portability was recorded, along with the reason, the conversation is short. If it was not, the team must reconstruct the reasoning from memory, and the omissions are exactly where such arguments begin.

A list of characteristics is only useful if it stays true as the system changes, and later chapters show how to keep it true. They turn each characteristic into a fitness function, an automated check that the architecture still has the characteristic it claims. “Survives a 500× spike,” for instance, becomes a load test that runs before every release and fails the release if checkout slows beyond the target. Chapter 15 then uses fitness functions to govern whole platforms. The reason this matters is that systems drift: each change is small and reasonable, and no single one of them seems to threaten elasticity or cost. A characteristic that is checked every week stays true, while one that is merely written down slowly stops describing the system it was written for.

Recap. Architectural characteristics describe what the system must be, as distinct from what it must do, and we find them by translating what the business says into the vocabulary of “-ilities.” Security and cost belong in the first design: security added later means redrawing boundaries, and cost ignored early is decided anyway, on the invoice. We choose seven driving characteristics or fewer, state each as a measurable scenario that can be tested, and record what we are deliberately not optimizing for.

Exercise 1.2. Interview a colleague about their product for 10 minutes. Write down the sentences they use to describe what matters, and extract five candidate characteristics from them, noting the sentence behind each. Then cut two of the five and write a sentence for each cut explaining why the product can live with that characteristic being merely adequate.


1.3 Modularity and Logical Components

Thinking in components

So far we have looked at Encore from two distances: from far away, as one system with one business purpose, and up close, at the level of design decisions such as naming the OrderPlaced class. Between those two distances sits the level where most architecture work actually happens. Here we speak of logical components: named building blocks, each with a clear responsibility, described without yet saying how or where they will run. “Seat Inventory keeps the true state of every seat” is a statement about a logical component; a statement such as “Seat Inventory runs as a set of containers in the cloud” would not be, because it describes deployment, and deployment is a separate decision we will come to later.

I have come to believe that where we draw the boundaries between components matters more than any technology we choose, and the reason is that those boundaries rarely stay logical for long. A line on the whiteboard soon decides which team owns which code, because people naturally take ownership of the boxes they are given. Once teams own code, they want to release it on their own schedule, so the same line becomes a deployment boundary. And if the system is later split into separately running services, the line becomes a service boundary, with a network between the two sides. A database or a framework can be swapped with effort; a boundary that has hardened into teams, release trains and network contracts is far more expensive to move.

The traffic between boxes and teams runs in both directions, and we have known this for a long time. In 1968 Melvin Conway observed that organizations which design systems tend to produce designs that copy their own communication structures. A system built by three teams that rarely talk tends to end up as three parts joined by narrow interfaces, because the interfaces are where the teams had to agree. We now call this Conway’s law. Much later, practitioners began to use the law deliberately, in reverse: instead of letting the existing org chart dictate the architecture, they shaped the teams to match the architecture they wanted. That move was named the inverse Conway maneuver at ThoughtWorks and was popularized by the book Team Topologies. Whichever direction we push, the consequence for us is the same. The boundaries we sketch for Encore today are, in effect, a first draft of how Encore’s engineers will be organized, so they deserve real care.

How, then, do we find good boundaries? Two complementary techniques help, and it pays to use both, because each one notices things the other tends to miss.

The workflow approach starts from the journeys people take through the system. We walk each main journey step by step and ask, at every step, what the system must do to make it possible. Take a fan’s on-sale journey: the fan browses the catalog to find the concert, joins the queue when sales open, passes the bot check, picks seats, pays, and receives the ticket. Each of those steps hints at a responsibility, such as “keep the catalog”, “control admission”, “tell fans from bots”, “hold seats”, “take payment” and “deliver tickets”. The strength of this approach is that it follows the order in which things happen, so it is good at finding the pieces a purchase depends on.

The actor/action approach starts from the people and systems that interact with Encore, rather than from a single journey. We list every actor, write down everything each actor does, and then group related actions together. Organizers create events and watch sales dashboards. Fans do everything in the journey above. The payment provider, an actor that is a system rather than a person, calls back to tell us whether a payment succeeded. This approach is good at finding responsibilities that no fan journey would reveal; the organizer’s dashboard, for example, appears nowhere in the fan’s path, yet it needs a home.

When we run both approaches over Encore and reconcile what they find, merging the duplicates and giving each responsibility exactly one owner, the following set of components settles out. The last column records which architectural characteristic, from Section 1.2, most strongly justifies each component’s existence.

Component Responsibility Driven by
Event Catalog Listings, search and event details availability (read-heavy: many fans browsing, few changes)
Sale Gate Admission control during on-sales: queueing fans and keeping the order fair elasticity, fairness
Bot Screening Telling real fans apart from automated scripts security
Seat Inventory The single source of truth for the state of every seat data integrity
Order The purchase workflow, from start to finish data integrity
Payment Integration Talking to the payment provider and handling its callbacks recoverability
Notification Sending tickets to fans’ wallets and emails to fans (deliberately modest)
Sales Analytics Organizer dashboards cost (isolate the load)
Encore's first-cut logical components Fans browse the event catalog and join an on-sale through the Sale Gate, which screens bots and admits fans to Order; Order reserves seats, charges through payments, sends tickets and feeds sales analytics, which organizers watch along with the catalog they manage. BROWSES JOINS SCREENS ADMITS RESERVES CHARGES SENDS TICKET SALES FACTS WATCHES MANAGES EVENTS Fan browser · app Organizer venue staff Event Catalog events · seats Sale Gate admission Bot Screening risk signals Order purchase flow Seat Inventory holds · sales Notification tickets · email Payments PSP integration Sales Analytics dashboards People Component Owns data Outside party Purchase path Fact, not a call

Figure 1.3 — Encore’s first-cut logical components. Sale Gate, Order and Payment Integration form the purchase path that every buying fan passes through. The dashed arrow shows Sales Analytics learning about sales from outside that path, rather than being called as part of it, a distinction on which Chapters 4 and 6 depend.

Notice that the characteristics, rather than the nouns in the business, shaped this cut. Sale Gate is the clearest case: it exists only because of the 500× spike. A business whose traffic stayed calm would let fans go straight to seat selection, and would never need a component whose whole job is to make people wait their turn. Seat Inventory is the second case. It is small and isolated on purpose, because data integrity, the promise that no seat is ever sold twice, needs a single owner for seat state, and that ownership is easiest to defend when the owner has nothing else to do. If Seat Inventory also handled, say, search, every change to search would put the seat-selling logic at risk. Read the last column of the table again with this in mind and a pattern appears: a good component set is the list of characteristics, drawn as boxes. Each box is there because some quality the business asked for needed a place to live.

The entity trap. The tempting wrong turn is to name components after the nouns in the database: User Manager, Event Manager, Ticket Manager. The trouble is that entities describe what a system stores, while components should describe what it does. Consider buying a ticket in such a design: the purchase needs the User Manager to check the fan, the Event Manager to check the on-sale, and the Ticket Manager to hold the seat, so no single component owns the purchase, and every workflow ends up smeared across all of them. An architecture of Managers is a database wearing a trench coat: it looks like a set of components from a distance, but underneath it is still just the tables.

Coupling, cohesion, and the shape of change

Once we have drawn boundaries, we need a way to judge whether they are any good. Two terms from the structured design movement of the 1970s give us that measure, and they have lasted because they are simple. Cohesion is the degree to which the things inside a component belong together: in a cohesive component, the parts serve one purpose and tend to change for the same reasons. Coupling is the degree to which a change in one component forces changes in others. We want high cohesion and low coupling, which is to say components that are tightly knit inside and only loosely connected to one another.

The idea is older than software design, and broader. Herbert Simon described the same property in “The Architecture of Complexity” (1962), where he argued that complex systems which endure tend to be nearly decomposable: their parts interact strongly within themselves and only weakly with each other. A living cell illustrates the idea well. A great deal of chemistry happens inside the cell, while far less crosses its membrane, and that separation is what lets the cell be understood, and indeed survive, as a unit. The analogy has a limit worth stating: a cell’s membrane evolved, while our component boundaries are chosen, and we can choose them badly. But the lesson carries over. A well-bounded component, like a cell, does most of its work internally and exchanges a small, well-defined set of messages with its neighbors.

Cohesion and coupling tell us what good boundaries look like, but not where to draw them. For that we turn to David Parnas, who in 1972 argued that a system should be divided along the design decisions that are most likely to change, with each module hiding one such decision from the rest. The idea, which we now call information hiding, is that if a volatile decision is known to only one module, then changing that decision disturbs only that module. From Parnas’s criterion follows the practical test we will use throughout this book, the shape of change: when a typical business change arrives, we trace which components it touches. With good boundaries, it lands inside one component. Suppose Encore decides to offer presale codes, so that members of an artist’s fan club can buy before the general public. That change concerns who is admitted to an on-sale and when, which is exactly Sale Gate’s responsibility, so it should touch Sale Gate and little else. If the same change instead forces edits to five components, the problem lies in the boundaries rather than in the people writing the code, and the boundaries should be fixed before anyone blames the developers.

One more distinction keeps all of this from going wrong: keep logical architecture and physical architecture apart, both in your diagrams and in your head. The logical architecture says what the components are and what each is responsible for. The physical architecture says how they run: in how many processes, on which machines, talking over which networks. Encore’s eight components might deploy as one process, as three, or as eight separate services. That is a separate decision with trade-offs of its own, which we make in Chapter 2 and revisit throughout the book. The danger in merging the two views is that every logical box quietly turns into its own service. Calls that would have been ordinary function calls inside one process then become network calls, each with latency, each able to fail, and each needing retries and timeouts. Treating every component as a service is how systems end up with a network call inside every function.

Recap. We find components by walking the main workflows and by listing what each actor does, then reconcile the two views. We let the architectural characteristics decide the cut, and we name components for what they do rather than for the data they store. Good boundaries are cohesive within and loosely coupled between, so that a typical business change lands in one component. How the components are deployed is a separate decision, and keeping it separate is half the discipline.

Exercise 1.3. Encore adds gift cards: fans can buy a gift card and later use it to pay for tickets. List the components from the table that would have to change, and for each one say briefly why. If more than two change, propose a better boundary, for example a new component or a moved responsibility, and show how it would contain the change.


1.4 Communicating Architecture — C4 and ADRs

Diagrams with the C4 model

We have found Encore’s components and argued about their boundaries. None of that work is finished until other people can see it, because an architecture that exists only in people’s heads cannot be checked. Each teammate carries a slightly different version of it: one believes Order talks to Seat Inventory directly, another believes everything goes through Sale Gate. Nobody notices the difference while the system is only talked about. They notice when two pieces of code, each written faithfully to one version, meet and fail to fit. Writing the architecture down, in pictures and in prose, is how we bring those private versions into the open while they are still cheap to reconcile.

Pictures come first, and the oldest lesson about them is that no single picture serves every reader. Philippe Kruchten made the point in his 4+1 view model (1995). He split an architecture into four views, each for a different concern: the logical view for what the system does, the process view for how it behaves at run time, the development view for how the code is organized, and the physical view for where it runs. Scenarios, the “+1”, tie the four together. The modern standard for drawing systems, Simon Brown’s C4 model, cuts the problem along a different line. Instead of separating concerns, it separates distances. Its four levels (Context, Containers, Components, Code) are four zoom settings on the same system, and each answers one question for one audience:

Level Name Answers Audience
1 System Context What is this system, and what does it touch? everyone, including non-technical readers
2 Container What are the separately deployable pieces? engineers, operations, security
3 Component What lives inside each piece? the team that owns it
4 Code How is a component built? rarely worth drawing, because it goes stale fastest

One word in the table needs care. A C4 container is not a Docker container. It means anything that runs or stores data on its own: a web application, a mobile app, a back-end service, a database. Encore has no containers yet, because how its components deploy is a separate decision that Chapter 2 makes. Suppose that decision puts Seat Inventory in its own service with its own database; those would then be two containers on the Level 2 diagram, and Level 3 would zoom into the service to show what lives inside it.

The levels work the way a road atlas does. A country map shows the motorways between cities, a regional map shows the towns along one motorway, and a street plan shows one town’s streets. Each map is useful precisely because it leaves out the detail of the next: a country map that tried to show every street would be unreadable. Figure 1.2 is a Level 1 map of Encore. It shows two kinds of users, two external dependencies, and one box for Encore itself, and it says nothing at all about what is inside that box. Figure 1.3 is a close relative of Level 3 rather than Level 3 itself. Strictly, C4’s Level 3 zooms into a single container, one diagram per container. Encore’s containers do not exist yet, so Figure 1.3 shows the logical components across the whole system instead.

Leaving things out has a second benefit, less obvious than readability: a diagram cannot be wrong about what it omits. Figure 1.2 makes no claim about Encore’s internals, so we can split Order in two, change databases, or give Bot Screening a service of its own, and Figure 1.2 stays true. Here the atlas analogy breaks, and the break is instructive. An atlas is printed once for territory that barely moves; a road built this year will still be there in twenty. Our territory moves every week. The higher levels, which leave out the most, stay accurate longest. The lower levels, which show the most, go stale soonest, which is why the table warns that Level 4 is rarely worth drawing.

That is why three habits help, each of them a defense against a diagram drifting away from the system it claims to describe.

First, title each diagram with the question it answers, such as “How does a fan’s payment flow?”, and delete anything that does not help answer it. A title like “Encore architecture” invites every box anyone can think of. A question gives you a test for each box: does it help answer this? If not, it belongs on another diagram, or on none.

Second, label every arrow with a verb. An unlabeled arrow between Order and Payment Integration could mean five different things: Order calls Payment Integration and waits for an answer, Order sends it a message and moves on, data flows from one to the other, one reads the other’s database, or one merely depends on the other’s code. Each reader will silently assume a different meaning, which brings us back to the private versions we started with. A label such as “requests a charge from” settles the question on the page.

Third, keep diagrams as code, in a text-based tool such as Mermaid or Structurizr, so that they live in the repository next to the system. A diagram stored that way shows up in code review, where a change to it can be diffed like any other change, and it changes in the same pull request as the code it describes. A diagram drawn in a wiki has no such link to the code. It is accurate on the day it is drawn and drifts from then on, because nothing reminds anyone to update it.

Architecture decision records

Diagrams show what the system is. They cannot show why it is that way, and the second law tells us that the why is the most valuable thing we produce. The architecture decision record (ADR) is how we keep it: a short document that records one significant decision, the situation that forced it, and what it will cost. The common format comes from Michael Nygard and has five sections. The title names the decision. The status says where it stands: proposed, accepted, rejected, or superseded. The context describes the forces at work when the decision was made. The decision states what we chose. The consequences state what follows, good and bad. Richards and Ford add a sixth, a compliance section that says how we will check the decision is actually being followed; this book calls it Governance. An ADR fits on one page so that people will read it. It is numbered so that others can cite it (“see ADR-007”). And it is immutable except for its status, for a reason we will come to shortly. Here is one of Encore’s:

Section Encore’s ADR-007
Title Virtual waiting room in front of seat selection
Status Accepted
Context On-sales bring 500× normal traffic within minutes. Letting that load reach Seat Inventory directly would bring it down at exactly the moment availability matters most. Fans expect a fair chance at seats, and bots try to skip the line.
Decision All on-sale traffic passes through a queue-based waiting room (Sale Gate), which admits fans only as fast as Seat Inventory can sustainably serve them.
Consequences Losses: fans have to wait (mitigated by showing each fan an accurate queue position). Sale Gate becomes a new critical path, and it alone must absorb the raw spikes. Gains: Seat Inventory can be sized for the admitted flow rather than for the peaks; fairness is enforced in one place; and the waiting room is a natural place to screen out bots.
Governance Before each release, a load test replays the worst on-sale so far at twice its traffic; an alert fires if 99th-percentile (p99) admission latency exceeds its budget.

Read the consequences row again. It lists losses as well as gains, in plain words. Fans wait, which they will not enjoy. And Sale Gate becomes a single point through which every purchase must pass: if it fails during an on-sale, nobody buys anything, however healthy Seat Inventory may be. An ADR that lists only gains is advertising. By the first law it is also wrong, because a decision with no cost is a decision whose trade-off we have not yet found, and the cost we have not found is the one that will surprise us later.

Now the reason for immutability. ADRs are never edited to look right in hindsight. When a decision is reversed, we write a new ADR that supersedes the old one. The old record changes in a single respect: its status becomes “superseded,” with a pointer to its successor. Everything else stays as it was written. The result resembles the strata in a cliff face, where each layer shows what the ground looked like when it was laid down, and a geologist can read the history of the land from bottom to top. Suppose that, years from now, Encore replaces its waiting room with some other way of handling on-sale spikes. The new ADR records why, and ADR-007 remains, showing what the team knew and feared when it chose the waiting room. That layered record does two jobs. It stops teams from re-arguing questions that were settled for good reasons, and it stops them from repeating mistakes whose reasoning has been forgotten.

An ADR's life An architecture decision record is written and proposed for review; the team either accepts it, and a later ADR may supersede it, or rejects it with reasons, and the rejected record is kept because it saves the next person from re-arguing the question. WRITTEN · IN REVIEW TEAM AGREES WITH REASONS NEW ADR REPLACES IT Proposed Accepted Rejected still worth keeping Superseded Start Status Kept even when rejected Transition: event

Figure 1.4 — An ADR’s life: it is proposed, then accepted or rejected, and an accepted ADR may later be superseded by a new one. Rejected records are useful too: “we considered sharding and declined, here’s why” saves the next person a week.

An ADR is written for engineers, but the decision it records matters to people who will never read one, and the same decision has to be told differently on different floors of an organization. Gregor Hohpe, in The Software Architect Elevator (2020), pictures the architect riding an elevator between the penthouse, where strategy is set, and the engine room, where systems are built, and being useful because they can speak the language of both floors. Engineers get the ADR. Executives get one sentence of outcome and risk: “Fans will queue briefly so the site cannot crash during our biggest sales moments.” Notice that this sentence keeps the cost: fans queue. That is what makes it translation rather than spin. Spin would mention only the site that cannot crash; translation carries the same trade-off as the ADR, in fewer and plainer words. Writing that sentence faithfully is part of the architect’s job.

Coding agents are now a third reader, and a peculiarly literal one. When an agent works in Encore’s repository, the ADRs and diagrams stored there become part of its context, and it reads only what is written. A human engineer also learns the unwritten rules: in a design review, in a hallway, from a colleague who says “we never do that here.” An agent has no such conversations. Suppose the rule that only Seat Inventory changes seat state is recorded in an ADR. An agent asked to speed up checkout will then route its change through Seat Inventory. If the rule lives only in the team’s heads, the same agent may have Order write to the seat table directly, without warning anyone, because nothing it can read told it not to. The agent will follow a recorded boundary and cross an unrecorded one. Agents also change the economics of writing ADRs, though not of owning them. An assistant can draft an ADR in seconds, but the consequences row is a promise about costs someone will bear, and it still needs an owner who will live with them.

Recap. The C4 model draws a system at four zoom levels, from context down to code, and each level is useful because of what it leaves out; the higher levels stay accurate longest. Good diagrams are titled with the question they answer, label every arrow with a verb, and live in the repository as code, changing with the system. Diagrams show what the system is; ADRs keep why it is that way. An ADR states its losses as plainly as its gains, and when a decision changes, it is superseded by a new record rather than rewritten.

Exercise 1.4. Pick a significant decision your team made but never recorded, and write its ADR with all six sections: title, status, context, decision, consequences and governance. Then show it to whoever argued for the other option at the time. Revise the consequences row until they agree it describes the losses fairly; only then is the ADR done.


1.5 Trade-Off Analysis in Practice

Anatomy of a trade-off

The first law tells us that every decision is a trade-off, but a law stated in the abstract is easy to agree with and hard to apply. So let us practice it deliberately, on a decision small enough that we can see all of it at once. When a fan completes a purchase, three components need to hear about it. Notification must send the fan their ticket. Sales Analytics must update its sales dashboards. Bot Screening must add the purchase to what it knows about buying patterns, so that it gets better at telling fans from scripts. The question is how Order, which owns the purchase, should tell them.

There are two familiar ways, and it helps to be precise about how each one works. With a queue, Order sends a separate message to each consumer: one queue for Notification, one for Sales Analytics, one for Bot Screening. Each queue has exactly one reader, and a message sits in it until that reader takes it. With a topic, Order publishes the news once, to a single shared stream, and any component that has subscribed receives its own copy. The producer writes once and does not know, or need to know, who is reading. The difference sounds like plumbing, but it decides who knows about whom, and that is an architectural question.

Option A: a queue per consumer Order publishes OrderPlaced three times, once into each consumer's own queue, with a payload tailored to that consumer: ticket fields for notifications, stats fields for analytics and risk fields for bot screening. Option A — a queue per consumer ORDERPLACED · TICKET FIELDS ORDERPLACED · STATS FIELDS ORDERPLACED · RISK FIELDS Order producer notif.queue queue analytics.queue queue botscreen.queue queue Producer Queue Publish
Option B: one topic, many subscribers Order publishes OrderPlaced once, with one shared schema, to the orders topic, which delivers the same event to every subscriber: Notification, Analytics and Bot Screening. Option B — one topic, many subscribers ORDERPLACED · ONE SCHEMA SUBSCRIBE SUBSCRIBE SUBSCRIBE Order producer orders.topic topic Notification subscriber Analytics subscriber BotScreening subscriber Producer Topic Subscriber Publish Delivery to a subscriber

Figure 1.5 — The two viable options: a dedicated queue from Order to each consumer, or one shared topic that every consumer subscribes to. Neither is wrong in general, which is why the choice needs analysis.

The first step of the method is to set the options side by side and state, fairly, what each one costs and what each one buys. “Fairly” matters: an architect who has already chosen tends to write a table in which the favorite wins every row, and that table proves nothing. Here is an honest one:

Concern Queues (A) Topic (B)
Adding a fourth consumer Order must change to send to one more queue the new consumer just subscribes; Order never knows
Payload discipline each message tailored to its one consumer one schema for everyone, so sensitive risk fields reach every reader
Per-consumer monitoring natural: each queue’s depth shows how far behind its consumer is lag per consumer group, which most brokers expose
Coupling Order knows every consumer by name consumers are invisible to the producer
Security need-to-know by construction every subscriber has read access to the whole stream

Read the table row by row and a pattern emerges. Option A keeps Order in control: it decides exactly what each consumer sees, which is good for security and payload discipline, but it also means Order has to be edited every time a new consumer appears. Option B frees Order from that work, because a new consumer simply subscribes, but the price is that everything Order publishes is visible to everyone who subscribes. The monitoring row is closer than it first looks. A queue’s depth tells you directly how far behind its consumer has fallen, and most topic-based brokers give you the same view as the lag of each consumer group (the set of instances that together read the topic on behalf of one consumer), so neither option leaves us blind.

A table like this is necessary, but it cannot make the decision, because each row favors a different option and nothing in the table says which rows matter most. Here is the heart of the method. We go back to the characteristics Encore committed to in Section 1.2, and we let them act as the arbiter. Two of them pull in opposite directions. Encore expects new consumers soon, organizer payouts and a recommendation engine among them, and under option A each one would mean another change to Order, on the purchase path, where changes are riskiest. That argues for B. But Bot Screening’s payload carries fairness data, the signals that help separate real fans from bots, and security and fairness is one of Encore’s seven drivers. Under option B that sensitive data would flow to every subscriber, including a recommendation engine that has no business seeing it. That argues for A.

When two committed characteristics point in opposite directions, the answer is often not to pick a winner but to split the decision along the line where they disagree. A defensible resolution uses both mechanisms. The broad OrderPlaced fact, which says that a fan bought these seats for this event and carries nothing sensitive, goes on a topic, so that payouts, recommendations and whatever comes after them can subscribe without touching Order. The sensitive risk payload goes on one dedicated queue that only Bot Screening reads, so need-to-know is enforced by construction rather than by policy. And an ADR records why we chose this, so that the next architect does not “simplify” the design back into a single topic and leak the fairness data in the process.

The first law promised that the hybrid would carry a price of its own, and it does. Order now has to publish twice for every purchase: once to the topic and once to the queue. Suppose Order sends to the topic and then crashes before it sends to the queue. Notification delivers the ticket and Sales Analytics counts the sale, but Bot Screening never learns that the purchase happened, and nothing in the system notices the gap. To close it, both publishes must go through a transactional outbox (Chapter 6). Order writes the outgoing messages into its own database in the same transaction as the order itself, and a separate relay then delivers them. Either the order and both messages are recorded together, or none of them is, so a crash can delay a message but cannot lose one. That is real extra machinery, and it belongs in the ADR’s consequences row alongside the gains.

Notice what kind of answer we have reached. There is no generic answer to the question “queue or topic?”, only an Encore answer. Trade-off analysis weighs the pros and cons by the characteristics a particular system has committed to, and so another company, looking at exactly the same table, could reasonably choose differently. A company with no sensitive payload would take the topic and stop there; one that never expected a new consumer might happily keep the queues. The table is the same for everyone. The weights belong to the business, and that is why the architect’s first job, back in Section 1.2, was to find out what those weights are.

Recap. We state the options fairly and tabulate what each one costs and buys. Because different rows favor different options, the table alone cannot decide, so we let the characteristics the system has committed to break the tie. When two of those characteristics disagree, the best answer may split the decision between them. The resolution earns an ADR, and whatever new risks it introduces, such as the double publish that needs an outbox, go into the consequences row, where the next architect will find them.

Exercise 1.5. Pick a messaging or integration choice in a system you know, and build its version of the queue/topic table: the options, the concerns, and what each option costs for each concern. Then answer two questions in writing. Which of the system’s committed characteristics broke the tie? And what did the winning option cost, including any new risk it introduced?


Kata: Curtain Call

Every chapter closes with an architectural kata. The word comes from the martial arts, where a kata is a set form that a student rehearses again and again, until movements that once had to be thought about become instinct and, in time, judgment. Ours works the same way, with one difference: instead of repeating a fixed form, we practice the chapter’s methods on a fresh problem under realistic constraints. Each kata gives you a business brief, asks you to work within a time box, and ends with you defending your choices before peers. The defense matters as much as the design. An architecture that you cannot explain to a skeptical colleague is one you do not yet fully understand, and the questions your peers ask are the closest thing to the real world that a practice session can offer.

Your brief: “Curtain Call.” A regional theater chain (12 venues) wants season subscriptions, seat selection, member presales, and a waitlist for sold-out shows. Two-person engineering team. Modest but loyal traffic, except the annual holiday show, which sells out in an hour. The CFO’s phrase: “we cannot afford drama, in either sense.”

Read the brief the way we read Encore’s founders in Section 1.2: every sentence is a clue, and few of them use the vocabulary of characteristics. Curtain Call looks a little like Encore, since it sells seats and has one evening a year when everyone arrives at once, but the surrounding facts are very different. A two-person engineering team and a CFO who wants no drama of either kind will pull your design in directions that Encore’s did not. Notice those differences before you start drawing.

Deliverables:

  1. Driving characteristics. Choose at most seven, and trace each one to a specific sentence in the brief. Then name one plausible candidate that you considered and explicitly declined, and say why, just as Section 1.2 set aside portability for Encore.
  2. Logical components. Present them as a table and a diagram, like Figure 1.3, and derive them with the workflow approach or the actor/action approach from Section 1.3. No Managers: name each component for what it does, not for the data it stores.
  3. Three ADRs, one for each of your most significant decisions. In each consequences row, state the losses in plain words alongside the gains.
  4. A context diagram in the spirit of Figure 1.2, showing Curtain Call as a single box surrounded by the people who use it and the outside systems it depends on.

Rubric: use these questions to judge your own work before you present it, and expect your peers to ask them too.

Criterion The question to ask yourself
Traceability Can you point to the sentence in the brief behind every characteristic you chose?
Boundary quality When the business asks for a typical change, such as a new kind of presale, does it land in one component?
ADR honesty Would the person who argued for the losing option agree that each consequences row describes their loss fairly?
Context clarity Does the context diagram show only the people, the outside systems, and Curtain Call itself, with no internals?
Restraint Is the design sized for Curtain Call and its two engineers? A design that could serve Ticketmaster has failed the CFO.

Where you now stand

Look back at what this chapter has given you. You can now hear characteristics in ordinary business sentences, shape components around those characteristics, weigh competing options with the first law, and record your reasoning in ADRs so that it outlives the conversation that produced it. One question remains open, and it is a large one: you cannot yet name the overall shape of the system. Should Encore be a modular monolith, deployed as one unit but divided cleanly inside? A service-based system, split into a handful of separately deployed parts? Or something more exotic? Architects have been building a shared vocabulary for such shapes for decades. Mary Shaw and David Garlan’s Software Architecture: Perspectives on an Emerging Discipline (1996) offered one of the field’s first systematic catalogs of them, and the catalog has grown since. Chapter 2 supplies that vocabulary of styles, along with the judgment to tell when each one fits.


References