Appearance
Chapter 6: Architecture Governance
General Track · Foundational · ~35 min read + exercise
Why this chapter exists
Every chapter so far has been about making good architectural decisions — seeing the trade-offs, choosing a style, modeling data, designing for failure and communication. This final foundational chapter is about a different and frequently neglected problem: making those decisions stick. Because here is an uncomfortable truth that every experienced engineer has watched play out — a well-designed system does not stay well-designed on its own. Left alone, it drifts. The clean boundaries blur, the rules get bent under deadline pressure, the careful trade-offs get quietly reversed by someone who didn't know they were trade-offs, and two years later the system that was designed with such care has decayed into something nobody quite intended and nobody fully understands.
This decay isn't caused by bad engineers or bad intentions. It's the natural entropy of a codebase touched by many hands over a long time, each making locally reasonable choices without the full picture. The architect who designed the layered boundaries isn't standing over every pull request. The reasoning behind the service split lives in someone's memory until they change teams. The "we'll keep these modules decoupled" agreement holds right up until a Friday-afternoon deadline makes a quick cross-module import irresistible. Governance is the discipline of resisting this drift — not by heroic vigilance, which doesn't scale and burns people out, but by building the resistance into the system itself, so that staying on course is the path of least resistance rather than a constant fight.
A concrete picture of the drift. Recall the modular monolith from Chapter 2 — the Laravel app with clean domain boundaries: Ticketing/, Billing/, CustomerProfile/, each talking to the others only through defined interfaces. That design is only a convention. Nothing at runtime stops a developer from importing a billing class directly into ticketing because it was the fastest way to ship a feature on a tight day. The first time it happens, no harm seems done. But that import is a crack in the boundary, and cracks invite more cracks. A year and fifty such imports later, ticketing and billing are thoroughly entangled, the "modular" monolith is a big ball of mud with module-shaped folders, and the option to extract billing into its own service — which the boundaries were supposed to preserve — is gone, because everything reaches into everything. No one decided to abandon the architecture. It eroded one reasonable-looking shortcut at a time, and nothing was there to catch it.
That's the gap governance fills. It has two complementary halves, and this chapter is built around them. The first is recording decisions so the reasoning survives the people who made it — which you already met as ADRs in Chapter 1, and which we now place in their full governance role. The second is enforcing decisions automatically — fitness functions, the mechanism that catches the boundary-crossing import in CI before it merges, instead of in a code review six months too late or never. Together they're how architecture becomes something that holds its shape over years and evolves on purpose, rather than something that decays by accident. This is the right chapter to close the foundational track, because it's where the decisions from all the previous chapters get the machinery that keeps them real.
What this chapter covers
- Why architecture drifts — the forces that erode a design, so you know what you're governing against.
- Architecture Decision Records — ADRs in their full role: not just a format, but the institutional memory that makes decisions durable and evolution traceable.
- Fitness functions — automated tests for architectural properties, the mechanism that enforces decisions continuously instead of relying on human vigilance.
- Evolutionary architecture — how to let a system change on purpose, governed and guided, rather than freezing it or letting it rot.
By the end you'll do a hands-on exercise identifying a property of your own system worth governing and designing a fitness function to protect it.
Let's start with the thing we're fighting: drift.
Why architecture drifts
You can't govern against a force you don't understand, so it's worth being precise about why a well-designed system decays. It isn't one cause; it's several, and they compound.
The first is knowledge loss. An architectural decision carries reasoning — why the boundary is here, what trade-off was accepted, what constraint forced the shape. That reasoning lives in the heads of the people who made the decision, and people forget, change teams, and leave. The code records what was built but not why, so a new engineer sees a structure whose purpose is invisible to them, and "fixes" or works around something that was deliberate. We met this exact scene in Chapter 1 — the engineer who "improves" the arbitrary-looking service split and reintroduces a year-old bug. Multiply it across a team over years and the original design intent simply evaporates.
The second is local optimization under pressure. Almost every act of architectural erosion is a locally reasonable choice. The cross-module import that saves an hour on a deadline. The duplicated bit of data that avoids a join today. The skipped abstraction that ships the feature now. Each one, in isolation, looks fine — even smart. It's only in aggregate, over time, that they add up to a degraded architecture. This is the time-to-market tension from Chapter 1, playing out one small decision at a time, and the danger is precisely that no single choice feels like the one that broke things. Ousterhout names this directly in A Philosophy of Software Design: complexity is incremental — it accumulates not through one catastrophic error but through dozens of small, individually defensible concessions, which is why he argues for a near "zero-tolerance" stance toward letting them slide. It's the deeper justification for everything in this chapter: governance has to be continuous, because a decay that arrives one reasonable-looking import at a time can only be caught by something watching at that same granularity.
The third is the absence of feedback. When you write code that fails a test, you find out immediately and fix it. When you write code that violates an architectural principle — crosses a boundary, introduces a cycle, couples two things that were meant to be independent — nothing tells you. There's no red X. The violation merges, works fine functionally, and sits there as latent decay. By the time anyone notices — usually when the accumulated decay finally makes some change painfully hard — the cause is buried under months of further changes. Architecture has historically lacked the fast feedback loop that unit tests gave functional correctness, and that absence is a big part of why it rots.
Seeing these three clearly tells you what governance has to do: preserve the reasoning (against knowledge loss), make the cost of erosion visible at the moment it happens (against local optimization), and provide fast feedback on architectural violations (against the feedback gap). The two main tools map onto these needs. ADRs preserve the reasoning. Fitness functions provide the feedback and surface the erosion in CI. Let's take them in turn.
ADRs: institutional memory for decisions
You met Architecture Decision Records in Chapter 1, as the way to capture a decision and its trade-offs — title, status, context, decision, consequences, with the consequences section honestly naming what the decision costs. There we treated them as a tool for thinking a decision through. Here we place them in their governance role, which is larger: ADRs are a system's institutional memory, the defense against knowledge loss, and the record that makes architectural evolution traceable rather than mysterious.
The governance value comes from a few properties worth drawing out beyond the basic format.
They make decisions discoverable. Kept in the repo (docs/adr/, numbered sequentially), versioned alongside the code, ADRs mean the reasoning behind a structure is a git log and a directory away, not a Slack search or a former colleague's memory. The new engineer who wonders "why is billing split out like this?" can find ADR 0014 and read the actual reasoning, instead of guessing and re-litigating a settled decision — or worse, silently undoing it.
They make decisions reviewable as decisions. When an ADR is part of the pull request that makes a significant change, the decision gets reviewed, not just the code. Reviewers can push on the reasoning, surface a constraint the author missed, or note that the consequences section is hiding a cost — before the decision is locked in. This turns architecture from something that happens silently in one person's head into a social, deliberate, reviewable act, which is itself a governance win.
They make evolution traceable. This is the property most people miss. You don't edit an accepted ADR when the decision changes — you write a new one and mark the old one "superseded by ADR 0023." Over time this builds a chain: ADR 0014 chose read replicas for reporting; ADR 0023 superseded it by moving reporting to a separate analytics database; ADR 0031 superseded that. The chain is a readable history of how the architecture's thinking evolved and why, which is exactly what you need when governing a system that changes — it lets you understand not just the current design but the path that led to it, so the next change is informed by the full arc rather than just the latest snapshot.
The practical discipline is the same as Chapter 1, now with the governance rationale clear: write an ADR for significant decisions (the ones where someone will later ask "why is it like this?"), write it when you decide so the context is fresh and honest rather than a later rationalization, keep them in the repo, supersede rather than edit, and — the part that makes it governance rather than paperwork — actually read them when making related decisions. An ADR archive nobody consults is just a diary. An ADR archive the team treats as the first stop before changing something is institutional memory doing its job.
ADRs preserve the reasoning. But reasoning in a document doesn't stop a developer from violating the decision in code — the boundary-crossing import merges whether or not an ADR said the boundary mattered. For that, you need something that runs.
Fitness functions: automated tests for architecture
A fitness function is any automated check that verifies an architectural property holds — an objective, runnable test not of what the code does (that's a unit test) but of how the system is structured or behaves as an architecture. The term comes from the book Building Evolutionary Architectures, and the idea is the single most important governance tool you can adopt, because it closes the feedback gap that lets architecture rot.
The distinction from a unit test is worth making sharp, because it's the key to recognizing what belongs in a fitness function. A unit test asks a domain question: given this input, does the function return the right output? A fitness function asks an architectural question: is the system still shaped the way we decided it should be? The clean test the Building Evolutionary Architectures authors offer: does the check require domain knowledge? "Does the tax calculation return the correct amount?" needs domain knowledge — it's a unit test. "Does the presentation layer avoid importing directly from the persistence layer?" needs no domain knowledge at all — it's a fitness function. The latter governs structure, and structure is what drifts.
The reason fitness functions matter so much is that they convert architectural principles from aspirations into enforced rules with immediate feedback. Take the modular monolith boundary from this chapter's opening. As a convention, it relies on every developer remembering it, understanding why it matters, and honoring it under deadline pressure — and we saw how that erodes. As a fitness function — an automated test in CI that scans for imports crossing module boundaries and fails the build when one appears — it becomes a wall instead of a wish. The boundary-crossing import doesn't merge. The developer gets a red X the moment they try, with a message explaining the rule, exactly when they can still easily choose a different approach. The erosion is caught at the instant it happens, by a machine that never gets tired or rushed, instead of in a code review six months late or never. That's the feedback loop architecture always lacked, finally present.
The range of properties you can govern this way is wide, and recognizing the categories helps you spot opportunities:
Structural — no circular dependencies between modules, no layer-skipping, no forbidden imports across boundaries. Tools exist for this in most ecosystems (ArchUnit in Java, dependency-cruiser and ESLint boundary rules in the JS/TS world, Deptrac in PHP/Laravel for enforcing exactly the module boundaries from our example). These are the most common fitness functions and the most directly aimed at drift.
Performance and operational — a build-time or monitored check that a key endpoint stays under a latency threshold, that a critical path doesn't exceed a query count, that the app starts within a time budget. These guard the performance attributes from Chapter 1 against slow erosion.
Security and compliance — automated checks that no known-vulnerable dependency version is present (the kind of check that, run universally, would have caught the framework vulnerability behind some famous breaches), that secrets aren't committed, that PII-handling code meets a policy. Governance scales here precisely because it's automated across every project.
A few principles keep fitness functions healthy rather than hated. Make them objective — they must give an unambiguous pass/fail, which means the architectural property has to be defined measurably (the "if you can't measure it, you haven't defined it" rule from Chapter 1 applies directly). Run them continuously, in CI, on every change — a fitness function that runs occasionally protects you occasionally; the value is in catching drift at the moment it's introduced. And use them with judgment, not zealotry — the Building Evolutionary Architectures authors warn against architects building an elaborate, interlocking fortress of fitness functions that mostly frustrates developers. The goal is a focused set guarding the principles that genuinely matter, an executable checklist of the important-but-not-urgent rules that deadline pressure would otherwise erode — not a bureaucracy in code. A handful of well-chosen fitness functions guarding real boundaries is worth more than fifty pedantic ones that teach developers to resent the build.
Together, ADRs and fitness functions cover both halves of governance: the reasoning is recorded and discoverable, and the structural decisions are enforced automatically. But governing a system isn't about freezing it. It's about letting it change safely — which is the final idea.
Evolutionary architecture: changing on purpose
There's a failure mode that looks like good governance but isn't: freezing the architecture. A team so determined to prevent drift that every change is a fight, every deviation forbidden, the design treated as sacred and immutable. This fails because the one thing you know for certain about your system's requirements is that they will change — the business pivots, the load grows, the team scales, the right style at ten engineers stops being right at a hundred (exactly as Chapter 2 noted). An architecture that can't change doesn't stay good; it becomes a straitjacket the organization eventually rips off in a painful rewrite. Governance that prevents all change is just a slower road to the same decay.
The resolution is the idea of evolutionary architecture: a system designed and governed to change safely and deliberately, rather than either frozen or left to rot. The two tools from this chapter are exactly what make safe evolution possible, and seeing how completes the picture.
Fitness functions are what let you change boldly without breaking. This is the same role tests play for refactoring code: a strong test suite lets you restructure aggressively because the tests catch you if you break behavior. Fitness functions do this for architecture — when the structural rules are enforced automatically, you can make a large architectural change (extract that billing module into a service at last) and the fitness functions confirm you haven't violated the other principles in the process. The safety net enables the boldness. Without it, every architectural change is scary, so teams avoid them, so the architecture ossifies. With it, change is routine and safe, so the architecture stays fit for its actual, current purpose.
ADRs are what make evolution coherent over time. The superseding chain we described isn't just a record — it's what lets a system's architecture change repeatedly without becoming an incoherent pile of half-reversed decisions. Each evolution is reasoned, recorded, and connected to what came before, so the system's design has a traceable through-line rather than a confusing sediment of choices nobody remembers making. Evolution guided by recorded reasoning is direction; evolution without it is just drift wearing a nicer name.
Put the whole chapter together and governance is not the bureaucratic, change-suppressing thing its name might suggest. It's the opposite: the machinery that lets a system stay good while changing. ADRs preserve the reasoning so decisions are durable and evolution is coherent. Fitness functions provide the automated feedback so structure holds and bold change stays safe. The forces of drift — knowledge loss, local optimization, missing feedback — are real and relentless, and a system left ungoverned loses to them every time. A system with these two tools in place doesn't fight entropy through exhausting human vigilance; it builds the resistance in, so the architecture you so carefully designed in the previous five chapters is still recognizably itself years later — and has changed, on purpose, into what the system actually needs to be now.
Practice
The skill here is recognizing what's worth governing and turning a principle into an enforced, runnable check. The exercises build toward a real fitness function for your system. Budget about two hours.
Exercise 1: Find the drift (20 min)
Pick a system you work on and find one place where the architecture has drifted from its intent, or is at risk of it — a boundary that's been crossed, a layer that gets skipped, a "we agreed not to do this" that's started happening. Write two or three sentences on what the intended rule was, how it's eroding, and what's caused the erosion (knowledge loss, deadline pressure, no feedback?). The goal is to see drift as a concrete, nameable thing rather than a vague sense that "the codebase is getting messy."
Exercise 2: Design a fitness function (45 min)
Take the drift from Exercise 1 and design a fitness function to govern it. State the architectural property precisely enough to be objectively checkable (if you can't, that's the first problem to solve). Then sketch how you'd enforce it: what tool or check (a dependency-rule tool like Deptrac for a PHP boundary, an ESLint boundary rule for a JS one, a latency assertion for a performance property), where it runs (CI, ideally), and what happens when it fails (build fails, with a message explaining the rule). Write a sentence on how this changes the developer's experience versus relying on memory and code review.
If you use an AI assistant to draft the actual check, interrogate it on false positives and the failure message — a fitness function that fires on legitimate code, or fails with a cryptic error, will get disabled by a frustrated team within a week, which is worse than not having it. The check has to be precise and its failure has to be self-explanatory.
Exercise 3: Write the governing ADR (45 min)
Write an ADR (the Chapter 1 format) that records the decision to govern this property and why — the context being the drift you found, the decision being the rule plus its fitness function, the consequences honestly including the cost (build time, occasional false positives, the discipline of maintaining the check). This closes the loop: the ADR preserves the reasoning, the fitness function enforces it, and together they're governance in miniature for one real property.
Deliverable
A one-page governance proposal for one real architectural property: the property and why it matters, the fitness function that would enforce it (tool, where it runs, failure behavior), and a short ADR recording the decision and its costs. A reviewer will read it in about fifteen minutes and respond to one question: is this property defined objectively enough to enforce automatically, and is the check precise enough that the team won't disable it in frustration?
Check yourself
Before you consider this chapter done, you should be able to:
- Name the three forces that cause architecture to drift, and what each one erodes.
- Explain the governance role of ADRs beyond their format — discoverability, reviewability, and traceable evolution via superseding.
- Define a fitness function and distinguish it from a unit test using the "does it need domain knowledge?" test.
- Give an example of a structural fitness function and explain how it changes the feedback loop versus code review.
- Explain why governance is about enabling safe change, not freezing the architecture — and how ADRs and fitness functions make evolution coherent and bold change safe.
If the unit-test-vs-fitness-function line feels fuzzy, redo Exercise 2 — turning one real principle into a runnable check makes the distinction concrete.
What a strong answer demonstrates: a strong governance proposal defines the property objectively enough to enforce automatically (the "if you can't measure it, you haven't defined it" rule) and designs the check so the team won't disable it in frustration — precise, with a self-explanatory failure message. The clearest signal you've understood the chapter is that you frame governance as enabling safe change rather than freezing the design: the ADR preserves the reasoning, the fitness function makes the bold change safe, and you can name the check's honest costs (build time, false positives, maintenance).
Going deeper (optional)
- Building Evolutionary Architectures (Ford, Parsons, Kua; O'Reilly, 2017) — the canonical source of the fitness-function concept and the definitive treatment of governing architecture for change. If this chapter resonated, this is the direct deep dive. (A 2nd edition, 2023, adds Pramod Sadalage.)
- Fundamentals of Software Architecture (Richards & Ford), the chapters on measuring and governing architecture characteristics and on architecture decisions — for fitness functions and ADRs in more depth, from the same authors.
- Software Architecture: The Hard Parts — its running use of ADRs and "fitness functions for governance" sections show the tools applied to concrete distributed-architecture decisions throughout, a good model for what governing real decisions looks like.
Primary sources (the foundational statements beneath what governance fights, if you want the theory on-ramp):
- Michael Nygard, "Documenting Architecture Decisions" (blog post, 2011) — the original ADR proposal; the source of the format that does the institutional-memory half of governance.
- Melvin E. Conway, "How Do Committees Invent?" (Datamation, April 1968) — Conway's Law again, here as the deepest reason structure drifts toward the org chart and why governing the seams matters.
- David L. Parnas, "On the Criteria To Be Used in Decomposing Systems into Modules" (Communications of the ACM, December 1972) — the seminal statement of the module boundaries that a structural fitness function exists to protect.
None of this is required to continue.
Key takeaways
- A well-designed system drifts without governance, eroded by knowledge loss, local optimization under pressure, and the historical absence of feedback on architectural violations.
- Governance builds resistance to drift into the system rather than relying on human vigilance — and it has two halves: recording decisions and enforcing them.
- ADRs are institutional memory: discoverable reasoning, decisions reviewed as decisions, and — via superseding — a traceable history that makes evolution coherent.
- Fitness functions are automated tests for architectural properties (structural, performance, security), distinguished from unit tests by needing no domain knowledge. They close the feedback gap, catching drift in CI the moment it happens.
- Governance is about enabling safe, deliberate change, not freezing the architecture. Fitness functions make bold change safe; ADRs make evolution coherent. Together they keep a system good while it changes.
This completes the Foundations track. You now have the core vocabulary and reasoning of software architecture: trade-offs, styles, data, reliability, communication, and governance. The stack-specific tracks (JS/TS and PHP/Laravel) apply these foundations in a concrete technology, and the synthesis and capstone bring everything together by building one real feature two different ways. Wherever you go next, the move is the same one you've practiced in every chapter: name the trade-off, fit it to your context, and write down why.