One dependency rule and the database becomes a detail again: the blueprint for your code.
Why this book
Clean Code looks at the line and the function; Clean Architecture climbs one floor up and looks at the building plan: who is allowed to depend on whom. Same author, same series, the logical sequel. My Clean Code notes already pointed here.
It is also the book that crystallized a whole family of architectures (hexagonal, onion, BCE) into one diagram and one rule. Whether you love or fight the four concentric circles, they came from here.
The ideas that stick
Thirty-four short chapters, one thesis. Here is what actually stays.
1The spiral of ever-more-expensive code
Every project starts fast, then each feature costs a little more than the last, until the team is wading through mud. Martin opens the book with the curves of a real company (anonymized) across eight releases:
- the cost per line of code in release 8 is 40 times that of release 1;
- the monthly payroll reaches 20 million dollars... and buys almost nothing;
- headcount explodes while productivity per developer creeps toward zero.
The lie everyone tells themselves, quoted as is: "We can clean it up later; we just have to get to market first!" (ch. 1). Except that "later" never comes: once you are on the market, there is a next deadline, a competitor to catch up with, a feature the customers demand. The pressure that justified dirtying the code yesterday justifies not cleaning it today, and so on all the way to the standstill. Hence the definition Martin builds the book on: "The goal of software architecture is to minimize the human resources required to build and maintain the required system" (ch. 1). Not elegance, not diagrams: the bill.
Chapter 2 names the trade-off: software has two values, behavior (urgent) and structure (important), and teams keep promoting the urgent over the important. Exactly backwards, says Martin.
2Three paradigms, three prohibitions
All three programming paradigms were discovered between 1958 and 1968. None since. And Martin defines them backwards from the usual brochure: each one removes a power from the programmer, none adds one.
- Structured (Dijkstra, 1968) removes
goto, the instruction that jumped anywhere in the program. Without it, code reads top to bottom: yourif,whileandforare what is left once the wild jump is gone; - Object-oriented (Dahl & Nygaard, 1966) removes wild indirect jumps: you no longer call "whatever code's address is lying around", you call a method, and the language guarantees it exists. Polymorphism, tamed;
- Functional (Church, 1936) removes assignment: once created, a value never changes again. And if nothing changes, whole families of bugs (shared state, concurrency) vanish with it.
"Each of the paradigms removes capabilities from the programmer. None of them adds new capabilities" (ch. 3). What is left to take away? Nothing, Martin bets: there will be no fourth paradigm. The practical lesson: a paradigm is a constraint you accept because it makes code predictable, not one more gadget.
Half of it. Since the book, at least two ideas have followed the exact same "remove to make predictable" pattern:
Rust and ownership: you lose the right to have two places in the code mutating the same data at the same time. The compiler rejects the program, and a whole family of concurrency bugs disappears.
Structured concurrency: you lose the right to launch a background task that outlives the block that created it. A famous 2018 essay is even titled "Go statement considered harmful", echoing Dijkstra's anti-goto paper: the wild spawn is the goto of concurrency.
Martin was talking about sequential code, and there his inventory is probably complete. But his mechanics of progress (remove, don't add) keeps producing: right on the principle, wrong on the prophecy.
3The SRP is not what you think it is
Two acronyms to lay down first. SOLID stands for the five object-oriented design principles popularized by Martin himself: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion. The SRP (Single Responsibility Principle) is the first of the five, the most quoted, and the most distorted.
Martin says it himself: "a function should do one thing" is a different, lower-level principle, and it is NOT the SRP. The real definition: "A module should be responsible to one, and only one, actor" (ch. 7). An actor is a group of people who request the same kind of changes.
The book's example: an Employee class with three methods.
calculatePay()answers to the CFO's people;reportHours()answers to the COO's people;save()answers to the DBAs.
Three actors in one class. The day finance asks for a tweak in a shared hours calculation, the operations reports silently go wrong; the book has the COO livid over millions lost to bad data (ch. 7). Splitting by actor means decoupling the angers.
The bad and the good split, before and after:
// ✗ BEFORE: one module, three bosses: their requests step on each other
class Employee {
name
hourlyRate
hoursWorked
calculatePay() // finance
reportHours() // operations
save() // the DBAs
regularHours() // private, shared by the first two: the ch. 7 bomb
}
// ✓ AFTER: one module per boss, shared data set apart (ch. 7)
// the data alone, zero logic: just the employee's record
class EmployeeData {
name
hourlyRate
hoursWorked
}
class PayCalculator { calculatePay(EmployeeData d) } // finance
class HourReporter { reportHours(EmployeeData d) } // operations
class EmployeeSaver { save(EmployeeData d) } // the DBAs
Look closely at what moved between before and after: not the data, the logic. The employee's record (name, hourly rate, hours) stays whole inside EmployeeData, a passive structure all three modules read on their own. The three methods, that is the three reasons to change, each moved in with their boss. Finance's request now only touches PayCalculator: the operations reports can no longer break silently, the code that produces them has not moved. And regularHours(), the shared calculation? Each module keeps its own version. Martin owns the duplication: two calculations that look alike today but will change for different reasons tomorrow are not real duplication. So the splitting criterion is not "what does what" but "who will ask to change it".
Almost, but not yet. PayCalculator receives EmployeeData as a parameter, uses it for the duration of one calculation, and hands it back: that is a plain usage dependency, the weakest link between two classes. Composition is one notch up: an object that durably contains another as a building block. It shows up in the next step of the book's example: an EmployeeFacade that holds the three modules and delegates, to keep a single entry point.
The underlying move, though, is the same as "favor composition over inheritance" from Design Patterns: replace the one class that does everything with small dedicated parts you assemble. The SRP says where to cut (one part per chain of command), composition says how to put the parts back together.
4OO's real superpower: turning the arrows around
Martin dismantles the classic definition of OO. Encapsulation? C did it perfectly with .h/.c files, and C++ actually damaged it by exposing private members in headers. Inheritance? A struct trick C programmers already simulated by hand. What is left: "OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system" (ch. 5).
Concretely: without an interface, business code calls the database, so it depends on it; the import follows the call. With an interface, business code declares "I need something that knows how to save", and the database implements it. The call still flows from business to database at runtime, but the source-code dependency now points from the database toward the business rules. The arrow has flipped. The enormous consequence: the UI and the database become plugins to the business rules, swappable like printers.
That is exactly chapter 5's answer. You do not need objects to flip the arrow: C programmers already did it with function pointers ("this slot will be filled by that function, wired by hand at startup"). But it was handcrafted and dangerous: nothing checked that the slot was filled, or filled with a function of the right shape. One oversight, and the program jumped to a rotten address at runtime.
OO's contribution fits in one line: making that flip safe, mundane and compiler-checked. An interface is a tamed function pointer: the implementation exists, the signatures match, the compiler guarantees it before anything runs. What demanded heroic discipline in C becomes an everyday tool. OO invented nothing; it made inversion practical at the scale of a whole system. And it loops back to idea 2: removing wild function pointers is precisely what OO removes.
Proof by Go, which has neither classes nor inheritance and flips the arrow better than anyone: any type with the right methods satisfies the interface, automatically, without declaring it. The business code defines its interface at home, and the database package does not even know it exists. Go threw away OO's packaging and kept the only piece that mattered according to Martin: the indirect call whose target the compiler guarantees.
5A good architect maximizes the decisions not made
The book's definition: architecture is the shape given to a system to ease its development, deployment, operation and maintenance; and the strategy is "to leave as many options open as possible, for as long as possible" (ch. 15). Hence the most famous line of the book: "A good architect maximizes the number of decisions not made" (ch. 15).
The lived proof: FitNesse, the testing wiki Martin built with his son in 2001. Instead of picking a database upfront, they wrote a WikiPage interface and stored pages in RAM, then in flat files. The result: "for 18 months, we did not have schema issues, query issues, database server issues, password issues, connection time issues" (ch. 17). The punchline: MySQL support was eventually written... by a customer, in a day, because the boundary was clean. Nobody else ever used it. The option was deleted. The postponed decision had become unnecessary.
6Four circles, one rule
The concentric-circles diagram became the most famous image of the book. Martin did not invent it from scratch: it synthesizes sibling architectures that were already saying the same thing. Alistair Cockburn's hexagonal architecture (2005, also called "ports & adapters"): the application core exposes standardized sockets, and each technology plugs into them like an appliance into a power strip. Ivar Jacobson's BCE (1992, for Boundary-Control-Entity): split every feature into three roles, what talks to the outside world, what orchestrates, what carries the business data. Same intuitions, different drawings; Martin merges them into four layers, inside out.
Take a car rental website to make each layer concrete:
- Entities: the business rules that would exist even without computers. "A driver must have held a license for 2 years", "a deposit is required above a certain price". The agency manager already enforced them with a paper notebook;
- Use cases: the rules specific to this application. "Book a vehicle" = check availability, compute the price, block the slot, trigger the email. The scenario, step by step, knowing nothing about the screen or the database;
- Interface adapters: the translators between inside and outside. The controller turning the HTTP request into a use-case call, the repository turning "block the slot" into an SQL query. All the project's SQL is confined here;
- Frameworks & drivers: Symfony, MySQL, the browser. Interchangeable technology, mostly glue.
And one law, the Dependency Rule: "Source code dependencies must point only inward, toward higher-level policies" (ch. 22). Concrete translation: a file may import files from its own circle or from circles further in, never the other way. The controller (adapter) may import the "Book a vehicle" use case; the use case may import the Driver entity; but the Driver entity imports nothing that comes from outside. And "know nothing" is meant strictly: not an import, not a class name, not even the word "MySQL" anywhere in the file.
The test fits in one line: open one of your entities and read its use statements at the top. If you see Symfony, Doctrine or Request in there, the rule is broken: your business rule is tied to a technology, and it will fall with it. No framework import in your entities. Ever.
Vocabulary collision: same word, two concepts. A Doctrine entity is a persistence model (columns, joins); in Martin's circles, it lives with the adapters. make:entity just installed the idea that it was the heart of the project. The purist version separates the two, example with the User we all have:
// 1. Domain: pure PHP, zero framework use. The rules live here.
final class User {
public function suspend(): void {
if ($this->hasActiveSubscription()) {
throw new CannotSuspendPayingUser(); // business rule
}
$this->status = Status::Suspended;
}
}
// 2. Infrastructure: the Doctrine uses live HERE
#[ORM\Entity]
class DoctrineUser { /* id, columns, joins */ }
class DoctrineUserRepository implements UserRepository {
public function save(User $u): void { /* translates User → DoctrineUser */ }
}
// 3. Security: Symfony's UserInterface is an adapter too
class SecurityUser implements UserInterface { /* wraps User */ }
The price is real: three classes and a translator where make:entity made one. When to pay it: when your User carries real rules (guest → verified → suspended lifecycle, permissions, quotas, billing) you want to test without starting a database. When to skip it: when User is just an email, a password and roles; there, the purist version is pure ceremony, keep one class and own the exception. The practical signal: the day a unit test of a business rule forces you to load Doctrine, that is the moment to split.
Chapter 21's test, the quickest architecture review you'll ever run: open your project's top-level folder. Does it scream "accounting"... or does it scream "Rails"? An architecture should tell readers what the system DOES, not what it is built with.
7The kitten problem (your microservices are coupled)
2017: everyone is slicing everything into microservices, and the sales pitch is always the same: independent services, separately deployable, therefore decoupled. Martin asks the awkward question: decoupled with respect to which change? That is the only test that matters.
His counterexample: an Uber-like taxi aggregator, neatly split into four services: the customer UI, the driver finder, the vehicle selector, the dispatcher. On paper, each lives its own life. Then marketing announces a new product: kitten delivery to your door. Walk through what it implies: customer allergies (UI), driver allergies (finder), animal-friendly vehicle criteria (selector), delivery rules (dispatcher). All four services must change together and be redeployed in a coordinated way. Against that change, the split decoupled nothing at all: "Services that simply separate application behaviors are little more than expensive function calls" (ch. 27). Expensive, because a function call got replaced by a network call: slower, and it can fail.
The lesson is not anti-services, it is anti-reflex:
- a service is only a real boundary if the likely changes stay inside it. The practical test: take marketing's next request and count how many services it crosses;
- the boundaries that actually protect you are the four circles (idea 6): business, use cases, adapters. And they must exist inside each service too: a good service is a small clean monolith, not one big file behind an API;
- a good architecture lets a system be born a monolith, grow into deployable units, then services, if the need ever comes (ch. 16). The network split is an option you keep open, not a starting point;
- and the jab, in a footnote: "the number of micro-services will be roughly equal to the number of programmers" (ch. 27). Meaning: teams too often split to mirror the org chart (one team, one service), not the structure of the problem.
8Everything is a detail, and discipline won't save you
The end of the book hammers three nails with the same hammer:
- the database is a detail: a disk technology, not the center of your system;
- the web is a detail: "The GUI is a detail. The web is a GUI. So the web is a detail" (ch. 31). The industry has been oscillating between fat client and fat server since the 1960s; your core business should not swing with the pendulum;
- the framework is an asymmetric marriage: you commit for life, its author commits to nothing. "Don't marry the framework!" (ch. 32): use Spring, but no
@Autowiredin your business objects.
The objection stings, and Martin only half answers it. His actual distinction: the DBMS is a detail, the data model is not. Event sourcing, for instance, passes his test: "our source of truth is the history of facts, not the current state" is a business decision, a high-level policy (chapter 6 even sings its praises). What stays a detail is the event store: EventStoreDB, Kafka or an append-only Postgres table, your use cases do not need to know.
The rule does have a limit. With some databases you are not just picking a technology, you are picking a way of thinking. Example: DynamoDB (Amazon's NoSQL database) is only fast if you arrange your data around the questions you will ask it. The whole data layout is built around those questions. A project designed that way will not "swap databases" by replacing a class behind an interface: the entire layout would need rethinking. The class is replaceable, the way of thinking is not.
Hence the honest version of the rule: you will probably never swap databases, and that is fine, it was never the real benefit. The real benefit is testing your business rules without starting MySQL, and keeping business code readable without knowing Doctrine. The big data choice (relational, events, graph) you own as an architecture decision. The brand of the database stays a detail.
Then chapter 34, written by a guest (Simon Brown), ends the book with a cold shower. His observation: you can draw the prettiest circles in the world, but if every class in the project is public, any developer in a hurry can short-circuit the plan. Example: the controller is supposed to go through the "Book" use case, which enforces the rules. But nothing prevents writing a direct call to the repository in the controller, skipping the rules. It compiles, it works, it ships. The architecture only existed on the drawing, and in everyone's goodwill.
His answer: ask the compiler to stand guard. In Java, a class declared without the public keyword is only visible inside its own folder. Declare the repository that way, and the controller cannot call it directly anymore: the forbidden shortcut does not compile, instead of being an oversight to catch in code review. "The devil is in the implementation details" (ch. 34). PHP has no native equivalent; you get close with a tool like Deptrac, which fails the CI whenever a layer imports what it should not.
Three things I didn't know
- The juiciest confession in the book (ch. 30): in the late 1980s Martin fought for years against bolting on a technically useless relational database. Customers wanted it as a checkbox. "They were absolutely right and I was wrong"... for marketing reasons, not technical ones. "What did I do? I quit and became a consultant."
- In 1957 the Dutch authorities refused "programmer" as a profession on Dijkstra's marriage certificate; he wrote down "theoretical physicist" instead (ch. 4).
- The autobiographical appendix hides a gem: a 3,000-line C function named
gi(), for Graphic Interpreter, in a startup where asking about architecture got you "Are you joking? This was a startup." (appendix A)
My take, honestly
I read this book the way you listen to an old professor who repeats himself a bit but has seen everything. Martin has been coding since 1964, and his big idea fits on a sticky note: dependencies point inward. The rest of the book is that sentence, retold with anecdotes. But what a sentence: every JavaScript framework that died since 2017 argues for it, and my own project history is a small cemetery of codebases married to their framework.
The fair criticism: it is verbose, preachy, and stingy with examples. The big case study is Hunt the Wumpus, a text game from 1972, and the only chapter with realistic modern code was written by a guest (Simon Brown). Worse, the four-circle diagram has become a religion: I have seen three-screen CRUD apps with twelve files to read one table. Applying Clean Architecture everywhere is missing the book, which literally says architecture exists to save effort.
My reading advice, concretely: the book is 400 pages, but the essence fits in about a hundred, the ones covering the Dependency Rule, the FitNesse story and the kitten problem. That hundred, I recommend to almost any dev with a few years of code behind them. The rest (the history lessons, the very Java-flavored chapters), skim without guilt, I won't tell anyone.
Odilon
Still relevant in 2026?
More than in 2017, for a reason Martin could not have planned: AI writes more and more of the code, but it does not decide where the code goes. A codebase where SQL lives in one ring and business rules in another is also a codebase an AI agent can modify without breaking everything: the boundaries Martin drew for humans now serve twice. What has aged: the Java-centric reflexes, the long detours through 1960s hardware, and the assumption that you control all your dependencies (the npm/supply-chain era says otherwise).
Who is it for?
Read it if
- You have 3+ years of code behind you and your projects slow down release after release
- Everyone yells "microservices" and you need ammunition to start with a clean monolith
- You want to know where hexagonal, onion and clean actually come from
- You liked Clean Code and want the floor above: structure instead of lines
Skip it if
- You expect a tutorial with code: there is almost none in it
- You are just starting out: read Clean Code first, this one assumes scar tissue
- Stories about PDP-8s and punched cards put you to sleep
Going further
Dependency direction is practiced hands-on in my OOP course (interfaces, composition, injection). In the library, Clean Code is the same author one floor down, A Philosophy of Software Design is the calm counter-voice (depth over rules), The Pragmatic Programmer shares the war on coupling, and Team Topologies picks up Conway's law where chapter 16 leaves it.
Comments (0)