The Principles of Clean Architecture by Uncle Bob Martin

The Principles of Clean Architecture by Uncle Bob Martin — NorfolkDevelopers

NorfolkDevelopers

AI-assisted summary of The Principles of Clean Architecture by Uncle Bob Martin — original source. Check the original for full context.

Source published:

Key ideas

  • Software architecture should scream its business domain rather than its delivery framework or tools, treating the web merely as an I/O delivery mechanism. (10:45)
  • Clean Architecture enforces an inward dependency rule where delivery mechanisms and databases act as plugins to core entities and interactors. (33:07)
  • Relational databases and SQL were engineering workarounds for spinning disk latency, an architectural constraint that evaporates with modern persistent RAM. (50:42)
  • A primary duty of a software architect is to maximize the number of decisions deferred, keeping frameworks and database choices at arm's length. (66:11)

Detailed notes

## Background Published on YouTube by NorfolkDevelopers on December 15, 2015, this presentation features industry veteran Robert C. "Uncle Bob" Martin delivering a lecture titled "The Principles of Clean Architecture." The video description highlights Martin's career dating back to 1970, his role in formulating Agile Software Development and the Software Craftsmanship Movement, and his authorship of foundational software engineering books such as Clean Code and The Clean Coder.

## TL;DR Uncle Bob Martin argues that high-level software architecture must boldly declare an application's business intent rather than advertising the web frameworks or databases used to build it. Drawing on Ivar Jacobson's use-case driven approach, Martin demonstrates how core business rules—modeled as invariant entities and application-specific interactors—should reside at the architectural center, insulated behind strict input and output boundaries. By treating the web, databases, and third-party frameworks as outer plugins whose dependencies point inward toward stable domain logic, engineering teams decouple from external volatility. This clean separation enables sub-second automated test execution, eliminates the fear of refactoring legacy code, and empowers architects to maximize the deferral of technical decisions.

## Atmosphere & style The presentation carries an energetic, theatrical, and irreverent keynote vibe, launching unexpectedly into mathematical ancestry calculations and evolutionary biology before pivoting into software engineering. Uncle Bob speaks with charismatic confidence and sharp comedic timing, peppering his critique of framework trends, disk hardware, and database administrators with expressive vocal shifts and deadpan humor. The atmosphere is provocative and earnest, transitioning from historical storytelling into an impassioned call for engineering craftsmanship, technical courage, and architectural discipline.

## Bulletin - [10:45](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=645s) Software architecture should scream its business domain rather than its delivery framework or tools, treating the web merely as an I/O delivery mechanism. - [33:07](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1987s) Clean Architecture enforces an inward dependency rule where delivery mechanisms and databases act as plugins to core entities and interactors. - [50:42](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3042s) Relational databases and SQL were engineering workarounds for spinning disk latency, an architectural constraint that evaporates with modern persistent RAM. - [01:06:11](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3971s) A primary duty of a software architect is to maximize the number of decisions deferred, keeping frameworks and database choices at arm's length.

## Key takeaways - [10:45](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=645s) Directory structures should reveal the system's business purpose rather than highlighting frameworks like Ruby on Rails or Spring. - [12:38](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=758s) The web is simply an I/O mechanism that displays output and captures input, not the fundamental foundation of software architecture. - [15:27](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=927s) Use cases originated by Ivar Jacobson describe business processes through input data, main courses, and alternative paths independent of UI or persistence technology. - [20:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1201s) Entities encapsulate enterprise-wide business rules, whereas interactors manage application-specific use cases and coordinate those entities. - [24:17](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1457s) Decoupling use cases with plain request and response models enables sub-second automated unit testing without running web servers or databases. - [27:14](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1634s) Trygve Reenskaug originally designed Smalltalk MVC for individual GUI widgets rather than full web pages, leading to modern architectural leakage in web frameworks. - [36:13](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2173s) Source code dependencies must cross architectural boundaries inward, ensuring stable business rules remain insulated from volatile external delivery plugins. - [37:56](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2276s) The presenter and view model pattern removes formatting logic from views, making views too simple to require automated testing. - [48:12](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2892s) Relational databases and B-tree indexes were historically engineered to compensate for the severe mechanical seek times and latency of spinning magnetic disks. - [50:42](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3042s) The transition to large addressable persistent RAM eliminates the need to map data structures into relational rows and tables. - [54:28](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3268s) The Entity Gateway pattern insulates core business logic from databases by providing abstract interfaces, treating persistent storage as an interchangeable plugin. - [56:31](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3391s) Test suites must execute in seconds to serve as a practical safety net, allowing developers to fearlessly refactor legacy code without breaking behavior. - [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) FitNesse operated successfully for months using in-memory mock storage and flat files, demonstrating the advantage of deferring database implementation decisions. - [01:06:11](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3971s) A good software architecture maximizes deferred decisions, delaying commitments to databases, message queues, and UI frameworks until sufficient information is available. - [01:08:19](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4099s) Applications must maintain an arm's-length relationship with third-party frameworks, as framework authors have no commitment to preserve backward compatibility. - [01:12:22](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4342s) Domain entities represent synthesized business behaviors across data sources rather than direct 1:1 representations of database tables.

## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1s) The Mathematics of Ancestry and Population Collapse

Uncle Bob Martin begins his talk with a seemingly non-technical puzzle directed at the audience: how many biological parents does each person have? Naturally, the answer is two—even in Norfolk. Going back another step, everyone has four grandparents, eight great-grandparents, and sixteen great-great-grandparents, assuming cousins have not lived close together for generations. Programmers readily recognize this progression as simple powers of two. However, extrapolating this binary progression into the past quickly leads to a physical impossibility.

Ten generations back, roughly 200 years, the formula $2^{10}$ yields 1,024 potential ancestors. Jumping back 400 years to 1615 gives roughly one million ancestors. Going back 600 years to 1415 produces one billion possible ancestors, which was roughly the entire population of the planet at the time. Taking another step back to 1215, the year the Magna Carta was signed, the calculation yields one trillion potential ancestors—far exceeding the total number of human beings who have ever lived on Earth, including all the dead. Martin points out that pedigree collapse and intermarriage flatten this geometric curve:

> “So this power of two thing doesn't work it only works in the first couple of generations and then it sort of curves around you can imagine this this growth curve of your possible ancestors and then the real ancestors kind of curve around and then another effect takes place.”

Because every genealogical line has a non-zero probability of eventually dying out, tracing family lines back far enough inevitably prunes away every branch except one. Mathematically, this guarantees that everyone in the room—and eventually everyone on the planet—shares a common ancestral mother and father.

- Generational scaling: 2 parents $\rightarrow$ 4 grandparents $\rightarrow$ 8 great-grandparents $\rightarrow$ 16 great-great-grandparents. - 200 years ago (10 generations): 1,024 theoretical ancestors ($2^{10}$). - 400 years ago (1615): $1,000,000$ theoretical ancestors ($2^{20}$). - 600 years ago (1415): $1,000,000,000$ theoretical ancestors ($2^{30}$). - 800 years ago (1215): $1,000,000,000,000$ theoretical ancestors ($2^{40}$).

---

[04:39](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=279s) Mitochondrial Eve and Maternal Lineage

While most human nuclear DNA undergoes cross-over recombination that blends maternal and paternal genes beyond easy recognition, certain molecules remain relatively undisturbed across generations. Within the cytoplasm of every cell sits the mitochondrion—jokingly clarified as “not the midi-chlorian”—a hot-dog-shaped organelle responsible for cellular respiration, consuming glucose and oxygen to release water, carbon dioxide, and cellular energy. Roughly 1.5 billion years ago, an ancestral single-celled organism swallowed another without digesting it, establishing an endosymbiotic relationship that gave rise to complex eukaryotic life.

Because egg cells contain mitochondria while sperm cells do not, mitochondrial DNA is passed down purely along the maternal line. Except for rare, benign point mutations—such as a cosmic ray flipping a single nucleotide without breaking the degenerate genetic code—a person's mitochondrial DNA is identical to their mother's and maternal grandmother's. By counting the genetic distance between populations, geneticists have mapped human lineage back to Mitochondrial Eve:

> “They didn't know each other probably they probably didn't live within a few hundred miles of each other they very likely did not live within a few thousand years of each other but these two individuals are the mother and father of everyone alive and this is a mathematical certainty.”

Mitochondrial Eve lived roughly 120,000 years ago in East Africa, an origin identified by the region's immense genetic diversity compared to isolated populations like Iceland, where mitochondrial DNA is virtually identical. Following her lifetime, during a warm era before the onset of the last Ice Age, her descendants branched out across the globe: one cohort moved into Western Europe, another journeyed through Eastern Europe and Russia into the steppes, a third skirted the southern coasts into Australia, and a fourth crossed the Bering Strait into North America around 14,000 years ago, reaching the southern tip of South America only 1,000 years later.

```mermaid flowchart TD accTitle: Mitochondrial Eve Dispersal Map accDescr: Flowchart illustrating the geographic spread of human populations originating from Mitochondrial Eve in East Africa.

subgraph Origin["East Africa (c. 120,000 Years Ago)"] Eve["Mitochondrial Eve"] end

subgraph Migrations["Global Migration Cohorts"] WestEur["North to Western Europe"] EastEur["North to Eastern Europe & Russia"] Oceania["Southern Coastal Route to Australia"] Americas["Bering Strait (14k ya) to South America"] end

Eve --> WestEur Eve --> EastEur Eve --> Oceania Eve --> Americas ```

---

[09:30](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=570s) Y-Chromosomal Adam and Human Migration

Just as women pass down mitochondrial DNA, men carry a stretch of DNA strictly along the paternal line: the Y chromosome. Free from maternal recombination, it represents an unbroken chain of paternal inheritance stretching back to Y-chromosomal Adam. He lived approximately 70,000 years ago in East Africa during the peak of the last Ice Age—thousands of years after Mitochondrial Eve lived.

Although their lifetimes were separated by tens of thousands of years, tracking the paternal lineages of his sons reveals migration routes across the continents that closely mirror the maternal dispersal patterns. Martin notes how deeply fascinating it is that the entire history and journey of the human species are physically encoded within the microscopic molecules of the body. With that foundation established, he turns away from evolutionary genetics to address the actual focus of the evening: software architecture.

- **Mitochondrial Eve**: Estimated to have lived ~120,000 years ago in East Africa. - **Y-chromosomal Adam**: Estimated to have lived ~70,000 years ago in East Africa during the height of the last Ice Age. - Paternal line acts as an unmixed, single-inheritance hierarchy passed down father to son.

---

[10:45](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=645s) The Structure of Rails Apps and the Architecture Problem

Martin pivots to software design by recalling an experience from roughly eight years earlier, when his son showed him a newly built Ruby on Rails application. Looking at the project's file tree, Martin instantly saw directories named `app/`, `controllers/`, `models/`, and `views/`. While any Rails developer immediately identifies that layout, the revelation struck Martin as fundamentally flawed. A top-level directory structure should announce the business domain and intent of the application, yet modern frameworks instead foreground themselves and obscure what the system actually does:

> “Why does the high level directory structure of this application tell me the framework that I'm using why doesn't it tell me what the application does?”

Martin asks the audience how many of them build systems with Ruby on Rails or similar web frameworks. He emphasizes that Rails, like any other web framework, is fundamentally a mechanism for delivering user interaction across the web. However, in the grand scheme of software systems, the web is merely an I/O mechanism. Elevating a delivery mechanism to the dominant organizing principle of an application distorts the architecture and buries the true business purpose beneath delivery-mechanism details.

> “Why would the web be considered something so significant that it would overshadow what the application does now?”

[12:38](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=758s) The Fallacy of the Web as Architecture

When the World Wide Web emerged in the late 1990s, it created an overwhelming sensation that the rules of software development had fundamentally shifted. Uncle Bob Martin recalls the headiness of the dot-com boom, where companies believed they could make trillions simply by creating things like a "Save a Pet website"—until the bubble burst, fortunes vanished, and programmers were left literally holding signs asking to code for food. Looking back across the decades, Uncle Bob notes that the industry took almost twenty years to realize a basic truth: the web did not fundamentally change software architecture. At its core, the web is merely an I/O device designed to take user input and project output onto a screen using HTML.

Despite its mundane nature as an I/O channel, web frameworks seized an outsized, architecturally dominant role. Developers began defining their software not by its purpose, but by the tools used to run it. Uncle Bob mocks the habit of calling something a "Rails app" or describing an architecture as "Java, Spring, and Hibernate." As Uncle Bob Martin insists:

> “That’s not an architecture, that’s a bunch of tools.”

If you are writing accounting software, you are building an accounting application—you merely happen to be delivering it through Rails or Spring.

To illustrate what architecture truly represents, Uncle Bob compares software to architectural blueprints. When examining a blueprint of a library, its identity is self-evident: it features bookshelves, desks, computer areas, and study tables. Similarly, a blueprint of a church immediately communicates that it is a church. Architecture is fundamentally about intent, declaring what the system actually does, whereas delivery mechanisms, frameworks, and databases are low-level implementation details that should remain hidden rather than defining the top-level structure.

- Architecture communicates the business intent of the system, not the delivery mechanisms or third-party frameworks. - Frameworks like Ruby on Rails or Spring, as well as libraries like Hibernate, are implementation tools and should not be confused with software architecture. - Describing a project as a "Rails app" obscures the actual domain, such as an accounting system or inventory management system.

---

[15:27](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=927s) Ivar Jacobson and the Lost Simplicity of Use Cases

The realization that architecture is about intent mirrors principles laid out decades earlier by Ivar Jacobson. In 1992, Jacobson published his seminal work, *Object-Oriented Software Engineering: A Use Case Driven Approach* (Uncle Bob quips that Jacobson's employees wrote it while Jacobson took the credit). When the book launched at a conference, developers lined up at the bookstore in droves, recognizing it as an immediate classic.

```mermaid flowchart TD accTitle: Evolution of Use Cases to User Stories accDescr: Diagram showing how simple Jacobson use cases became bloated by consultants before pivoting into Agile user stories.

subgraph JacobsonOrigin["1992: Original Concept"] A["Jacobson Use Cases<br>(Simple, goal-driven)"] end

subgraph ConsultantBloat["The Consultant Era"] B["PostScript / PDF Forms"] C["Actor Overload<br>(Primary, secondary, tertiary)"] D["Exhaustive Pre/Post-conditions"] A --> B --> C --> D end

subgraph AgilePivot["Agile Revolution"] E["Agile User Stories<br>(Identical concept, no bloated forms)"] D -->|"Rejection of bureaucracy"| E end ```

However, the industry's enthusiasm was soon corrupted by what Uncle Bob calls the "invasion of the consultants." To outdo their predecessors, consultants began publishing increasingly rigid, complex forms—originally distributed as PostScript files and PDFs on the nascent web. These templates forced developers into administrative nightmares, requiring explicit listings of primary, secondary, and tertiary actors, along with exhaustive preconditions and postconditions. This bureaucratic bloat alienated software teams to the point of exhaustion. When the Agile movement later arrived advocating "user stories," the industry embraced them enthusiastically, largely failing to recognize that user stories and use cases are fundamentally the exact same thing; user stories simply stripped away the suffocating procedural forms.

- Ivar Jacobson's 1992 book *Object-Oriented Software Engineering: A Use Case Driven Approach* introduced use cases to structure systems around user goals. - Escalating consulting templates introduced administrative bloat (primary, secondary, and tertiary actors, preconditions, postconditions) that made use cases cumbersome. - Agile user stories addressed the exact same conceptual need as use cases while discarding bureaucratic form templates.

---

[17:49](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1069s) Anatomy of a Use Case

Returning to Jacobson's original formulation, Uncle Bob highlights that a use case is meant to be simple, abstract, and independent of technical mechanisms. Looking at a concrete example—the `Create Order` use case within an order entry system—the specification describes the interaction without binding itself to data schemas, delivery channels, or storage engines:

1. **Input Data**: Captures elements like `Customer ID`, `Shipment Destination`, and `Payment Information` without specifying physical structure, data formats, or whether the payload originated from a web form or another source. 2. **Primary Course**: Describes the normal execution flow through concise processing steps: - The order clerk issues the `Create Order` command with the required data (a manual step triggering the flow). - The system validates all data (deferring specific validation criteria for later). - The system creates the order and determines the order ID (an operation that could involve a database, though the use case does not state this). - The system delivers the order ID back to the clerk (likely on a screen or web page, left uncoupled from the use case itself). 3. **Alternate/Error Courses**: Handles failures and edge conditions, which typically amount to only one or two exceptional paths.

Uncle Bob points out how deliberately agnostic this specification is: it never mentions HTML, HTTP, SQL, or relational tables. It solely outlines the business workflow.

---

[20:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1201s) Objectifying Use Cases: Interactors, Entities, and Boundaries

The central insight of Jacobson's work was translating these procedural use cases directly into object-oriented structures. Because a use case accepts input, manipulates data, and emits an output, it can be modeled directly as an object. Jacobson originally referred to these as "control objects." Uncle Bob chose the term **Interactors** to avoid confusion with the Controller in Model-View-Controller (MVC), though he acknowledges that calling them "use case objects" might have been simpler:

> “The interactor object contains application-specific business rules.”

Uncle Bob differentiates business rules into two distinct categories:

- **Application-Independent Business Rules (Entities)**: Rules that remain invariant across multiple systems within an enterprise (often called "business objects"). For instance, the calculation of an order's total price applies across order entry, fulfillment, and billing. Jacobson called these *Entities*. An interactor coordinates several entities to achieve a goal, such as an `Order`, a `Line Item`, and a `Customer`. - **Application-Specific Business Rules (Interactors)**: Rules that belong strictly to a single application and define its specific use cases. Creating an order belongs uniquely to the order entry system; other systems cannot invoke that specific workflow.

```mermaid flowchart LR accTitle: Interactor, Entities, and Boundaries Flow accDescr: Structural diagram showing how delivery mechanisms communicate with interactors and entities across boundary interfaces.

subgraph Delivery["Delivery Mechanism"] User(["User"]) --> UI["Delivery Component<br>(Web / Console / SOA)"] end

subgraph CleanCore["Application Core"] direction TB InBound["«interface»<br>Input Boundary"] OutBound["«interface»<br>Output Boundary"]

Interactor["Interactor<br>(Application-Specific Rules)"]

subgraph EnterpriseRules["Domain Entities"] Order["Order"] Customer["Customer"] LineItem["Line Item"] end

InBound <|.. Interactor Interactor..|> OutBound Interactor --> Order Interactor --> Customer Interactor --> LineItem end

UI -->|"calls"| InBound OutBound -->|"notifies / passes data"| UI ```

To isolate the Interactor from external concerns, Jacobson introduced **Boundary Objects** (originally called *Interface Objects*), which Uncle Bob models as abstract interfaces:

- **Input Boundary**: An interface implemented by the Interactor through which input data enters. - **Output Boundary**: An interface implemented by an external component (such as a presenter or UI component) through which outgoing results leave the Interactor. - **Delivery Mechanism**: The outermost layer (console, thick client, web application, or service-oriented endpoint) that interacts with external users and systems, remaining entirely decoupled from the core business rules.

[24:17](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1457s) Boundaries, Interactors, and Testability

Uncle Bob outlines the end-to-end data flow across an architectural boundary. When a user interacts with the system—such as filling out a web form and clicking the submit button—the delivery mechanism takes whatever data was generated in that moment and packages it into a data structure called a request model. He emphasizes that while this might look superficially like an HTTP request to developers accustomed to web work, it must remain strictly untainted by web concepts:

> “I don't want the words HTTP in there at all I don't want any I don't want it to derive from some Base Class which is tied to the web I don't want any field in it that have any webbish kind of knowledge to it in fact what I want this data structure to be is a plain old Java object or a plain old.NET object or whatever language you're using with a bunch of Primitives in it and no dependencies at all on any framework...”

This framework-agnostic request model passes directly into the input boundary. Because the interactor derives from or implements this input boundary, it accepts the request model, decodes it (for example, recognizing a "create order" request), extracts the primitive data, and uses that data to “control the dance of the entities.” The interactor instructs the entities to create an order, attach line items, and associate the customer. Once those tasks finish, the interactor orchestrates the entities in reverse, gathering up the computed results into another decoupled data structure: the result model (or response model). Finally, the result model passes back out through the output boundary to the delivery mechanism, which renders or sends the response to the user.

```mermaid flowchart LR accTitle: Request and Response Flow Through Clean Architecture Boundaries accDescr: Data flows from the delivery mechanism into an input boundary, is processed by an interactor and entities, and returns via an output boundary.

subgraph Delivery["Delivery Mechanism (Web)"] UI["User Form / Submit"] Req["Request Model (POJO)"] Res["Result Model (POJO)"] OutPresenter["Delivery / Presenter"] end

subgraph Application["Application Business Rules"] IBound["Input Boundary"] Interactor["Interactor / Use Case"] OBound["Output Boundary"] end

subgraph Enterprise["Enterprise Business Rules"] Entities["Entities"] end

UI --> Req Req --> IBound IBound --> Interactor Interactor <-->|"orchestrates dance"| Entities Interactor --> Res Res --> OBound OBound --> OutPresenter ```

Uncle Bob stresses that this structure unlocks pure, effortless testability. An interactor can be tested in complete isolation simply by handing it a request model and asserting on the resulting response model. There is no need for a web server, an application server, or even a database to be running. He argues that this isolation is an absolute prerequisite for testing business rules:

> “...when you're writing tests especially tests of your use cases you do not want to have the whole system running especially any part of the system that is slow or any part of the system that makes it difficult to communicate with the business rules like a web server does you don't want to have any of that stuff running you'd like to just talk right to the business rules...”

---

[27:14](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1634s) Meeting Trygve Reenskaug and the Origins of Smalltalk MVC

Anticipating an audience objection—asking how this boundary-driven model relates to Model-View-Controller (MVC), the standard paradigm for web applications—Uncle Bob takes a detour into history. He introduces Trygve Reenskaug, whom he credits with inventing MVC in the 1970s for Smalltalk, remarking that MVC might be the earliest named design pattern before the term "design pattern" was even coined.

He shares a humorous personal anecdote about meeting Reenskaug in the speaker lounge at a conference in Norway. Uncle Bob was on the floor searching for an outlet to plug in his laptop when an older gentleman silently reached over and handed him a power strip. True to programmer form, Uncle Bob took it without looking up:

> “...programmers don't make eye contact right so I I took the power strip from and then I looked up and oh it's Trygve Reenskaug and as I took it you know our fingers just barely touched I haven't washed that hand...”

Martin then breaks down what Reenskaug’s original MVC actually was: - **Model**: Encapsulates a very small, specific business rule. The model is strictly forbidden from knowing what input devices control it or where its data originates. - **Controller**: Captures hardware gestures from input devices (keyboard, mouse) and translates those raw gestures into method calls against the model. - **View**: Observes the model. Uncle Bob draws attention to an observer relationship where the view registers with the model, and the model notifies the view whenever its internal state changes so the view can re-render the data to the user.

Crucially, Uncle Bob explains that in Smalltalk, MVC was applied *in the small*. You had a distinct triad for an individual button, another for a single text box, and another for a radio button group. It was never intended to represent an entire web page.

---

[30:25](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1825s) The Corruption of Web MVC and the Discipline Deficit

Uncle Bob explains how the software industry routinely corrupts good ideas: developers recognize that a concept is sound, conclude that their own new idea is also sound, assume both ideas must be the same thing, and slap the original label onto their new construct. This is how the web inherited a distorted version of MVC applied to entire pages:

```mermaid flowchart TD accTitle: Typical Web MVC Flaws accDescr: In typical Web MVC, controllers invoke business models, which hand off to views, but code bleeds across missing boundaries.

subgraph WebBoundary["Delivery (Web / HTTP)"] Req["HTTP GET/POST / URL"] C["Controller"] V["View"] end

subgraph ModelArea["Model Area"] BO["Business Objects"] end

Req --> C C -->|"unpacks parameters & calls"| BO C -->|"passes control"| V V -->|"queries data from"| BO

BO -.->|"leaks controller logic into"| BO BO -.->|"leaks business logic into"| V ```

In typical web MVC, an incoming HTTP GET or POST URL triggers a controller. The controller unpacks string parameters and invokes business objects located in the "model." Afterward, control passes to the view, which queries those same business objects and outputs HTML.

The fatal flaw with this setup is the complete absence of firm architectural boundaries: - Controller logic bleeds directly into the business objects when programmers under deadline pressure scramble to make things work. - Business logic leaks into views, a pattern anyone working in web applications has seen repeatedly.

Uncle Bob connects this structural degradation directly to a lack of professional discipline across the software industry:

> “...one of the problems we have as an industry is that we're not very good at keeping discipline I mean hell half of us don't write tests and that's unbelievable right can you imagine telling your users you know I don't write tests I just write the code and sometimes it even works and I ship it to you and if bugs well you're going to tell me aren't you that's our attitude...”

---

[33:07](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1987s) The Double Black Line and the Web as a Plugin

Turning back to Ivar Jacobson’s architecture, Uncle Bob highlights the "double black line." He clarifies that he is not speaking of an individual boundary class or boundary object, but a hard, dividing architectural line separating enterprise business rules from external delivery mechanisms like the web.

Across this boundary, all dependencies point inwards toward the business rules:

> “This is the fundamental architecture of a plugin this the web is a plugin to the business rules...”

To demonstrate the structural power dynamics of plugin architectures, Uncle Bob surveys the audience on the programming languages they use: - **Java** and **.NET** - **Python** (“dynamically typed, lots of cool things”) - **APL** (Uncle Bob reacts with disbelief: “Really? People still use APL? Wow. Do you have like special keyboards with a million keys?”) - **PHP** (“accidental language, was never supposed to be a language, but it became one”) - **Tcl/Tk** (“graphics language”) - **JavaScript**

He then uses a concrete software tool pair to illustrate plugin dynamics: Microsoft’s Visual Studio and JetBrains’ ReSharper (a plugin that Martin quips “makes it almost usable”). He asks the audience to consider the relationship between the Visual Studio team at Microsoft and the ReSharper team at JetBrains: which of those teams holds absolute power, and which team is completely powerless? While Microsoft could leverage its financial dominance to harm anyone, Uncle Bob points out that purely at the architectural level of the plugin boundary, Microsoft does not need money to put JetBrains out of business—the author of the boundary controls the contract.

[36:13](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2173s) Plugins, Compile-Time Immunity, and UI Architecture

Uncle Bob explains how dependency direction establishes power and immunity within software systems. When one system acts as a host and another connects as a plugin, the host holds all the leverage. For instance, a platform vendor could drive an extension developer out of business simply by altering their public interface daily, repeatedly invalidating the plugin just as it prepares to deploy. A plugin is intrinsically vulnerable to the system it plugs into, whereas the core system remains completely immune at compile time to anything occurring within the plugin.

Software architects should intentionally leverage this asymmetry. In any production system, the core business rules must remain immune to the erratic, frequent shifts of the user interface. Because the user interface changes far more frequently than core business logic, it belongs on the vulnerable, outer side of the boundary:

> “If something changes a lot it should be a plug-in if something doesn't change very often it should be plugged into this is the signature of a plug-in plugins always have source code dependencies that point One Direction.”

```mermaid flowchart LR accTitle: Plugin Dependency Flow accDescr: Demonstrates that the UI depends on Business Rules, leaving Business Rules immune to UI changes. subgraph UIModule["UI (Volatile Plugin)"] UI["User Interface"] end subgraph CoreModule["Core (Stable Host)"] BR["Business Rules"] end UI -->|"depends on (source code)"| BR ```

[37:56](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2276s) Presenters, Response Models, and the "Stupid" View

Tracing the flow of data back out toward the user, the interactor completes the usecase execution—the "dance of the entities"—and packages the output into a response model data structure. It hands this response model across the output boundary to a presenter. The presenter’s sole responsibility is translating the raw domain data in the response model into a view model tailored for display.

While the response model contains raw, non-presentable structures such as `Date` instances or currency `Money` objects, the view model consists almost entirely of plain strings and simple primitives. The presenter applies the formatting—invoking date formatters, applying currency symbols, setting decimal precision, and wrapping negative amounts in parentheses. If a monetary value is negative and needs to appear in red, the presenter evaluates that condition and sets a boolean flag on the view model. The view model also stores display-level UI state: button label strings, booleans indicating whether buttons or menu items should be grayed out or disabled, and lists governing menu element ordering.

By moving all formatting, logic, and conditional state evaluation into the presenter, the view itself is stripped of decision-making logic:

> “The goal behind that is to make the view so stupid you don't have to test it.”

Because the view merely extracts values from the view model and writes them directly to the screen, developers only need to verify it visually. The presenter, by contrast, is fully unit-testable without launching any UI harness, verifying every string format, toggle, and conditional display flag in complete isolation.

- **Response Model:** Holds business-level results produced by the interactor (raw `Date` objects, currency/numeric types, domain structures). - **Presenter:** Formats raw data into strings, evaluates layout flags, and controls widget state (e.g., active, disabled, text color). - **View Model:** A dumb data holder consisting purely of formatted strings and booleans for the view to render directly. - **View:** Completely devoid of business or formatting logic; acts as an untyped renderer that reads the view model.

[41:28](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2488s) Enforcing Boundaries for Independent Deployment

Every dependency across the architectural boundary must point strictly inward toward the higher-level policies. Uncle Bob emphasizes that this is an absolute rule with zero tolerance for leaks: every import, include, and source code reference must strictly align in that single direction.

This strict adherence is what enables true independent deployment. The UI layer can be packaged into its own JAR, DLL, Ruby gem, or shared library, completely separated from the core business logic component. When modifications are made to the GUI, developers recompile and redeploy only the presentation artifact. The core business rules binary remains entirely untouched, uncompiled, and undeployed, proving that the presentation layer is behaving as a genuine plugin.

```mermaid flowchart TD accTitle: Clean Architecture Output Boundary Flow accDescr: Flow from Interactor through Output Boundary and Presenter to View Model and View. subgraph CoreEngine["Core Business Logic"] UC["Interactor"] OB["Output Boundary (Interface)"] RM["Response Model"] end subgraph PresentationPlugin["Presentation (Plugin)"] P["Presenter"] VM["View Model"] V["View (Dumb)"] end

UC -->|"creates"| RM UC -->|"calls"| OB P -.->|"implements"| OB P -->|"reads"| RM P -->|"populates"| VM V -->|"reads"| VM ```

[42:46](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2566s) The Myth of the Central Database and the Rise of DBAs

Uncle Bob shifts to the database, addressing the pervasive architectural belief that the database resides at the center of the universe while all surrounding applications exist merely to serve it. He mockingly asks whether the database calls to developers in their sleep like an idol surrounded by worshiping minions. This mental model directly benefits database administrators (DBAs), elevating them to "high priests" who guard the system from atop their perches.

He recounts the historical emergence of DBAs during the 1970s, tracing the title back to aggressive enterprise marketing campaigns led by companies like Oracle. Back when relational database vendors had to fight for market share, their sales forces approached corporate executives with a manufactured concern:

> “They went to CEOs and they said to the CEOs are you protecting your data assets what a beautiful word that is a data asset no one had ever thought of this word data and asset together a data could be an asset are you protecting your data assets CEO goes I didn't know I had data assets oh yes you do and you'd better be protecting them and we have the system that will protect them ah but in order to use our system you have to change your organization you need people to preside over the control of your data assets”

This narrative spurred enterprises to hire dedicated database administrators holding exclusive keys to data stores. Uncle Bob views this elevation as a profound architectural mistake. A database is not an architectural centerpiece; it is merely an I/O device—a mechanical repository for storing bits that happens to offer indexing, searching, and reporting tools. Treating the database as the core architectural focus repeats the architectural error found in frameworks like Ruby on Rails, which places both the web and the database at the center of the application.

- **Role of the Programmer:** Managing details by rigorously separating concrete mechanisms from overarching abstract business rules. - **True Nature of Databases:** An auxiliary I/O device and persistence mechanism, not an architectural foundation.

[46:15](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2775s) Relational Databases, SQL, and Disk Mechanics

Uncle Bob challenges the modern developer's uncritical acceptance of relational table structures. Persistent storage existed long before relational databases—Unix file system directory hierarchies and hierarchical database engines operated successfully for years. Despite this, the industry spent three decades cramming object-oriented and relational pointers into flat tables, linking them through relationship tables, and querying them through SQL.

He characterizes SQL as a fundamentally flawed access language that introduced widespread security vulnerabilities worldwide:

> “And we have to have this this actually truly horrible language called SQL SQL to access all of this data which by the way has led to tremendous security problems all over the place because somebody had the great idea that our data access language should be textual and then interpreted inside the code so that some guy out in Nigeria could type select star inside the date field”

These elaborate relational engines and frameworks were never invented because tabular layouts represent pure data theory; they were created because physical disks are difficult to manage. Uncle Bob asks the audience if anyone has ever written a disk driver, explaining that rotating magnetic platters—historically stacked 14 or 15 high—forced software designers to invent complex access layers simply to navigate physical read/write heads across magnetic tracks. Relational database engines are, at their foundation, historical workarounds for hardware constraints.

[48:12](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2892s) The Physical Mechanics and Pain of Spinning Discs

Robert C. Martin ("Uncle Bob") takes the audience back to the mechanical realities of early computer storage to explain why databases took the shape they did. In the early days, disk drives featured magnetic platters spinning on a central spindle at speeds like 3,600 RPM (spinning roughly 50 to 60 times a second), which later accelerated to 7,200 RPM as drives shrank. A physical read/write head had to swing mechanically in and out across the surface, writing microscopic magnetic dots arranged in concentric circles called tracks. With tracks spaced perhaps half a millimeter apart, a platter held 200, 500, or 1,000 such rings; because platters were stacked vertically on the spindle, the vertical cylinder formed by all tracks sharing the same radius was termed a cylinder.

Writing or reading a single byte was a tortuous physical process. A developer or controller had to issue a directive telling the drive head to seek to a specific cylinder (such as cylinder 72). In the earliest hardware, programmers even had to account for physical momentum, manually commanding acceleration and deceleration. A typical seek time in the 1960s, 1970s, and into the mid-1980s was roughly 100 milliseconds. After that delay, the controller had to wait out the rotational latency for the target section of the spinning platter to arrive beneath the head. Furthermore, drives could not read a single byte directly; they had to retrieve an entire 4-kilobyte sector. Only after bringing all 4 KB into memory could software extract the solitary byte it actually needed.

Uncle Bob emphasizes that the entire enterprise of relational databases and early data frameworks was created to mitigate this physical friction: “The job of the database was to get rid of that pain to to make it efficient to get data in and off this horrible spinning magnetic surface.” To overcome disk geometry, computer scientists devised B-tree indexes, structured relational tables, and specialized retrieval frameworks solely to maximize mechanical efficiency.

- Disk platters spun at 3,600 RPM up to 7,200 RPM (50–60 revolutions per second). - Seek times routinely took about 100 ms just to physically position the arm over a cylinder. - Data could only be read in whole sector chunks (typically 4 KB) after rotational latency elapsed. - Relational tables and indexing were technological workarounds invented to conquer physical disk mechanics.

[50:42](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3042s) The Death of the Disk and the Illusion of Persistent Tables

Turning to the audience, Uncle Bob asks how many developers in the room have a spinning hard drive inside their laptop. Not a single hand goes up. He reflects that five years earlier, most of the room would have had spinning platters, while the two or three pioneers with solid-state drives would have been smugly sitting there while everyone else hated them; ten years earlier, nobody would have been disk-free. Now, mechanical disks are disappearing completely, not just from personal machines but from enterprise server farms as well. In their place is massive, linear, addressable RAM.

Uncle Bob challenges the fundamental premise of continuing to store business state in relational rows and columns when disks are gone. If developers possess a massive 32-bit or 64-bit address space of persistent RAM, the rationale behind relational mapping evaporates. In traditional enterprise development, programmers issue SQL queries, pull relational row sets into ADO structures, and then immediately execute an expensive object-relational mapping step: they unpack tabular data into linked lists, hash tables, and binary trees because those are the programmatic structures software engineers actually want to work with. Programmers want pointers, not joins and foreign keys.

Uncle Bob argues that the entire relational database industry faces obsolescence: “If I were Oracle I would be pretty scared because the reason for my existence is evaporating out from underneath me.” While acknowledging the deep cultural momentum behind SQL and the mathematical elegance of relational algebra, he notes that developers increasingly have access to virtually unlimited persistent memory where data can remain in native tree, hash, and list formats. He views the rise of the NoSQL movement not as an isolated trend, but as natural fallout from disks dying: “The NoSQL movement is born out of this notion that we don't need the discs anymore we don't need the databases anymore.” Binding an entire system tightly to a relational database today risks trapping that codebase in obsolete paradigms ten years down the road while the industry moves to far cleaner memory-native techniques.

[54:28](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3268s) Decoupling via the Entity Gateway Pattern

To insulate core business logic from storage mechanisms, Uncle Bob insists on placing a firm architectural boundary between them, representing it on his diagram with a prominent double line. Under this architecture, the database becomes a mere plug-in to the business rules, rather than the foundation of the system. He strongly condemns placing business logic into SQL stored procedures. While stored procedures have legitimate uses for maintaining data integrity or tracking back-pointers, using them to execute domain behavior is disastrous—he recalls with horror seeing a system where an entire mail-merge operation was implemented inside a SQL stored procedure.

To achieve clean separation, the use-case interactors interact with storage exclusively through an abstract interface called an **Entity Gateway**. The Entity Gateway contains pure domain-level query and command methods (for example, `getOrdersSince(date)`), completely free of database syntax. The business rules live on one side of the boundary, oblivious to whether data is stored in Postgres, Oracle, flat files, or an in-memory hash table. On the other side of the boundary, a gateway implementation maps those method calls into concrete SQL queries, communicates with the database API, and feeds plain domain objects back across the line.

```mermaid flowchart TD accTitle: Database Boundary and the Entity Gateway accDescr: Interactors depend on an Entity Gateway interface, making the database implementation a plug-in.

subgraph Core["Core Business Rules (Application)"] direction TB Interactor["Use Case Interactor"] IGateway["«interface»<br/>Entity Gateway<br/>+getOrdersSince(date)"] Interactor --> IGateway end

Boundary(("Boundary Double Line"))

subgraph DBPlugin["Database Plug-in (Infrastructure)"] direction TB GatewayImpl["Entity Gateway Implementation<br/>(SQL / ORM Mapping)"] Database[("Relational Database / Storage")] GatewayImpl --> Database end

IGateway -.-> Boundary Boundary -.-> GatewayImpl GatewayImpl --"implements"--> IGateway ```

Because dependencies point inward toward the business rules, the database is architecturally subordinate. Uncle Bob summarizes the relationship: “The database should plug into the business rules business rules should not be written anybody write their business rules of stored procedures in the database this is a bad idea we don't want to have SQL as our business rules.”

[56:31](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3391s) Fast Test Suites, Instant Feedback, and the Courage to Clean Code

A primary practical benefit of decoupling business logic from the database through an Entity Gateway is test suite execution speed. Real database access makes integration suites notoriously sluggish. By decoupling through an interface, developers can swap out the database entirely during automated testing, replacing slow database calls with lightweight in-memory stubs or fakes that return canned answers instantly. When stubs replace database transactions, thousands of unit tests can execute in microseconds rather than dragging out over minutes or hours.

Uncle Bob underscores that speed is a hard architectural requirement for a test suite: “One of the design imperatives for a test Suite is that it execute quickly and the reason for that is very simple if it doesn't execute quickly you're not going to execute it you're going to put it in some thing that is executed at night.” If tests take longer than two or three minutes, engineers stop running them locally during their inner development loop, deferring execution to nightly continuous integration runs or weekend builds. When tests run in seconds, they give developers instantaneous feedback.

This fast feedback loop changes the entire software delivery culture:

1. **Deploying on Green:** If a comprehensive test suite passes and developers truly trust its coverage, passing is not an ambiguous signal; it means the software is production-ready. The test suite effectively becomes the QA department: “One of the goals is to get the test suite to be the QA process test pass shipping and I mean literally if the test pass you deploy.” 2. **Fearless Refactoring:** Trust in a rapid test suite gives developers the safety net needed to continuously clean dirty code. When an engineer encounters messy code, they can refactor a small piece, press a key, see the green light within seconds, clean further, and immediately catch and fix any regressions. Uncle Bob sets up the psychological contrast: without an airtight, microsecond-fast test suite, developers look at ugly code and back away out of fear of breaking it.

- Any test suite taking more than a couple of minutes gets avoided and relegated to nightly CI runs. - Replacing the database boundary with fast stubs reduces test runtimes to microseconds. - A trusted, fast-running test suite is the single prerequisite that empowers engineers to clean legacy code without fear.

[01:00:12](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3612s) The Fear of Touching Code and the Power of Fast Tests

Uncle Bob reflects on the natural human reaction developers experience when looking at fragile legacy code: the immediate thought is "I'm not touching it," because touching it risks breaking it, and breaking it makes you the owner of that mess. He warns that once developers adopt this defensive posture, software inevitably rots. New features will be added and bugs will be fixed, but every single intervention will make the codebase worse over time because no one dares to clean it up.

The only antidote to this decay is having a suite of automated tests that you truly trust. With reliable tests, cleaning code becomes easy and costs nothing, because developers know instantly if anything breaks. Uncle Bob emphasizes that by decoupling business rules from heavy external dependencies like the database, test suites run blazingly fast—executing all core business rules in mere seconds, or at most a minute.

> “The only way to make it better is to have a sweet of tests that you trust and then it's easy then you make it better all you want and it cost you nothing” — Uncle Bob Martin

[01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) The Story of FitNesse: Deferring the Database Decision

Around 2002, Uncle Bob and his son started building FitNesse, a wiki tool designed to run acceptance tests written in HTML tables based on a framework Ward Cunningham had created in 1999. In the beginning, they assumed they needed a place to store wiki pages, which meant they needed a database. At the time, MySQL was essentially the only open-source relational database available. They prepared to install MySQL and design a schema, but realized they did not need it yet because their immediate problem was simply parsing wiki markup into HTML.

To keep moving without a real data store, they created a simple mock called `MockWikiPage` derived from the `WikiPage` base class. The `save()` method was mocked out to do nothing, allowing them to focus entirely on the `toHtml()` method for three months. Later, when test-driven development demanded fetching multiple pages, the team again considered MySQL. Instead, they opted to store pages in an in-memory hash table while keeping the same query and save interfaces intact. For an entire year, they developed the bulk of FitNesse without persisting anything to disk; tests passed, features grew, and life was good.

```mermaid flowchart TD accTitle: Evolution of Storage in FitNesse accDescr: Step-by-step postponement of persistence mechanisms across the development cycle of FitNesse.

subgraph Iterations ["Phases of Persistence"] direction TB P1["MockWikiPage<br/>(save mocked out, focus on HTML rendering)"] P2["In-Memory Hash Table<br/>(same query/save API, runs for ~1 year)"] P3["File-System Storage<br/>(implemented in 1 day by Michael Feathers)"] P4["MySQL Plug-in<br/>(client implemented in 1 day, eventually abandoned)"] end

P1 --> P2 P2 --> P3 P3 --> P4 ```

Eventually, testing the bootup sequence required actual persistence to load pages from disk. Michael Feathers suggested dumping the hash table into a rudimentary file-system structure rather than pulling in MySQL. He implemented it in a single day. Three months later, while using and demonstrating the tool on real projects, the team realized the file-system solution was more than good enough and abandoned the database altogether.

- FitNesse integrated Ward Cunningham's 1999 test framework, turning HTML tables green upon passing. - Storing pages in simple files offered an unexpected benefit: wiki pages could be checked directly into source control. - When an enterprise client insisted a year later that artifacts were "data assets" requiring a database, FitNesse's interface allowed the client to implement a MySQL adapter in a single day; the plugin was bundled with FitNesse, but nobody used it and it was eventually dropped.

[01:06:11](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3971s) The Core Role of an Architect: Deferring Decisions

Uncle Bob uses the FitNesse experience to illustrate a central tenet of software architecture: good architecture does not rush to make big decisions; it creates the structural freedom to defer them as long as possible. Deferring decisions allows software to be built in the absence of premature commitments, giving teams the chance to make those calls later when they have the maximum amount of real-world information.

While some fundamental choices (such as the programming language) must be made early, decisions regarding databases, message queues, and user interfaces can almost always be delayed. Even if an organization has spent millions on a vendor database license, developers should still architect the business rules as though the database is completely unknown. Similarly, starting a web application by typing `rails new` locks developers into a framework before the core business behavior is even defined.

> “The job of an architect is not to make decisions the job of an architect is to defer decisions as long as possible to allow the program to be built in the ab absence of decisions so that decisions can be made later with the most possible information” — Uncle Bob Martin

> “A good architecture maximizes the number of decisions not made and you achieve that by using a plug-in model” — Uncle Bob Martin

[01:08:19](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4099s) The Plugin Model and Keeping Frameworks at Arm's Length

In a clean architecture, business use cases sit at the center, while user interfaces, databases, and third-party frameworks are treated as plugins that point inward. The inner business rules remain completely oblivious to external mechanisms.

```mermaid flowchart TD accTitle: Plugin Architecture Boundary accDescr: The core use cases and entities remain unaware of the peripheral plugin components.

subgraph Plugins ["Peripheral Plugins"] UI["User Interface"] DB["Database"] FW["DI & Web Frameworks"] end

subgraph Core ["Core Application"] UC["Business Rules & Use Cases"] end

UI -->|"depends on"| UC DB -->|"depends on"| UC FW -->|"depends on"| UC ```

Uncle Bob addresses the overuse of dependency injection (DI) frameworks, warning against letting annotations like `@Autowired` or large XML configuration files bleed deep into domain logic. When DI is scattered everywhere, debugging startup crashes or runtime binding errors becomes a nightmare. Instead, teams should only inject a small number of top-level factories or strategies—perhaps a dozen or two at most—and keep the injection logic confined to an outer plugin layer that feeds plain objects inward.

He cautions that developers must maintain strict boundaries around all third-party frameworks because framework authors have incentives that do not align with application developers. When you build your core logic around a framework, you make an enormous, irreversible commitment to it, while the framework author makes zero commitment to you and can break APIs whenever they see fit.

- Injection frameworks should inject factories and strategies, not raw domain entities or fine-grained business logic. - The use case core should never import or reference dependency injection annotations or framework libraries. - Framework authors are not evil, but they design tools for their own priorities, leaving developers vulnerable to breaking changes and vendor lock-in.

> “You don't want your use case code to know much about any framework why because framework authors are out to screw you” — Uncle Bob Martin

> “You make a tremendous commitment to that framework the author makes no commitment to you at all and the author will change the framework anytime the author feels” — Uncle Bob Martin

[01:10:51](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4251s) Keeping Frameworks at Arm's Length

Robert Martin warns developers about the hidden risks of blindly committing to third-party frameworks. Mimicking a callous framework creator, he jokes about the vendor lock-in that ensues when updates occur: “Tough tough beans guys, I changed the framework and you are bound to me.” A skilled software developer must evaluate frameworks cautiously, understanding that the immense power they deliver comes with substantial costs. To avoid becoming captive to those costs, teams must keep frameworks at arm's length rather than letting them dictate the core structure of an application.

```mermaid flowchart LR accTitle: Keeping Frameworks at Arm's Length accDescr: Diagram illustrating clean boundaries between the application core and external frameworks. subgraph Core["Core Application"] Entities["Entities & Use Cases"] end subgraph Delivery["External Frameworks"] WebFW["Web Framework (e.g., Rails)"] end Delivery -->|"calls boundary interfaces"| Core ```

The cardinal mistake, Uncle Bob emphasizes, is mimicking the tutorials and quick-start guides written by the creators of those tools. Uncle Bob explains: “The most important thing about that is you do not want to follow the examples that the framework author wrote for you, because the framework author is perfectly comfortable coupling to his framework, but you should not.” Citing Ruby on Rails, he asks how a team could ever break away if they weld their business code tightly to the framework. A clean architecture ensures that if a tool like Rails ever turns into a bottleneck or organizational liability, the system can detach from it without requiring a full rewrite: “You should architect your system so that if Rails becomes a liability, it is not impossible to break away from it.” He then winds down the lecture, glancing at his contact slide and humorously announcing that it is late and he is ready for a beer before opening the floor to questions.

- Frameworks offer power and speed, but tight coupling transfers control of your release cycle and architecture to external maintainers. - Example code written by framework authors demonstrates framework features, not sensible boundary architecture for enterprise business logic. - An application's architectural independence guarantees that replacing an underlying web or persistence framework remains feasible throughout the system's life.

---

[01:12:22](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4342s) Q&A: Stored Procedures, Entities, and Database Decoupling

An audience member asks about database boundary practices, noting that stored procedures are frequently employed below the repository layer to completely shield the underlying database from application programmers. Under this setup, developers are denied direct table access and forbidden from issuing arbitrary SQL queries. The attendee remarks that developers appreciate having clean calls, while DBAs appreciate being able to protect and control their relational domain.

Uncle Bob agrees with the premise and uses the question to clarify an architectural distinction regarding entity design. He points out that domain entities should never be thought of as one-to-one mirror images of database rows or relational tables. As Uncle Bob remarks: “One of the things I didn't talk about there was that the entities are not instances of database tables; they're usually constructions from many database tables and maybe some operations as well.” Because domain entities bundle behavioral operations and synthesize data across many relational sources, abstracting persistence mechanisms—whether through stored procedures, mapping layers, or repository interfaces—protects the business core from schema churn. With the question answered, Uncle Bob decides not to dive deeper into database mechanics, wraps up the talk with a cheerful exclamation of “Beer!”, and thanks the audience.

- Stored procedures can act as an access barrier, allowing DBAs to optimize relational schemas while preventing application code from executing direct SQL queries against underlying tables. - Entities in clean architecture encapsulate core business rules and behaviors; they represent synthesized domain concepts rather than 1:1 representations of relational database records. - Decoupling database storage mechanisms from entities ensures business rules remain unaffected by table-level schema refactorings.

## Notable quotes > [01:06:11](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3971s) A good architecture maximizes the number of decisions not made and you achieve that by using a plug-in model — Uncle Bob Martin

> [01:10:51](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4251s) You should architect your system so that if Rails becomes a liability, it is not impossible to break away from it. — Uncle Bob Martin

> [01:10:51](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4251s) you do not want to follow the examples that the framework author wrote for you, because the framework author is perfectly comfortable coupling to his framework, but you should not. — Uncle Bob Martin

## Mentioned Robert C. Martin, Mitochondrial Eve, Y-chromosomal Adam, Ruby on Rails, Spring, Hibernate, Ivar Jacobson, Object-Oriented Software Engineering: A Use Case Driven Approach, Trygve Reenskaug, Smalltalk, Microsoft, Visual Studio, JetBrains, ReSharper, Python, APL, PHP, Tcl/Tk, JavaScript, Oracle, SQL, FitNesse, Ward Cunningham, Michael Feathers, MySQL, Postgres

## Video description The Principles of Clean Architecture by Uncle Bob Martin (@unclebobmartin)

Robert C. Martin, aka, Uncle Bob has been a software professional since 1970 and an international software consultant since 1990. In the last 40 years, he has worked in various capacities on literally hundreds of software projects. In 2001, he initiated the meeting of the group that created Agile Software Development from Extreme Programming techniques and served as the first chairman of the Agile Alliance. He is also a leading member of the Worldwide Software Craftsmanship Movement - Clean Code.

He has authored "landmark" books on Agile Programming, Extreme Programming, UML, Object-Oriented Programming, C++ Programming and most recently Clean Code and Clean Coder. He has published dozens of articles in various trade journals.He has written, directed and produced numerous "Code Casts" videos for software professionals.Bob is a regular speaker at international conferences and trade shows.Mr. Martin is the founder, CEO, and president of Uncle Bob Consulting, LLC and Object Mentor Incorporated.

Uncle Bob has published dozens of articles in various trade journals, and is a regular speaker at international conferences and trade shows.

Mr. Martin has authored and edited many books including:

• The Clean Coder

• Clean Code

• Agile Software Development: Principles, Patterns, and Practices.

• Designing Object Oriented C++ Applications using the Booch Method

• Agile Principles Patterns and Practices in C#

• Patterns Languages of Program Design 3

• More C++ Gems

• Extreme Programming in Practice

• UML for Java Programmers

http://www.meetup.com/Norfolk-Developers-NorDev/events/223533655/

[Source](https://www.youtube.com/watch?v=o_TH-Y78tt4)

中文简报

Bob大叔谈整洁架构原则 Bob大叔(Uncle Bob Martin)认为,顶层软件架构必须鲜明体现应用的业务意图,而非大肆展示构建它所用的Web框架或数据库。Martin借鉴Ivar Jacobson以用例为驱动的方法,阐明了作为核心业务规则的不变实体与应用特定交互器,应当位于架构中心,并受严格的输入输出边界保护。通过将Web、数据库和第三方框架视为依赖关系向内指向稳定领域逻辑的外部插件,工程团队得以与外部波动解耦。这种清晰的分离不仅能实现亚秒级自动化测试执行,消除重构遗留代码的恐惧,还使架构师能够最大程度地推迟技术决策。 软件架构应当大声宣示其业务领域,而非交付框架或工具,同时将Web仅仅视为一种I/O交付机制。 (10:45) 整洁架构推行向内依赖规则,交付机制和数据库充当核心实体与交互器的插件。 (33:07) 关系型数据库和SQL曾是应对旋转磁盘延迟的工程变通方案,而这一架构限制在现代持久性内存面前已不复存在。 (50:42) 软件架构师的一项主要职责是最大化推迟决策的数量,使框架和数据库选型保持在臂长之外的距离。 (66:11)

Reading summary

Robert C. Martin argues that software architecture must communicate core business intent rather than delivery mechanisms, frameworks, or database technologies. By structuring systems around use cases and treating external mechanisms as plugins behind strict boundary interfaces, development teams can defer technical commitments, execute fast automated tests, and preserve codebase maintainability. Architecture as Domain Intent Over Frameworks (12:38) Martin contends that software architecture should mirror architectural blueprints, where examining a plan immediately reveals whether a structure is a library or a church. In contrast, modern web development frequently foregrounds delivery tools over domain intent—organizing codebases around framework conventions such as Rails' controllers, models, and views directories, or describing an application as 'Java, Spring, and Hibernate.' Martin asserts that the web is merely an I/O device intended to project output and accept input, and elevates delivery mechanisms to an outsized role that obscures business purpose. To restore architectural focus, Martin points to Ivar Jacobson's 1992 use-case model, which he argues was later rebranded as Agile user stories after consulting templates burdened use cases with administrative forms. An effective use case—such as creating an order—remains agnostic of HTML, HTTP, and SQL, specifying only input data, execution steps, and error courses without tying application workflows to specific delivery channels or database schemas. Boundaries, Interactors, and Test Isolation (24:17) Clean architecture divides business logic into application-independent business rules (Entities) and application-specific workflows (Interactors). Interactors orchestrate domain entities while communicating with external systems exclusively through abstract Input and Output Boundaries. Data crosses these boundaries via decoupled request and response models composed strictly of plain language primitives, completely devoid of framework base classes, HTTP fields, or web knowledge. On the output side, a presenter converts raw domain types from the response model into formatted strings and UI booleans inside a view model. Martin notes that moving formatting and conditional display flags into the presenter leaves the view 'so stupid you don't have to test it.' This separation permits full isolation: interactors and presenters can be comprehensively verified through unit tests without booting web servers, application containers, or databases. The Plugin Model and Database Subordination (33:07) Martin defines a clean system boundary as a 'double black line' across which all source code dependencies point inward toward the core business rules. In this arrangement, both the user interface and the database behave as replaceable plugins. Martin challenges the traditional elevation of databases to central architectural pillars, characterizing that status as an artifact of 1970s enterprise marketing and physical hardware constraints—such as 100-millisecond head seeks, rotational latency, and 4-kilobyte sector reads across spinning platters. With non-volatile, large address spaces and solid-state storage reducing the technical justification for relational mappings, Martin advocates placing storage behind an Entity Gateway interface containing domain-level methods. Outer gateway implementations map these calls to concrete SQL or alternative persistence engines without leaking database syntax into the core. Furthermore, Martin stresses that domain entities synthesize logic across multiple sources and must not be modeled as direct mirrors of underlying database tables. Deferring Decisions and Maintaining Independence (66:11) According to Martin, a primary duty of an architect is not rushing into implementation choices, but structuring systems to defer decisions as long as possible until the team possesses maximum information. Highlighting the development of FitNesse around 2002, Martin recalls that relying on simple mocks and an in-memory hash table allowed the team to defer persistence for an entire year, ultimately discovering that a rudimentary file-system store built in a single day was sufficient to discard a planned MySQL implementation. Decoupling the database also allows automated test suites to swap persistence for in-memory stubs, completing thousands of tests in seconds. Martin observes that fast, trustworthy tests give developers the psychological safety required to refactor legacy code without fear of introducing undetected regressions. Finally, he warns against tightly binding core logic to third-party framework libraries or author-provided examples, noting that framework creators have no obligation to preserve an application's architecture or release stability. Martin concludes that software longevity depends on rigorous professional discipline, warning that without firm architectural boundaries and comprehensive test suites, systems inevitably rot under deadline pressure as developers fear touching fragile code.

精炼阅读摘要

Robert C. Martin(Uncle Bob)认为,优秀的软件架构应当彰显业务用例而非第三方框架或交付机制,Web、数据库与框架在本质上只是底层实现细节,应通过严格的单向依赖边界作为插件挂载于核心业务逻辑之外。 架构应当彰显业务意图而非交付机制 (10:45) 许多开发者习惯将系统描述为“Rails 应用”或“Spring 与 Hibernate 架构”,但 Martin 指出这仅是工具组合而非软件架构。建筑蓝图会直观展现图书馆的书架或教堂的长椅,同样地,软件的高层目录结构应当清晰表达其业务领域(如会计或库存系统),而不是将其掩盖在 app、controllers、models、views 等框架目录之下。在整个软件系统体系中,Web 本质上只是一种通过 HTML 输入输出的 I/O 设备,在过去二十年里并未从根本上颠覆软件开发的本质规律。如果将交付机制上升为主导应用的组织原则,核心业务意图就会被具体细节严重遮蔽。 以用例为核心的领域对象与边界隔离 (15:27) Martin 借鉴了 Ivar Jacobson 在 1992 年提出的用例驱动思想,提倡去除顾问咨询流程中繁琐的表格模板,将用例还原为纯粹的业务交互。用例能够被对象化为交互器(Interactor),其中封装了特定于该应用的业务规则;而跨系统通用的企业级业务规则(如订单总价计算)则封装在实体(Entity)中。通过定义抽象的输入边界(Input Boundary)与输出边界(Output Boundary),交互器利用不包含任何 HTTP 或 Web 框架依赖的纯数据结构(POJO)接收请求模型,调度实体协作,并将结果模型传递给表示器(Presenter)。 插件架构与极简视图保障可测试性 (36:13) 在清晰架构中,所有跨越边界的源代码依赖必须严格单向指向内部的高层业务规则,使外部机制成为内部核心的“插件”。Martin 指出,频繁变动的用户界面处于边界的外侧,核心业务规则在编译期对其完全免疫。交互器输出的响应模型由表示器处理,负责日期格式化、货币符号添加以及设置按钮置灰等展示逻辑,生成仅由纯字符串和布尔标记构成的视图模型(View Model)。这种设计剥离了视图层的所有逻辑判断,使视图极度简化而无需编写自动化测试,同时表示器与交互器脱离 Web 服务器和数据库即可独立运行单元测试,从而实现秒级反馈与无惧重构。 数据库与框架作为外部插件的解耦 (42:46) 针对将数据库视作系统核心并赋予 DBA“特权”的传统现象,Martin 强调数据库只是用于持久化存储的 I/O 设备,关系表和 SQL 的诞生很大程度上是为了克服早期磁头寻道与扇区读取等物理磁盘机械瓶颈。他提出交互器应仅通过抽象的实体网关(Entity Gateway)接口执行领域方法,阻断 SQL 存储过程侵入业务逻辑。在持久内存与 NoSQL 普及的背景下,过度绑定特定数据库会沦为技术负债。此外,开发者应对第三方框架保持警惕,不可直接照搬框架作者的强耦合示例,以防框架重大版本变更时丧失架构独立性。 架构师的核心职责是延迟决策 Martin 以 FitNesse 工具的研发历程为例,展示了如何在长达一年的时间里通过 Mock 对象和内存哈希表开发核心功能,直至最后仅用一天通过文件系统实现持久化,从而彻底摒弃了原计划引入的 MySQL。他据此主张,架构师的职责不是过早拍板重大技术方案,而是通过构建插件模型尽可能推迟对数据库、消息队列和界面框架的决策,允许团队在掌握最充分的现实信息后再做决定。对于依赖注入(DI)框架,亦应限制在最外层的插件配置中注入顶层策略,严禁将注解渗透进核心领域代码。 Martin 总结认为,实体并非关系数据库表的直接镜像,而是融合了业务行为与多表数据的领域抽象;通过单向依赖将 Web、存储和第三方框架隔离在核心用例之外,才能确保系统在工具过时或成为负担时具备灵活脱钩与演进的能力。

Detailed notes — zh

## Background 该视频由 YouTube 频道 NorfolkDevelopers 于 2015 年 12 月 15 日发布。页面内容记录了软件行业先驱、敏捷联盟首任主席 Uncle Bob(Robert C. Martin)在技术聚会上发表的关于“整洁架构原则(The Principles of Clean Architecture)”的经典主题演讲。 ## TL;DR Uncle Bob 在演讲中犀利地指出,Web 和数据库本质上都只是外部的 I/O 设备与实现细节,软件架构必须专注于传达业务意图,而非展示底层使用的技术工具。他结合 Ivar Jacobson 的用例驱动方法,深入拆解了实体、交互器与边界对象的职责划分,阐述了 UI 与数据库应当如何作为纯粹的“插件”单向依赖核心业务规则。通过 FitNesse 等实战案例,他强调优秀架构的核心在于尽可能推迟重大架构决策,并呼吁开发者借助高速轻量的自动化测试套件与框架“保持一臂之距”,从而避免被框架和数据库绑架。 ## Atmosphere & style 全场充满极客式的幽默与辛辣的冷嘲热讽,Uncle Bob 凭借强大的舞台掌控力和讲故事能力,将看似枯燥的架构理论讲得妙趣横生。从祖先基因悖论与人类起源的宏大铺垫,到痛批 Web 框架和数据库霸权的当头棒喝,整场演讲的情绪弧线跌宕起伏。现场笑声与掌声不断,既有对行业通病针针见血的无情揭露,也有程序员在聚会中渴望早点去“喝一杯啤酒”的轻松随性氛围。 ## Bulletin - [12:38](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=758s) Web 与数据库本质上都只是 I/O 设备与实现细节,软件架构的核心职责是传达系统的业务意图,而非展示所使用的 Spring 或 Rails 等框架工具。 - [36:13](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2173s) 整洁架构采用插件化模型:变动频繁的外围(如 UI 和数据库)应作为插件依赖稳定的业务规则,所有源码级依赖与 import 必须单向指向核心业务。 - [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) 优秀架构师的核心职责是尽可能推迟重大架构决策,FitNesse 案例证明核心系统可在无关系型数据库的情况下依靠内存与文件系统稳定开发运行数年。 - [56:31](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3391s) 自动化测试套件的终极目标是建立微秒级打桩与快速反馈能力,达到“全绿即可直接上线”的信任度,以此赋予团队持续重构与清理代码的底气。 ## Key takeaways - [12:38](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=758s) 架构的核心关乎业务意图而非技术工具,一个系统的顶层结构应当清晰表明它是会计或订单系统,而不是宣称自己是 Rails 或 Spring 应用。 - [20:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1201s) Ivar Jacobson 的用例驱动方法将用例转化为具体的交互器(Interactor)对象,负责封装应用特定的业务流程,跨系统不共享。 - [20:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1201s) 业务实体(Entities)封装应用无关的业务规则,交互器通过编排实体的交互逻辑,实现输入数据向输出结果的流转。 - [24:17](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1457s) 请求模型与响应模型必须是纯粹的原生数据结构(POJO/POCO),严禁包含任何特定协议(如 HTTP)或外部框架基类的痕迹。 - [27:26](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1646s) 原始 Smalltalk MVC 模式属于微观组件级设计,而现代 Web MVC 往往缺乏坚固边界,导致控制器、视图与业务规则代码互相严重渗透。 - [36:13](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2173s) 频繁变动的模块应当作为插件,UI 必须作为业务规则的插件存在,业务规则在编译期对 UI 的频繁变更保持完全免疫。 - [38:08](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2288s) 展现器(Presenter)负责将原生业务数据转换为纯字符串和布尔状态构成的视图模型(View Model),从而使视图变得极度简单且无需复杂测试。 - [41:28](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2488s) 所有的源码依赖(包含 import 和头文件引用)必须保持单向,从而实现业务核心库与 GUI 交互界面的完全独立编译与独立部署。 - [42:46](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2566s) 数据库中心主义与 DBA 岗位源于 20 世纪 70 年代商业厂商对“数据资产”的营销推广,关系型数据库主要历史意义在于掩盖旋转机械硬盘的读写延迟与复杂驱动。 - [50:42](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3042s) 随着大容量线性持久化内存(Persistent RAM)的发展,程序能直接持久化二叉树和哈希表,传统关系型表格与 SQL 的存在基础正逐步瓦解。 - [54:26](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3266s) 系统应当通过实体网关(Entity Gateway)抽象接口隔离持久化层,严禁在数据库存储过程中编写业务逻辑,将 SQL 封锁在边界外层。 - [56:31](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3391s) 测试套件必须在几分钟内甚至数秒内跑完,借助微秒级测试桩解耦外部慢速依赖,唯有建立“测试通过即发布”的底气才能支撑无畏的代码重构。 - [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) 架构师的职责不是过早做决策,而是通过插件模型尽可能推迟重大技术选型,避免在项目初期敲下框架命令导致业务被工具深度绑架。 - [01:08:41](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4121s) 依赖注入应仅局限于外围插件层的少数工厂或策略对象,业务用例层必须杜绝感知注入框架与第三方容器的存在。 - [01:10:51](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4251s) 开发者必须对框架保持一臂之距,切勿盲目复制官方教程的高侵入性示例,避免框架作者在不顾及使用方的情况下破坏系统演进能力。 - [01:12:15](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4335s) 领域实体并非数据库单张物理表的简单映射,而是跨多张数据表及业务行为高度抽象构建的聚合体。 ## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1s) 祖先悖论:2 的幂次与现实人口的崩溃 Uncle Bob 以一个看似与软件开发毫无关系的生物学提问开场:每个人到底有多少个亲生父母?台下观众回应是两个。他调侃道,即便在 Norfolk 也是两个,这是无法违背的自然法则。接着他又推导:祖父母是 4 个,曾祖父母最多是 8 个,而高祖父母理论上是 16 个。对于程序员来说,这是最熟悉的“2 的幂次”数列。 然而,这种数学模型在往回推导时迅速走向荒谬。Uncle Bob 带着台下算了一笔账:往回推 10 代(大约 200 年前),理论祖先数量是 $2^{10} = 1024$ 个;再退 200 年到 1615 年,祖先数量将达到 100 万;推到 1415 年(约 600 年前),理论祖先数量将达到 10 亿,而当时地球的总人口甚至未必有 10 亿;再往回退到 1215 年《大宪章》签署的年份,理论祖先数量直接飙升到 1 万亿。Uncle Bob 直言,即便算上所有死去的亡魂,地球上也从未有过 1 万亿人。 “So this power of two thing doesn't work; it only works in the first couple of generations and then it sort of Curves around,” Uncle Bob 解释道。理论曲线与现实曲线发生背离的根本原因在于表亲通婚(近亲繁殖),导致祖先树发生大量重叠与闭环。 - 亲生父母:2人 - 祖父母:4人 - 曾祖父母:8人 - 10 代前(约 200 年):理论上限 1,024 人 - 400 年前(约 1615 年):理论上限约 100 万人 - 600 年前(约 1415 年):理论上限约 10 亿人(已触及甚至超出当时地球总人口) - 800 年前(1215 年,《大宪章》签署):理论上限约 1 万亿人(远超人类历史人口总和) --- ### [03:07](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=187s) 线粒体夏娃与 Y 染色体亚当:单一继承层级 随着时间推移,不仅祖先树会折叠,另一个效应也开始显现:绝嗣。在漫长的历史中,许多家族的谱系因为后代没有繁衍而彻底断绝。Uncle Bob 指出,任何一条血脉都有绝嗣的正概率,如果将时间线拉得足够久远,所有的血脉都会被淘汰,最终只剩下一条。在数学上,全人类必然能追溯到共同的始祖父母——一个全人类专属的“亚当与夏娃”。 这两位始祖并非生活在一起,他们甚至可能相隔数千里、相距数千年,但“these two individuals are the mother and father of everyone alive, and this is a mathematical certainty.” 现代遗传学揭示了这一奇迹:人类细胞内部的线粒体(Mitochondria,Uncle Bob 戏称“不是星球大战里的绝地微小生物 Midi-chlorian”)保留着一套独立的 DNA。线粒体大约在 15 亿年前由一个单细胞吞噬另一个细胞并共生演化而来,它负责利用葡萄糖和氧气生成能量。因为精子不提供线粒体,受精卵的线粒体完全来自母亲的卵细胞,所以线粒体 DNA 是纯粹的母系单传。通过统计线粒体 DNA 单碱基突变的差异,科学家构建了人类的母系谱系树,最终定位到了约 12 万年前生活在东非的“线粒体夏娃”(Mitochondrial Eve)。 同样地,男性的 Y 染色体只由父亲遗传给儿子,不与母亲的 DNA 发生重组。这是一条纯粹的单继承层级(Single inheritance hierarchy),可以一路追溯到约 7 万年前生活在东非末次盛冰期的“Y 染色体亚当”(Y-chromosomal Adam)。 ```mermaid flowchart TD accTitle: 遗传学中的母系与父系单继承模型 accDescr: 展示线粒体 DNA 的纯母系继承与 Y 染色体的纯父系继承如何追溯到共同祖先 subgraph 母系谱系 ["母系单继承 (线粒体 DNA)"] ME["线粒体夏娃 (约 120,000 年前,东非)"] --> M1["欧洲与亚洲迁徙血脉"] M1 --> M2["美洲与澳洲迁徙血脉"] M2 --> M_Today["现代全人类 (通过卵细胞继承)"] end subgraph 父系谱系 ["父系单继承 (Y 染色体)"] YA["Y 染色体亚当 (约 70,000 年前,东非)"] --> Y1["男性迁徙支系"] Y1 --> Y_Today["现代全体男性 (纯父系继承,无重组)"] end ``` --- ### [10:36](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=636s) 架构的当头棒喝:为什么 Web 框架成了主角? Uncle Bob 话锋一转,打趣道:“That's not why we're here, is it? I need to talk about architecture instead.” 铺垫这个庞大的人类寻根故事,正是为了引出他对软件架构核心本质的追问。 大约八年前,他的儿子写了一个 Ruby on Rails 应用程序并拿来给他展示。Uncle Bob 看了一眼目录结构,立刻脱口而出:“哦,这是一个 Ruby on Rails 应用!”因为一眼看去全是典型的 `app` 目录、`controllers`、`models`、`views`,任何熟悉 Rails 的程序员都会得出完全相同的结论。 然而,那一瞬间让 Uncle Bob 感到脊背发凉,陷入了深思:“Wait, why do I know that this is a Ruby on Rails app?” 他开始自我质疑:为什么系统最高级别的顶层目录结构,展示的全是“系统使用了什么框架”,而不是“这个系统究竟是做什么的”? Uncle Bob 越想越觉得荒谬和不安。一个系统的核心应当是它的业务逻辑和业务价值,而框架本应只是实现细节。他严厉质问:“Why is the framework standing out in front of everything, hiding what this application is supposed to do?” 面对台下包括熟悉 Rails 和其他 Web 框架的开发者,Uncle Bob 给出了极具颠覆性的定论:Ruby on Rails 不过是一个 Web 框架,而本质上,Web 不过是一个 I/O 设备(“The web is an IO device”)。既然 Web 仅仅是一个输入/输出设备,它凭什么占据如此显赫的核心位置,甚至彻底遮蔽掩盖了系统本身的业务职责与架构灵魂? ### [12:38](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=758s) Web 只是 I/O 设备,架构关乎“意图”而非工具 随着 20 世纪 90 年代末万维网(Web)的兴起,整个行业陷入了狂热,认为互联网改变了一切。当时正值 dot-com 泡沫的黄金时代,人们只要搭建一个“救助宠物”之类的网站就能轻易融到惊人资金。但当泡沫破裂后,很多程序员甚至沦落到举着“愿意为了食物写代码(I will code for food)”的标语上街。Uncle Bob 直言,软件行业花了将近 20 年才认清现实:“Web 根本没有改变任何本质,它就像其他任何 I/O 设备一样,只是一台 I/O 设备。”它接收用户的输入,并通过奇怪的 HTML 语言向屏幕呈现输出,仅此而已。 然而,Web 带来的心理统治力导致开发者误以为 Web 是一个“具备架构重大意义的元素”。像 Ruby on Rails 这样的 Web 框架开始占据应用程序的核心架构位置。很多开发者张口便称自己在做“Rails 应用”或“Spring 应用”。Uncle Bob 对此极力反驳:当你被问及应用架构时,回答“我的架构是 Java、Spring 和 Hibernate”完全是荒谬的,因为“那不是架构,那只是一堆工具”。你实际开发的是一个会计系统或订单系统,恰好使用了 Rails 或 Spring 而已。 为了证明这一点,Uncle Bob 拿建筑蓝图作对比。如果你看一份图书馆的蓝图,能清晰看到书架、书桌和计算机桌,蓝图在大声告诉你:“我是一座图书馆”;同样,教堂的蓝图也会毫不含糊地宣示:“我是一座教堂”。建筑架构直接表达其用途与意图。Uncle Bob 由此得出结论:“架构关乎意图;它关乎系统的意图,而不是关于工具或框架,那些只是应该被隐藏而非暴露的细节。” - 1990 年代末的 dot-com 繁荣与泡沫破裂,让程序员一度盲目迷信 Web 的颠覆性。 - Web 从本质上仅是负责输入处理与屏幕渲染的 I/O 设备。 - 架构的职责是传达系统的业务意图(如“会计系统”),而非公开其依赖的底层工具链(如 Spring、Rails、Hibernate)。 --- ### [15:27](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=927s) Ivar Jacobson 的用例驱动方法与被顾问毁掉的狂潮 早在 1992 年,Ivar Jacobson 就在其经典著作《面向对象软件工程:用例驱动方法》(*Object-Oriented Software Engineering: A Use Case Driven Approach*)中阐述了完全相同的思想。Uncle Bob 回忆,该书在当时的一场大会上首发时引发了轰动,包括他在内的所有开发者都争相排队购买。然而随后,这场关于“用例(Use Cases)”的运动却走向了灾难。 这场灾难源于“顾问群体的大举入侵”。每个咨询顾问为了体现自身优越性,开始制作并上传各种下载表格(早年通过互联网分发 PostScript 文件,后来演变成 PDF 表单)。表格变得极其繁琐臃肿,强制要求开发者详细填写主要参与者(Primary Actors)、次要参与者、三级参与者、前置条件、后置条件等各种琐碎项。这最终变成了一场官僚式噩梦,逼得团队无法忍受。正好此时敏捷革命兴起,人们纷纷宣称要用“用户故事(User Stories)”取代用例,尽管 Uncle Bob 戏谑地指出:“没有人真正明白用户故事和用例实际上是一回事,但至少我们摆脱了那些恶心的表单。” 标准的 Jacobson 式用例其实非常简朴直接。以订单录入系统中的“创建订单(Create Order)”用例为例: 1. **输入数据**:顾客 ID、送货地址、付款信息。Uncle Bob 强调,用例在此阶段绝不能描述数据的具体内部数据结构,更不应标注数据是否来源于 Web,只需承认这些数据存在并会进入系统。 2. **主路径(Primary Course)**: - 订单文员使用上述数据发起创建订单命令(这是触发用例的手工步骤)。 - 系统验证全部数据(此时不描述具体验证算法与验证规则)。 - 系统创建订单并生成订单 ID(很可能是数据库操作,但用例中无需提前假定并写明)。 - 系统向文员交付订单 ID(可能展示在网页上,但无需在用例层绑定)。 3. **异常路径(Alternative Courses)**:处理各种可能发生的错误与异常,通常只有一两条分支。 --- ### [20:01](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1201s) 交互器(Control)、实体与边界对象的职责划分 Jacobson 理念中最核心的突破在于:既然用例接收输入、处理数据并产生输出,那么完全可以**将用例本身转化为一个具体的对象**。Jacobson 在书中将此类对象命名为“控制对象(Control Objects)”。Uncle Bob 觉得这个名字与 MVC 模式中的控制器过于接近容易混淆,因此将其改称为“交互器(Interactors)”(虽然他也承认叫“用例对象”可能更合理,但名字已经叫开)。 ```mermaid flowchart LR accTitle: Use Case Driven Architecture Components accDescr: The interactions between Boundary, Interactor, and Entity components showing input, processing, and output paths. User["外部系统 / 用户"] --> DM["交付机制 (Delivery Mechanism)"] subgraph Boundaries ["边界 (Boundaries)"] IB["输入边界\n(Input Boundary: 接口)"] OB["输出边界\n(Output Boundary: 接口)"] end subgraph Core ["业务逻辑核心"] INTR["交互器 (Interactor)\n[应用特定业务规则]"] ENT["实体 (Entities)\n[应用无关业务规则]"] end DM -->|"调用"| IB INTR -.->|"实现"| IB INTR -->|"操作 / 协调"| ENT INTR -->|"调用"| OB DM -.->|"实现"| OB ``` Uncle Bob 详细拆解了业务规则的分类以及系统三类核心对象的协作关系: 1. **交互器(Interactors)**: 封装“应用特定的业务规则(Application-Specific Business Rules)”。这类规则仅在特定的应用程序中有意义。例如,“创建订单”的处理流程专属于订单录入应用,其他系统不会复用此流程。因此每个用例对应一个交互器,交互器天然不应在跨应用间共享。 2. **实体(Entities / 业务对象)**: 封装“应用无关的业务规则(Application-Independent Business Rules)”。这类规则跨越多个系统依然成立,例如计算订单各项价格总和的计价逻辑,无论是订单录入系统还是后续订单处理系统都会共用。在典型场景下,一个创建订单的交互器会协调操作多个实体对象,如 `Order` 实体、`LineItem`(订单项)实体和 `Customer`(客户)实体。 3. **边界对象(Boundary Objects)**: Jacobson 最初称其为接口对象,后更名为边界对象。在代码实现中,边界就是 Java 风格的 `interface` 或抽象类。 - **输入边界(Input Boundary)**:由交互器本身实现。外部数据穿过该边界进入交互器执行。 - **输出边界(Output Boundary)**:由外部的展现组件实现。交互器处理完毕后,将结果通过该输出接口向外传递。 - **交付机制(Delivery Mechanism)**:Uncle Bob 刻意使用交付机制这一术语来代替 Web。交付机制负责与最外层的人类用户交互,它可以是 Web 前端、命令行控制台、厚客户端(Thick Client GUI)甚至是 SOA 服务总线。用例核心完全不关心也无需依赖具体的外部交付形式。 ### [24:17](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1457s) 请求模型、交互器与实体之舞 在系统的交互流程中,用户在页面上填写完表单并按下提交按钮后,投递机制(Delivery Mechanism,通常是 Web,但也可能是其他界面)的核心职责就是收集当时产生的全部数据,并将其打包塞进一个名为“请求模型”(Request Model)的数据结构中。Uncle Bob 强调,尽管在 Web 领域这个结构很像 HTTP 请求,但他绝对不希望在其中看到任何与 Web 相关的痕迹:“I don't want the words HTTP in there at all I don't want it to derive from some Base Class which is tied to the web I don't want any field in it that have any webbish kind of knowledge to it”。他所追求的请求模型应当是一个纯粹的原生对象(在 Java 中是 POJO,在.NET 中则是 POCO),里面只包含基础类型(Primitives),完全不依赖任何外部框架。 这个纯净的请求模型随后被传递跨越输入边界(Input Boundary)。由于交互器(Interactor)实现了该输入边界,它接收并解码这个请求模型,识别出这是一个“创建订单”的用例,提取出所有数据。接下来,交互器便开始“编排实体的舞蹈”(control the dance of the entities)——它向各个业务实体发号施令,指示它们创建订单、向订单追加行项、关联客户信息等。当核心业务逻辑执行完毕后,交互器再次驱动这场实体的舞蹈向反方向运动,汇总所有的执行结果,构建出另一个纯净的数据结构——“结果模型”(Result Model/Response Model)。最后,结果模型穿过输出边界(Output Boundary)返回给负责具体渲染的投递机制,再由此呈现给终端用户。 ```mermaid flowchart LR accTitle: Use Case Interactor Data Flow accDescr: Data flows from Delivery Mechanism through Input Boundary into Interactor, controls Entities, and returns via Output Boundary as Result Model. subgraph External ["投递机制 (Delivery Mechanism)"] UI["用户界面 / Web 表单"] end subgraph UseCase ["用例层 (Use Case Layer)"] IB["输入边界<br/>(Input Boundary)"] UC["用例交互器<br/>(Interactor)"] OB["输出边界<br/>(Output Boundary)"] end subgraph Core ["业务实体 (Entities)"] ENT["业务实体之舞<br/>(Order / Customer / Items)"] end UI -->|"请求模型 (POJO/POCO)"| IB IB --> UC UC <-->|"操控与收集"| ENT UC -->|"结果模型 (Result Model)"| OB OB --> UI ``` - **请求模型纯粹性**:绝不继承任何特定框架的基类,不包含 HTTP 请求头或特定协议字段,仅保留业务相关的基础数据类型。 - **交互器的职责**:充当总指挥,解码请求、协调实体完成业务规则、收集数据并生成结果模型。 ### [26:15](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1575s) 脱离外部依赖的业务规则测试 这种边界隔离设计带来了极大的可测试性优势。因为交互器与实体完全没有被外部运行环境污染,开发者在编写用例测试时,只需在测试代码中实例化交互器,构造一个简单的请求模型传入,然后断言输出的结果模型是否符合预期。为了测试这个用例,开发者完全不需要启动真实的 Web 服务器,不需要加载庞大的应用服务器,甚至连数据库都不需要启动。Uncle Bob 指出:“And by the way this is a fundamental goal of writing tests when you're writing tests especially tests of your use cases you do not want to have the whole system running especially any part of the system that is slow or any part of the system that makes it difficult to communicate with the business rules like a web server does”。测试应当能够跨过这些沉重且缓慢的外部基础设施,直接同核心业务规则展开对话。 ### [27:26](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1646s) MVC 模式的最初起源与小粒度设计 谈到架构,许多人会理所当然地联想到 MVC(Model-View-Controller),认为现代 Web 应用天然就应该采用 MVC。为了澄清这个概念的历史真相,Uncle Bob 讲述了自己与 MVC 发明者特里格韦·梅恩斯考格(Trygve Reenskaug)相遇的奇妙逸事。那是在挪威举办的一场技术大会的讲师休息室里,Uncle Bob 正弯腰满地找插座给笔记本电脑充电。一位满脸络腮胡的老者伸出手递给他一个电源排插。Uncle Bob 笑着打趣程序员通常不爱眼神交流的习惯:“Programmers don't make eye contact right so I I took the power strip from him and and then I looked up and oh it's Trygve Reenskaug and as I took it you know our fingers just barely touched I haven't washed that hand”。 随后,Uncle Bob 拆解了 Trygve 在 1970 年代为 Smalltalk 语言发明的原始 MVC 模式。在那个模式诞生之际,“设计模式”这个词甚至都尚未出现: - **模型(Model)**:负责封装非常小范围的核心业务规则。模型绝对不允许知道有哪些外部输入设备在控制它,完全不感知输入源来自哪里。 - **控制器(Controller)**:负责捕获物理设备(如键盘、鼠标)的输入手势,并将其翻译为对模型的方法调用。 - **视图(View)**:负责将模型内部的数据适当地展示给用户。视图与模型之间通过观察者模式(Observer pattern)建立联系——视图向模型注册,当模型发生变化时通知视图:“嘿,我变了,赶紧重新绘制我”。 Uncle Bob 特别强调,当年的 MVC 是“微观层面的架构”(used in the small)。一个按钮有一个独立的 MVC,一个文本输入框有一个 MVC,一组单选按钮也有一个 MVC。在那个时代,根本不存在“为一个完整的网页构建一个整体 MVC”的做法。 ### [30:29](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1829s) Web MVC 的架构退化与职责混乱 当优秀的软件思想传播开来时,行业往往会出现一种畸变:“人们知道 MVC 是个好点子,又觉得自己写出来的东西也是好点子,于是顺理成章地把自己写出来的东西直接扣上 MVC 的名字,结果彻底丢掉了原始设计理念的精髓。”现代 Web 应用所谓的 MVC 架构,通常是 Web 服务器在解析完 URL 或收到 HTTP GET/POST 请求后,唤起一个大一统的控制器;控制器从 URL 参数或表单中解包字符串,调用模型层中的各种业务对象;接着控制器再将控制权移交给视图,视图反过来查询业务对象并将数据塞进 HTML 输出。 这种架构面临的最严重弊端就是缺乏坚固的边界保护。因为交付压力,开发人员在赶进度时往往无所顾忌:“Oh crap I got to get crap done and they start writing code and they don't really care where they're writing it so they might write it here they might write it there it works it's okay it works you know leave me alone”。最终导致的结果是控制器逻辑渗透进业务对象,视图渲染逻辑渗透进业务对象,而业务规则也大量渗入视图模板中。Uncle Bob 直言整个软件行业极度缺乏工程自律:“Hell half of us don't write tests and that's unbelievable right can you imagine telling your users you know I don't write tests I just write the code and sometimes it even works and I ship it to you and if bugs well you're going to tell me aren't you that's our attitude”。 ### [33:09](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=1989s) 边界双黑线与 Web 的“插件化”本质 在伊瓦·雅各布森(Ivar Jacobson)提出的架构体系中,引入了一条关键的“双黑线”(Double Black Line),这代表了一条坚硬的架构边界,用于将核心业务规则与外部世界(如 Web)彻底隔离开来。Uncle Bob 提醒所有人观察双黑线两端的依赖箭头方向:“All of the dependencies point across those double black lines in One Direction always pointing towards the business rules this is the fundamental architecture of a plugin this the web is a plugin to the business rules”。Web 在这套体系里根本不是应用的核心,它仅仅是插在业务规则上的一个外部插件。 为了让听众更直观地理解这种单向依赖与控制力度的非对称性,Uncle Bob 现场调研了与会者使用的开发语言(涵盖 Java、.NET、Python、APL、PHP、Tcl/Tk 及 JavaScript)。随后他以 IDE 与插件的关系打了个极具说服力的比方:微软(Microsoft)团队开发了 Visual Studio,而 JetBrains 团队为其开发了极其强大的插件 ReSharper。Uncle Bob 抛出问题引发思考:“Which of those teams can completely screw the other team which of those two teams is powerless and which of those two teams is has absolute power who could put the other out of business”。显而易见,作为宿主平台的 Visual Studio 拥有绝对生杀大权,微软甚至不需要动用任何资金竞争,只需调整接口设计就能轻易毁掉插件。同理,当业务规则作为宿主、Web 作为插件时,核心业务便牢牢掌握了架构的主动权。 ### [03:06:13](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11173s) 插件的脆弱性与 UI 作为业务规则的插件 Uncle Bob 首先用工具链生态打了个生动的比方:像微软或 IDE 厂商,完全可以通过每天随心所欲修改接口,把 ReSharper 这样的插件厂商逼入绝境——只要他们刚准备好发布,就发个通知说“抱歉,接口又改了,你们发不了版”,从而永远阻止其上线。这一现实揭示了一个核心架构法则:插件总是容易受到其所插入系统的伤害,而核心宿主在编译期对插件是完全免疫的。 既然如此,架构师必须深思:系统中难道不应该让某些部分对其他部分的诡异变更保持绝对免疫吗?答案毫无疑问是业务规则(Business Rules)必须对 UI 的奇异变动完全免疫。UI 天生就是为了频繁变更而设计的,其改动频率远高于业务逻辑。Bob 总结出一条清晰的架构法则:“频繁变动的东西应该做成插件;不常变动的东西应该被作为宿主插入。”因此,UI 必须作为业务规则的插件存在。在这种单向依赖关系下,UI 无论怎么胡乱改动,都绝对无法破坏业务规则。 ```mermaid flowchart LR accTitle: UI 作为业务规则的插件架构图 accDescr: 展示 UI 依赖并插入到业务规则宿主中,源码依赖单向指向核心业务规则。 subgraph Core["核心宿主(稳定)"] BR["业务规则<br>(Business Rules)"] end subgraph Plugin["插件(频繁变动)"] UI["用户界面<br>(UI / GUI)"] end UI -->|"源码依赖 (单向)"| BR ``` - 插件在编译期(compile time)对宿主是脆弱的,而宿主对插件具有天然的免疫力。 - 架构变更法则:变动频率高的模块应当成为插件,变动频率低的稳定模块应当成为被插入的目标。 - 业务规则与 UI 的关系界定:UI 的源码依赖必须单向指向业务规则,杜绝反向依赖。 > “Isn't it true that there are things in your system that you would like to be immune from other things in your system? In particular, wouldn't you want the business rules to be immune from the strange bizarre changes that occur to the UI?” —— Uncle Bob Martin --- ### [03:08:08](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11288s) 交互器、展现器与“极度愚蠢”的视图层 当交互器(Interactor)完成业务用例逻辑后,业务实体的交互即告结束,交互器会构建出一个响应模型(Response Model)。随后,交互器通过输出边界(Output Boundary)将响应模型传递给展现器(Presenter,类似于 MVP 或 MVVM 中的概念)。Uncle Bob 明确区分了响应模型与视图模型(View Model)的职责:响应模型包含纯粹的业务用例输出数据(例如原生的 Date 对象、Money 货币对象),这些数据并不能直接拿来呈现;而展现器的唯一职责,就是把这些原生数据翻译并格式化为视图模型。 在展现器的处理下,视图模型中存放的“除了纯字符串,什么都没有”。展现器负责调用日期格式化方法将 Date 转换成显示文本,将货币加上货币符号、小数位和括号,甚至判断负数货币并在视图模型中打上红字标记(如 `isRed = true`)。不仅如此,按钮显示的文字、按钮是否置灰的布尔值、菜单项的名字、排列顺序以及置灰状态,全部由展现器一手包办。Bob 指出,这种设计刻意剥离了屏幕呈现中的所有实质性逻辑,目标就是“让视图(View)变得极度愚蠢,愚蠢到你根本不需要去测它”。视图仅仅负责机械地把数据取出来贴在屏幕上,而整个呈现逻辑、格式化与状态标记都能在展现器中通过快速、独立的自动化测试得到 100% 的覆盖。 - 响应模型(Response Model):由交互器产生,存放原生业务数据类型(如 Date、Money 对象)。 - 视图模型(View Model):完全由格式化字符串及布尔值(置灰、变红等状态)构成的平铺数据结构。 - 展现器(Presenter):承担全部屏幕呈现计算与格式转换,隔离界面与业务。 - 视图(View):无任何业务与计算逻辑,无需编写复杂单元测试,仅凭人工视觉校验;若要测视图,只需单向传入一个模拟好的 View Model,完全解耦后端链路。 > “The goal behind that is to make the view so stupid you don't have to test it.” —— Uncle Bob Martin --- ### [03:11:28](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11488s) 严苛的单向源码依赖与独立部署 Uncle Bob 展示了从交互器向左延伸的完整架构边界图,并严正强调边界上的所有箭头方向。他声称,所谓“所有箭头都指向单一方向”,绝非仅指宏观的几个大型架构对象,而是“每一个 import、每一个 `#include`、每一个哪怕最微不足道的源码级引用”,都必须严苛地保持同一方向。 这样做的根本目的,是为了实现真正的物理级独立部署。研发团队可以将核心业务逻辑打包成一个单独的 DLL、JAR、Gem 或共享库(Shared Library),而将 GUI 界面代码打包成另一个完全分离的发布包。当界面发生变化时,开发者仅需重新编译 GUI 分支的代码,重新打出 GUI 的 JAR 包并部署即可,核心业务规则库完全不需要重新编译、更不需要重新构建。这就是插件架构带来的独立交付威力。 - 单向源码依赖涵盖代码中每一个文件引用、导入语句与头文件包含。 - 交付产物解耦:核心业务与 GUI 分别构建为独立的二进制/包产物(DLL/JAR/Gem)。 - 部署独立性:UI 的升级与重构不会触发核心业务模块的重新编译与重新部署。 > “Every arrow, every import, every pound include, every source code reference no matter how trivial must maintain this pointing in single direction.” —— Uncle Bob Martin --- ### [03:12:46](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11566s) 数据库高僧的诞生与“数据资产”的营销骗局 Uncle Bob 切换了话题,展示了一张数据库居于正中心、所有应用系统如仆从般环绕膜拜的传统架构图。他用戏谑的口吻调侃道:“数据库是所有应用的伟大神明吗?其他应用都像卑微的小黄人一样在周围顶礼膜拜?”他指出,唯一能从这种架构视角获益的只有 DBA(数据库管理员),因为他们可以借此稳坐神坛、扮演高高在上的“高僧祭司”。 随后 Bob 揭示了 DBA 这个工种的诞生历史。在早期计算机时代根本没有 DBA 这个职位,直到 Oracle 等商业数据库公司为了销售软件,想出了天才般的商业营销方案。他们走向企业的 CEO 们,抛出一个当时前所未闻的概念:“你在保护你的‘数据资产’(Data Assets)吗?”CEO 们诚惶诚恐,Oracle 便顺理成章地表示:要使用我们的系统来保护数据资产,企业必须重组组织架构,设立专职人员掌管数据资产——于是自 20 世纪 70 年代起,DBA 岗位席卷整个 IT 行业,他们独占数据库钥匙,掌控至高权力,让数据库在软件架构中被无限拔高。 - 数据库中心主义的实质:将存储机制置于系统正中心,把所有业务应用降级为外围附庸。 - DBA 工种的历史起源:20 世纪 70 年代由 Oracle 等数据库厂商通过“数据资产”营销策略创造的产物。 - 组织异化:厂商通过兜售专有系统迫使客户企业设立专属权力阶层(DBA)。 > “And companies like Oracle back when Oracle actually had to compete with people... they went to CEOs and they said to the CEOs, ‘Are you protecting your data assets?’” —— Uncle Bob Martin --- ### [03:15:03](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11703s) 数据库只是 IO 细节与 SQL 的历史代价 Uncle Bob 尖锐地指出,将数据库捧上神坛是极其讽刺的,因为“数据库归根结底只是个 I/O 设备”。数据本质上是存储在磁盘上的二进制位(Bits),数据库虽然提供了搜索、索引、报表等丰富特性,但它本质上只是框架和工具,绝不是任何系统的架构中心。正如 Ruby on Rails 错误地将 Web 和数据库同时推向系统中心一样,把数据库当成核心是严重的架构设计失误。程序员的核心天职是“抽象”与“管理细节”——将细节与通用的核心业务逻辑严格剥离,而数据库仅仅是一个可以替换的技术细节。 Bob 回顾了存储技术的发展:早在关系型数据库出现前,系统就拥有 Unix 目录结构、分层数据库等多种持久化方案。而在过去 30 年里,业界却近乎偏执地将一切装入“表结构”(Table Structure)中,依靠关联表维护指针,并采用了一种“极其糟糕的文本语言——SQL”。他指出,让数据访问语言变成文本并在代码内部动态解释执行,是造成全行业灾难性安全漏洞(SQL 注入)的罪魁祸首。而这类庞大复杂的数据库引擎之所以存在,根本原因不是它有架构优越性,而是因为“磁盘操作极其令人头痛”。为了让大家理解这一点,Bob 开始向台下听众追问有谁写过磁盘驱动程序,并回忆起那些由多个涂有磁性涂层的扁平盘片堆叠在一起的早期物理硬盘结构。 ```mermaid flowchart TD accTitle: 核心抽象与存储细节分离架构 accDescr: 业务用例与领域实体属于通用的核心抽象,关系型数据库与 SQL 仅作为外部 IO 细节。 subgraph CoreDomain["核心抽象(系统本质)"] UC["用例与业务逻辑<br>(General Abstraction)"] end subgraph IODetail["IO 细节(可替换工具)"] DB["数据库引擎<br>(SQL / RDBMS / NoSQL)"] Disk["磁盘与物理驱动<br>(Platters / Hardware)"] DB --> Disk end UC -.->|"依赖倒置 / 解耦"| DB ``` - 数据库的本质定义:数据库只是外部 I/O 工具与细节,绝非系统的架构中心。 - 架构师的天职:抽象化(Abstractors)与细节管理者(Detail Managers),负责分离细节与通用规则。 - SQL 语言的历史代价:基于文本解释执行的设计理念引发了长达数十年的安全注入隐患(如在日期字段输入 `SELECT *` 即可发动攻击)。 - 关系型数据库框架存在的物理现实:掩盖底层多盘片物理磁盘读写驱动的复杂性与痛点。 > “The database is a detail, and this should be burned into your mind. You're a programmer, right? And the programmer's job is to separate the things that are important from the things that are not.” —— Uncle Bob Martin ### [48:12](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=2892s) 机械硬盘的历史负担与数据库的起源 Uncle Bob 详细回顾了早期的机械硬盘(HDD)物理结构及其操作方式。硬盘依赖主轴旋转盘片,转速最初为 3600 RPM(每秒约 60 次),后来缩小尺寸后提升至 7200 RPM。读写磁头在盘片表面内外移动,在盘片表面划出密集的同心圆磁化点记录数据,半毫米间距内可排布数百乃至上千个圆圈,这些圆圈被称为磁道(Tracks)。多个叠放盘片上半径相同的一组磁道则合称为柱面(Cylinders)。在早期,向磁盘写入一个字节需要直接控制寻道,程序员甚至得处理驱动器的物理动量,发送加速和减速指令等“可怕的底层细节”;即使后来简化为“移动到 720号柱面”这样的高级指令,寻道延迟(Seek Time)依然高得惊人。在 60 年代、70 年代至 80 年代中期,平均寻道时间普遍在 100 毫秒左右。 除寻道时间外,系统还必须等待盘片转动所带来的旋转延迟(Rotational Latency)。并且底层硬件无法单独读取某一个具体字节,必须一次性读入一整个扇区(通常是 4KB)。系统等数据旋转到磁头下方后,把 4KB 扇区全部读入内存,程序才能从中筛选出所需的那一个字节。Uncle Bob 直言这一过程异常痛苦,而数据库的本质使命正是为了消除这种痛苦。数据库构建了索引、表格结构及复杂的检索框架,核心就是为了“让数据在极其糟糕的旋转磁性表面进出变得更高效”。 - 早期磁盘转速:3600 RPM(约 60 转/秒),后期提升到 7200 RPM。 - 磁道(Tracks)与柱面(Cylinders):同心圆为磁道,多个垂直重叠盘片上相同半径的磁道集合为柱面。 - 寻道延迟(Seek Time):60 至 80 年代中期的典型平均延迟约为 100 毫秒。 - 扇区读写限制:即便只需要 1 个字节,也必须读取整个 4KB 扇区后再从中提取。 - 数据库的最初职责:“数据库的作用就是消除这种痛苦,使得从这种糟糕的旋转磁表面读取和存入数据变得高效。” --- ### [50:42](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3042s) 旋转磁盘的消亡与持久化内存(Persistent RAM)的兴起 Uncle Bob 询问在场听众是否有人的笔记本电脑里还装有机械硬盘,全场无人举手。他调侃道,如果五年前问这个问题,绝大多数人还在用机械硬盘,只有两三个人没有,而全场都会嫉妒并“讨厌”他们;如果是十年前问,绝对百分之百都是机械硬盘。如今,不仅个人电脑,连大型服务器集群中的机械磁盘也正在彻底退出历史舞台,取而代之的是巨大的线性随机存取内存(RAM)。当底层介质从机械盘变成直接寻址的线性 RAM 时,系统的存储逻辑迎来了颠覆性的转变。 Uncle Bob 提出了一个发人深省的问题:如果我们拥有 32 位或 64 位寻址空间的持久化内存(Persistent RAM),编程习惯将发生何种改变?现代程序员使用 SQL 查询数据库,把数据拉入类似 ADO 的表格结构中,随后第一件事就是将表格数据拆解并重构为链表(Linked Lists)、哈希表(Hash Tables)和二叉树(Binary Trees)。“我们是程序员,我们不喜欢这些愚蠢的表。这些表太蠢了;我想使用指针,我不想使用关系表。”如果存储介质本身就是持久化内存,数据完全可以直接以二叉树、链表或哈希表的形式原地存储,根本不再需要关系型表的转换。 Uncle Bob 直言不讳地指出:“如果我是 Oracle,我会感到非常恐慌,因为我赖以生存的理由正在从我脚下蒸发。”尽管数据库技术拥有巨大的社会惯性,很多拥趸依然极力维护其地位,声称背后有严密的关系代数与数学体系,但磁盘的淘汰必然导致传统数据库的生存土壤逐步瓦解。这也是 NoSQL 运动爆发的核心推动力。他告诫工程师,切勿在当前为了短期方便而与某款关系型数据库深度绑定,否则十年后当整个行业转向更优美的持久化形态时,遗留系统将被锁死在落后架构中无法脱身。 ```mermaid flowchart LR accTitle: Data Structure Transformation accDescr: Diagram showing how SQL table data is unpacked into in-memory data structures like hash tables and trees. subgraph Storage["底层存储形态"] direction TB Disk["旋转机械盘 / RDBMS 表格"] PRAM["持久化内存 (Persistent RAM)"] end subgraph AppMemory["应用内部数据结构"] direction TB DS1["哈希表 (Hash Tables)"] DS2["二叉树 (Binary Trees)"] DS3["链表 (Linked Lists)"] end Disk -->|"SQL 查询与解包开销"| DS1 Disk -->|"SQL 查询与解包开销"| DS2 Disk -->|"SQL 查询与解包开销"| DS3 PRAM -.->|"直接原生持久化存储"| DS1 PRAM -.->|"直接原生持久化存储"| DS2 ``` --- ### [54:26](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3266s) 数据库边界与实体网关(Entity Gateway) 为了避免系统被特定数据库深度绑架,架构中必须划出一道清晰的隔离边界。Uncle Bob 强调,数据库应当仅仅作为业务规则的一个“插件”(Plugin)。他明确反对将业务逻辑写进数据库的存储过程(Stored Procedures)中:“不要把 SQL 当作业务规则来实现。”虽然存储过程在维护数据完整性或反向指针方面有其价值,但用它来实现诸如邮件合并(Mail Merge)等纯业务逻辑是严重的架构反模式。 在这道边界上,交互器(Interactors)不直接与数据库通信,而是依赖一个名为“实体网关”(Entity Gateway)的标准接口。该接口定义了用例层所需要的所有数据操作方法(如根据日期获取订单列表的 `getOrdersSince(date)` 以及增删改查等)。实体网关的实现类位于边界外层,由它包含具体的 SQL 语句,并通过底层 API 将命令发送给数据库。从依赖方向上看,底层数据访问层依赖于位于核心业务层的网关接口,数据库由此被驯化为业务规则的外置插件。业务规则层看不到任何一行 SQL,完全实现了业务核心与数据存储方案的解耦。 ```mermaid flowchart TD accTitle: Database Boundary and Entity Gateway Architecture accDescr: Flow diagram showing the architectural boundary between business rules and database via the Entity Gateway interface. subgraph BusinessCore["业务规则层 (高层策略)"] direction TB Interactor["用例交互器 (Interactor)"] GatewayInterface["实体网关接口 (Entity Gateway)<br/>+ getOrdersSince(date)<br/>+ create() / update() / delete()"] Interactor --> GatewayInterface end subgraph Boundary["=== 架构边界 (插件边界) ==="] end subgraph Implementation["底层实现层 (插件细节)"] direction TB GatewayImpl["实体网关实现类 (Gateway Implementation)<br/>包含具体 SQL 代码"] DB[(数据库系统 / RDBMS)] GatewayImpl -->|"API 通信"| DB end GatewayImpl -.->|"实现 (依赖倒置)"| GatewayInterface ``` - 核心金句:“业务规则应该把数据库当成插件;数据库应该插入到业务规则中,业务规则不应该依赖数据库。” - 实体网关(Entity Gateway):纯抽象接口,仅向用例层暴露语义化数据访问契约,绝不泄露 SQL 概念。 - SQL 集中隔离:所有 SQL 语法和数据库驱动 API 均被密封在实体网关实现类中,外界完全不可见。 --- ### [56:31](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3391s) 测试套件的设计底线:速度、微秒级打桩与可部署信心 拥有实体网关边界的另一大决定性优势,是解决了测试套件(Test Suite)运行迟缓的问题。传统测试若直接穿透到真实数据库,由于 I/O 和事务开销,往往非常缓慢。Uncle Bob 指出,借助实体网关这一插件边界,单元测试可以用轻量级的打桩组件(Stubs)瞬间替换掉真实数据库。测试桩仅需在被调用时返回预设数据,能让全套单元测试在微秒级别内执行完毕。 Uncle Bob 强调测试套件的核心设计指标是“快速执行”:“如果测试执行不快,你就不会去执行它。”一旦全套测试耗时超过几分钟,开发者就会失去就地运行的耐心,进而将其推迟给夜间任务、持续集成(CI)服务器或周末构建系统去跑。这直接破坏了反馈闭环——因为唯有当测试能在两三分钟内迅速跑完时,开发者才能即时依据测试结果做出战略决策。 全套测试通过后的理想决策是什么?Uncle Bob 明确指出:测试全绿,即可发布(“If the test suite passes, ship it.”)。当台下爆发笑声时,他直击痛点:大家之所以发笑,是因为深知自家的测试套件覆盖率不足,不敢在通过后直接部署上线。但如果测试通过不能带来上线信心,那么“测试通过”这一状态本身就毫无价值。一个合格测试套件的终极目标应当是完全承担起 QA 的职能,打造“测试通过即部署”的最高工程信任。 当拥有一套覆盖全面、能在数秒内跑完且“托付生命般信任”的测试套件时,程序员将获得一项极其强大的超能力——持续重构与清理代码(Clean Code)。程序员在屏幕上看到丑陋代码时,可以放手改造,按一下按钮看到绿灯,再清理一点,再按一下;若一旦改错变红,立刻撤回排查。唯有在绝对可信的高速测试网保护下,代码整洁才不会沦为停留在口头的空想。 - 测试运行时间红线:超过几分钟的测试套件会被程序员潜意识抛弃并推迟给后台 CI。 - 测试打桩收益:使用 Stubs 替换数据库访问,能将测试耗时压缩至微秒级。 - 质量目标:“测试套件的目标之一是成为实际的 QA 流程:测试通过即发布;我的意思就是字面上的,只要测试通过就立即部署。” - 代码重构的前提条件:唯有运行极其迅速且值得性命相托的测试,才能赋予程序员无所畏惧地清理丑陋代码的底气。 ### [01:00:12](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3612s) 遗留代码的腐化与测试套件的力量 Uncle Bob 描述了程序员面对糟糕代码时的经典心理:“你脑子里的下一个念头就是‘我绝不碰它’,因为你知道一旦碰了它,你就会把它搞坏;如果你搞坏了它,它就成了你的责任”。程序员于是选择对糟糕代码退避三舍。Uncle Bob 直言,如果团队对代码都是这种态度,那么代码唯一的宿命就是持续腐化:即使后续不断增加新功能、修复 Bug,做的每一件事也只会让代码越变越糟,因为没有人敢去重构它。 摆脱这一恶性循环的唯一方法,就是拥有一套值得信赖的自动化测试套件。一旦有了这套保护网,改进代码就会变得轻松且毫无后顾之忧,甚至不耗费任何心智成本。将数据库等外部细节解耦后,业务规则的测试便可以脱离庞大的外部依赖运行,在几秒钟或一分钟内全部执行完毕。 - “If you touch it you will break it, if you break it it becomes yours, so you walk away from the code.” —— Uncle Bob Martin - “The only way to make it better is to have a suite of tests that you trust, and then it's easy.” —— Uncle Bob Martin --- ### [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) FitNesse 案例:如何将数据库决策推迟到底 Uncle Bob 分享了一个亲身经历的故事。大约在 2002 年,他和儿子启动了名为 FitNesse 的开源项目。FitNesse 本质是一个集成了 Ward Cunningham 于 1999 年开发的框架的 Wiki 系统,允许用户用 HTML 表格编写验收测试,运行后表格单元格会直接变绿或变红。在构思系统之初,他们需要保存 Wiki 页面,当时唯一的开源数据库选择是 MySQL。正当他们准备设计 Schema、安装配置 MySQL 时,有人提出可以先暂缓搞数据库,因为还有一个核心问题未解决:将 Wiki 标记语言解析并渲染成 HTML。 团队于是创建了一个名为 `MockWikiPage` 的测试替身,派生自 `WikiPage` 基类,将其 `save()` 方法设为空实现,集中精力实现 `toHTML()`。这项工作耗费了大约三个月。随后进入测试驱动开发(TDD)阶段,测试需要从存储层获取多个页面。正当团队又打算安装 MySQL 时,有人建议将所有页面保存在内存中的哈希表(Hash Table)里。团队采纳了这一做法,并以此方式开发了整整一年:虽然系统无法持久化存储,但在持续的测试编写和通过过程中,核心功能和测试运行逻辑全部顺利成型。 ```mermaid flowchart LR accTitle: FitNesse 持久化演进过程 accDescr: 展示 FitNesse 项目在架构设计中多次推迟引入关系型数据库的具体演变步骤 subgraph Phase1 ["第一阶段:内存模拟"] A["MockWikiPage (save为空操作)"] --> B["内存 HashTable (运行1年)"] end subgraph Phase2 ["第二阶段:平铺文件"] B --> C["Michael Feathers 的文件系统实现"] end subgraph Phase3 ["第三阶段:插件化对接"] C --> D["MySQL 插件 (仅作为外部插件存在)"] end ``` 一年后,系统需要测试启动流程(从外部文件载入页面),必须具备持久化能力。此时 Michael Feathers 提出直接把哈希表数据序列化并写入简单的文件系统中。Michael 仅用了一天时间便将这套轻量级文件系统搞定。凭借这套文件存储方案,FitNesse 不仅跑得飞快,还获得了一个意外的巨大优势:用户可以直接将测试用例签入版本控制系统(Source Code Control)。又过了三个月,他们意识到这套文件系统完全满足需求,“将数据库的决定彻底踢出了项目之外”。 直到一年后,某家企业客户坚持要求必须将数据存入数据库,理由是“企业所有数字资产必须进入数据库”。Uncle Bob 团队向客户展示了插件接口结构,指出只要编写一个 `MySQLPage` 替换掉原本的 `FileSystemPage` 即可。该客户仅用一天时间就写好了 MySQL 插件。FitNesse 后来曾将此作为插件一起打包发布,但由于文件系统方案过于好用,最终没有任何人真正使用 MySQL 插件,甚至连提出需求的那家客户最后也弃用了它。 - “Good architectures allow major architectural decisions to be deferred. The job of an architect is not to make decisions; the job of an architect is to defer decisions as long as possible.” —— Uncle Bob Martin - “A good architecture maximizes the number of decisions not made, and you achieve that by using a plug-in model.” —— Uncle Bob Martin - “If the first thing you type at the beginning of your project is `rails`, you've lost the war.” —— Uncle Bob Martin --- ### [01:08:41](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4121s) 依赖注入与防范框架绑架 针对系统架构中的插件模型,Uncle Bob 谈到了依赖注入(Dependency Injection,DI)框架的滥用现状。许多团队过度依赖注入框架,在庞大臃肿的 XML 配置文件或满天飞的 `@Autowired` 注解中迷失,系统发生崩溃时甚至根本不知道问题隐藏在哪一行注入配置里。 Uncle Bob 强调,依赖注入本身是一个极好的经典概念,但开发者绝不应该把所有东西都交给容器注入。系统真正需要注入的仅仅是少数工厂(Factories)或者特定的策略对象(Strategies),整套系统注入的对象数量通常不应超过一打或两打(12 到 24 个)。此外,依赖注入的位置必须位于外围的插件层,业务用例(Use Cases)绝不能感知到注入框架的存在。 谈及与框架作者的关系时,Uncle Bob 语出惊人地提醒道:“框架作者会坑你的”,因为框架作者维护的是他们自己的利益,而非你的利益。开发者在选型时对框架做出了巨大的架构承诺,甚至把整个业务与框架深度绑定;而框架作者对使用者毫无承诺可言,随时可能按照自己的意愿大改 API 或调整设计,让深度绑定的团队承受巨大的升级和维护代价。 - “You don't want your use case code to know about your injection framework. You don't want your use case code to know much about any framework. Why? Because framework authors are out to screw you.” —— Uncle Bob Martin - “You make a tremendous commitment to that framework; the author makes no commitment to you at all.” —— Uncle Bob Martin ### [01:10:51](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4251s) 警惕框架陷阱与保持一臂之距 Uncle Bob 警告开发者切勿对框架产生盲目的依赖与绑定。框架作者更新或重构他们的工具时,根本不会在乎你的业务代码会遭遇什么痛苦——他们会摆出一副“算你们倒霉,反正我改了框架,而你们已经被死死绑定在我身上了”的傲慢姿态。一名优秀的软件开发者必须以极其审慎的眼光审视各类框架,清晰认识到框架虽然功能强大、能带来诸多便利,但往往伴随着极其高昂的代价。因此,开发者固然要利用框架的威力,但核心原则是必须与框架“保持一臂之距”(keep them at arm's length),绝不能任由框架侵入业务核心。 尤为关键的一点是,开发者千万不能盲目照搬框架作者提供的示例代码。框架作者在编写教程和示例时,巴不得将所有代码都与他们自己的框架紧密耦合在一起,因为那是他们推销自己作品的方式;但作为应用构建者的你,绝对不能这么做。以 Ruby on Rails 为例,如果在早期开发中将业务逻辑与 Rails 深度绑定,日后一旦 Rails 成为项目的累赘或瓶颈,系统将根本无法从中脱身。正确的架构设计必须确保:即便未来某个框架变成了一种负债,系统也依然保留随时将其剥离和替换的自由。正如 Uncle Bob 所强调的: > “A good software developer looks very carefully at Frameworks and understands that Frameworks come with benefits but also with costs and those costs can be pretty high so you want to take your Frameworks you use you want to use them just they're powerful but you also want to kind of keep them at arms length and and the most important thing about that is you do not want to follow the examples that the framework author wrote for you cuz the framework author is perfectly comfortable coupling to his framework but you should not.” ```mermaid flowchart TD accTitle: 解耦框架与业务核心的架构原则 accDescr: 展示将业务逻辑与外部框架保持一臂之距以避免被绑定的设计结构。 subgraph Core["核心业务层(受保护的领域)"] UC["用例 (Use Cases)"] E["业务实体 (Entities)"] UC --> E end subgraph FrameworkBoundary["防腐层 / 接口边界"] IF["抽象接口 / Boundary Interfaces"] end subgraph FrameworkLayer["外层框架(细节与工具)"] FW["Web 框架 / ORM / 交付机制\n(如 Rails, Spring 等)"] end FW -->|"实现 / 依赖注入"| IF Core -->|"定义依赖"| IF ``` - **框架成本认知**:强大的外部框架绝非免费午餐,高度绑定的框架一旦发生重大破坏性变更,业务系统将彻底沦为附庸。 - **避免照搬示例**:框架官方文档的示例代码通常具有极高侵入性,仅为了展示框架特性,绝不可作为生产级业务架构的范例。 - **架构解耦目标**:良好的系统架构应当让框架始终处于系统边缘,确保在技术栈演进或框架过时时具备随时替换的能力。 --- ### [01:12:15](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4335s) 现场问答:存储过程与业务实体的本质 在演讲接近尾声时,Uncle Bob 亮出了包含个人联系方式的幻灯片,并半开玩笑地催促进度,坦言时间已经不早,自己现在满脑子只想喝杯啤酒,随即询问台下是否有观众提问。一位现场听众抓住机会提出了一个关于数据库架构的问题:在许多团队的实践中,大家会在仓储层(Repository)下方广泛使用存储过程(Stored Procedures),以此将底层具体的数据表结构完全从程序员的视线中抽象出去;这样程序员既无法直接操作物理表,也无法随意执行原始 SQL 查询,不仅程序员写得省心,DBA 也能借此稳固地捍卫他们的数据领域。 针对这一提问,Uncle Bob 给予了充分的肯定,并借此补充说明了一个之前未曾详述的核心架构概念:业务实体(Entities)绝不是数据库物理表的简单镜像或实例。在真实的整洁架构中,一个业务实体往往是由多张不同的数据表联合构建而成的聚合体,并且其自身通常还封装了一系列相关的领域操作与业务规则。虽然受限于时间他未进一步深入展开技术细节,但他由衷认可了提问者隔离底层表结构的设计思路,并在观众热烈的掌声与“去喝啤酒”的欢快呼声中为全场演讲画上了圆满句号。正如 Uncle Bob 所指出的: > “One of the things I didn't talk about there was that the entities are not instances of database tables they're usually constructions from many database tables and maybe some operations as well.” - **存储过程与职责隔离**:在仓储层之下使用存储过程不仅可以隔绝开发人员直接触碰物理 SQL,还能在 DBA 维护数据规范与研发团队交付业务功能之间建立清晰边界。 - **实体与数据表映射误区**:实体专注于表达业务领域概念和行为规则,绝不等于单张关系型数据库表(ORM 模型)的简单对应。 - **轻松幽默的尾声**:演讲在“喝啤酒”的极客式幽默氛围中收尾,体现了技术分享不仅关乎严苛的工程规范,更是一种社区聚会的轻松文化。 ## Notable quotes > [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) Good architectures allow major architectural decisions to be deferred. The job of an architect is not to make decisions; the job of an architect is to defer decisions as long as possible. — Uncle Bob Martin > [01:01:16](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=3676s) If the first thing you type at the beginning of your project is `rails`, you've lost the war. — Uncle Bob Martin > [03:05:03](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=11103s) The database is a detail, and this should be burned into your mind. You're a programmer, right? And the programmer's job is to separate the things that are important from the things that are not. — Uncle Bob Martin > [01:08:41](https://www.youtube.com/watch?v=o_TH-Y78tt4&t=4121s) You make a tremendous commitment to that framework; the author makes no commitment to you at all. — Uncle Bob Martin ## Mentioned Uncle Bob Martin, Ivar Jacobson, Trygve Reenskaug, Ward Cunningham, Michael Feathers, Ruby on Rails, Spring, Hibernate, Smalltalk, Visual Studio, JetBrains, ReSharper, Oracle, MySQL, FitNesse ## Video description The Principles of Clean Architecture by Uncle Bob Martin (@unclebobmartin) Robert C. Martin, aka, Uncle Bob has been a software professional since 1970 and an international software consultant since 1990. In the last 40 years, he has worked in various capacities on literally hundreds of software projects. In 2001, he initiated the meeting of the group that created Agile Software Development from Extreme Programming techniques and served as the first chairman of the Agile Alliance. He is also a leading member of the Worldwide Software Craftsmanship Movement - Clean Code. He has authored "landmark" books on Agile Programming, Extreme Programming, UML, Object-Oriented Programming, C++ Programming and most recently Clean Code and Clean Coder. He has published dozens of articles in various trade journals.He has written, directed and produced numerous "Code Casts" videos for software professionals.Bob is a regular speaker at international conferences and trade shows.Mr. Martin is the founder, CEO, and president of Uncle Bob Consulting, LLC and Object Mentor Incorporated. Uncle Bob has published dozens of articles in various trade journals, and is a regular speaker at international conferences and trade shows. Mr. Martin has authored and edited many books including: • The Clean Coder • Clean Code • Agile Software Development: Principles, Patterns, and Practices. • Designing Object Oriented C++ Applications using the Booch Method • Agile Principles Patterns and Practices in C# • Patterns Languages of Program Design 3 • More C++ Gems • Extreme Programming in Practice • UML for Java Programmers http://www.meetup.com/Norfolk-Developers-NorDev/events/223533655/ [Source](https://www.youtube.com/watch?v=o_TH-Y78tt4)

Source quotes

[61:16] Uncle Bob Martin: Good architectures allow major architectural decisions to be deferred. The job of an architect is not to make decisions; the job of an architect is to defer decisions as long as possible. [61:16] Uncle Bob Martin: If the first thing you type at the beginning of your project is `rails`, you've lost the war. [185:03] Uncle Bob Martin: The database is a detail, and this should be burned into your mind. You're a programmer, right? And the programmer's job is to separate the things that are important from the things that are not. [68:41] Uncle Bob Martin: You make a tremendous commitment to that framework; the author makes no commitment to you at all.

Source quotes

[66:11] Uncle Bob Martin: A good architecture maximizes the number of decisions not made and you achieve that by using a plug-in model [70:51] Uncle Bob Martin: You should architect your system so that if Rails becomes a liability, it is not impossible to break away from it. [70:51] Uncle Bob Martin: you do not want to follow the examples that the framework author wrote for you, because the framework author is perfectly comfortable coupling to his framework, but you should not.

Original transcript

[0:01] um biological parents do you [0:08] have generally too yeah actually always [0:11] too it's one of those laws of nature you [0:14] already nor well even in [0:19] norfol it's going to be [0:22] two but how many biological grandparents [0:26] do you [0:28] have four [0:31] generally almost always four yes four [0:33] would [0:34] be very close to the appropriate number [0:38] um great [0:42] grandparents not to exceed [0:46] eight [0:48] yes this is a sequence we know we [0:51] programmers isn't it this is powers of [0:53] two uh great great grandparents would [0:58] be well if you've lived in in the same [1:00] area for a long time and there are [1:02] cousins that live nearby but still [1:05] probably 16 although there's a good [1:08] chance it's something less than [1:11] 16 but let's not dilly dally around this [1:14] region let's go out 10 Generations 200 [1:18] years now that's 2 to the 10th so the [1:22] maximum would be 1024 1,24 possible [1:27] ancestors uh but probably not in fact [1:32] really good chance that it's not 1024 [1:34] cuz the the whole cousin thing starts to [1:36] really take effect at that point and if [1:39] we were to go back another 200 years [1:42] right to um 400 years ago would be what [1:46] 16 [1:48] 1715 is that right did I do that right [1:51] 1715 or 1615 it would be [1:54] 1615 go back to 1615 400 years ago you'd [1:59] have a million possible ancestors [2:01] possible but but it's no way near it [2:04] almost certainly not even close to a [2:06] million and if you were to go back [2:08] another 200 years to uh [2:12] 1415 the year the Magna no that's not [2:14] the year the Magna cartter was signed [2:16] that was the year uh well that was [2:18] somewhere before Columbus sailed [2:20] actually um you would have a billion [2:23] possible [2:24] ancestors although there probably [2:27] weren't a billion people on the planet [2:30] or maybe just a billion people on the [2:32] planet and one more 200-year jump would [2:35] take you back to the year the Magna [2:37] cartter was signed in 12:15 and the [2:39] maximum possible number of ancestors [2:41] would be a [2:42] trillion but there haven't been a [2:44] trillion people on the planet even [2:46] including all the dead ones so this [2:49] power of two thing doesn't work it only [2:52] works in the first couple of generations [2:55] and then it sort of Curves around you [2:57] can imagine this this growth curve of [2:59] your possible ancestors and then the [3:01] real ancestors kind of curve around and [3:04] then another effect takes place let's go [3:07] back 600 years all right there were [3:10] people alive back then and they had [3:12] children but if you were to look at [3:14] their lines their [3:16] preny some of them died out some of them [3:20] some of their children didn't make it to [3:23] 2015 the lines just died out and in fact [3:26] every line has a positive probability of [3:29] dying out and therefore if you go back [3:32] far [3:33] enough you eliminate every line except [3:37] one [3:39] so you and I would have a single person [3:44] somewhere in the deep dark past single [3:46] pair of people one man one woman who [3:49] were the [3:52] parents of everybody in the [3:55] room our uh little private Adam and Eve [3:59] so to speak and if we did this analysis [4:02] with everyone on the planet we'd have to [4:05] go back further in time but we could [4:07] find a single man and a single woman who [4:11] were the sole [4:13] parents of everyone alive on the [4:17] planet they didn't know each other [4:20] probably they probably didn't live [4:22] within a few hundred miles of each other [4:24] they very likely did not live within a [4:26] few thousand years of each other but [4:29] these two individuals are the mother and [4:32] father of everyone alive and this is a [4:34] mathematical [4:36] certainty what can we know about them [4:39] well it turns out that there are certain [4:41] molecules in our body that must have [4:45] come from them and these molecules are [4:48] relatively undisturbed most of our DNA [4:51] gets chopped up into little bits and [4:53] mixes left and right and and you [4:55] couldn't possibly trace it out but there [4:58] is a strand of DNA in your body which [5:02] comes only from your mother and it lives [5:06] in a little organel in every one of your [5:09] cells this this this organel is called [5:12] the [5:13] mitochondria not the midi [5:16] choran the mitochondria mitochondria is [5:20] a cute little hot dog shaped um organel [5:24] which takes in glucose and oxygen and [5:27] emits water carbon di oxide and energy [5:31] this is the little uh little device [5:34] within our cells which does the chemical [5:37] reaction of respiration of life and it [5:41] reproduce it has its own DNA and it [5:43] reproduces on its own schedule uh it's [5:45] almost a little bacteria in fact it very [5:48] closely reme resembles a little bacteria [5:52] um apparently a billion and a half years [5:55] ago one cell swallowed another one and [5:59] failed to digest it and the swallowed [6:03] cell thought this is nice in here and It [6:06] produced energy and the swallowing cell [6:10] thought gee it's nice to have someone [6:11] produce energy for me and a symbiosis [6:14] was set up that led to you and [6:19] me now if you were to take your [6:23] mitochondrial DNA the DNA from your [6:25] mitochondrial which you got from your [6:28] mother not from your father because egg [6:30] cells have mitochondria and sperm cells [6:33] don't so if you were to take your [6:36] mitochondrial DNA and compare it to your [6:38] mothers it would be identical almost [6:41] certainly and if you were to compare it [6:43] to your maternal grandmother it would be [6:45] identical almost certainly if you were [6:47] to go back far enough in time however [6:49] there is a small chance that there would [6:52] be a mutation there are lots of possible [6:54] mutations a cosmic gray can come along [6:56] and hit hit an egg cell at just the [6:58] right moment and flip one little DNA uh [7:01] nucleotide and most of the time when you [7:03] flip a nucleotide it doesn't do any [7:05] damage cuz the genetic code is worked [7:08] out so that single bit errors uh don't [7:11] accumulate so that's interesting we [7:14] could probably lay them aside and see uh [7:16] how long it took a mutation to take [7:18] place if you were to take your [7:21] mitochondrial DNA and lay it next to [7:25] mine we might see a few differences and [7:28] the number of differences would tell us [7:31] how soon we had a common [7:34] mother and we could do this analysis [7:36] with everyone on the planet you don't [7:38] have to do everyone just do a spot check [7:40] you know a few people from here and [7:41] there and whatever and you could build a [7:43] tree of um the descendants of everybody [7:48] and you could tell uh how far back in [7:50] time we all had common mothers and who [7:53] their mothers were and how long back in [7:55] time their mothers were and how long [7:56] back in time their mothers were all the [7:58] way back to the might mitochondrial eve [8:00] and you could even tell when and where [8:02] she lived and this work has been done of [8:05] course so the mitochondrial eve the [8:08] woman who is the mother of everyone [8:09] alive lived probably 120,000 years ago [8:14] in the east of Africa we know it's the [8:16] east of Africa because this is where the [8:19] mitochondrial DNA has the most diversity [8:21] it's had the most time to change and [8:24] mutate and spread if you go to Iceland [8:27] all the mitochondrial DNA is exactly the [8:29] same [8:30] but in the east of Africa it's all [8:32] really [8:33] different so she lived in the east of [8:36] Africa 120,000 years ago at a time when [8:40] the weather was [8:42] warm but about to get cold the last ice [8:46] age started shortly after that and only [8:49] ended 14,000 years [8:52] ago we can trace what happened to her [8:55] children we know that her children left [8:59] the east of Africa went North and then [9:01] moved to the West into Western Europe [9:03] and a completely different cohort moved [9:06] North and then went to the east into [9:07] Eastern Europe over the steps into [9:10] Russia and different different batch [9:12] entirely went around the oceans all the [9:15] way down into Australia there was [9:17] another batch that managed to leave [9:19] Russia and cross the bearing States [9:21] bearing Straits and go into North [9:23] America about 14,000 years ago and they [9:25] wound up at the tip of South America a [9:28] thousand years later [9:30] men in the [9:32] room you have a stretch of DNA that [9:34] comes from your father it is the Y [9:37] chromosome why chromosome only comes [9:39] from dad and mom's DNA does not mix with [9:43] it it is a hunk of pure testosterone [9:46] coming from your father and his father [9:48] and his father and so on a single [9:50] inheritance hierarchy going all the way [9:52] back to the Y chromosome adom who lived [9:56] 70,000 years ago at the he of the last [10:00] ice age in the east of Africa and we can [10:03] trace his the migration of his sons and [10:06] it's roughly the same not quite the same [10:09] oddly as the migrations of the [10:12] mitochondrial eve because after all they [10:14] had very different timings um but [10:17] roughly the same and actually the map of [10:19] these migrations is very very precise [10:21] and detailed there been a lot of [10:24] fascinating research done on this uh and [10:26] I find that very interesting that that [10:29] the history of the human race is written [10:32] in the molecules of our body but that's [10:36] not why we're here is it I need to talk [10:38] about architecture [10:42] instead [10:45] okay I guess it was about 8 years ago [10:48] now my son came to me uh and he had [10:50] written a Ruby on Rails [10:52] app and uh he showed it to me and this [10:55] is what I [10:58] saw and I looked at it and I immediately [11:01] recognized that oh yeah that's a Ruby on [11:02] Rails app and cuz you can tell right [11:04] it's got the app directory it's got the [11:06] controllers models views this is a Ruby [11:08] on Rails app anybody who's done rails [11:10] would look at that and go yep Ruby on [11:11] Rails [11:12] app and then it occurred to me for the [11:16] first time in many years it occurred to [11:18] me that wait why do I know that this is [11:21] a Ruby on Rails app I'm looking at the [11:23] high level directory structure why does [11:25] the high LEL directory structure of this [11:27] application tell me [11:30] the framework that I'm using why doesn't [11:32] it tell me what the application [11:35] does and that bothered me I thought wait [11:38] wait the highest level directory [11:40] structure in the system shouldn't it [11:42] tell me what the application does isn't [11:45] that what's important about this [11:47] application why is the framework [11:50] standing out in front of everything [11:53] hiding what this application is supposed [11:56] to do I thought well that's just not [11:58] right there's something wrong with that [12:00] because what is rails anybody do some [12:04] Ruby on Rails here y okay Paul you do [12:07] you do okay and you know the rest of you [12:09] probably use some other framework web [12:11] framework Ruby un rails is a web [12:14] framework I thought rubian rails is a [12:17] web framework why is it why is it so [12:20] obviously in front of everything when [12:23] the [12:25] web is an IO [12:27] device why would the web be considered [12:31] something so significant that it would [12:34] overshadow what the application does now [12:38] there's reasons for [12:39] that when the web came out some of you [12:43] may [12:44] remember when the web came out in the [12:46] late 90s and uh it changed [12:50] everything the internet is here and and [12:53] web is here and it changed everything it [12:57] took us close to 20 years to realize it [12:59] didn't change [13:00] anything that the web was just an IO [13:02] device like any other IO device um it [13:06] takes input from a user delivers output [13:09] to the screen okay it uses this odd HTML [13:12] language to do that but so what but you [13:16] remember if you remember those heady [13:18] days of the dotom era and everybody was [13:20] making a trillion dollars by putting up [13:23] a a Save a Pet website right and that [13:26] was the way things were until of course [13:28] every every body lost the trillion [13:30] dollarss and then we had programmers [13:32] holding out signs saying I will code for [13:34] food those were bad [13:38] days the web had dominated us to the [13:42] point where we had thought that the web [13:46] was an architecturally significant [13:48] element of a system but of course it's [13:50] not it's an IO device and rails and [13:55] every other web framework out there took [13:57] an architectural significant position in [14:00] all of our systems so to the extent [14:03] where you would describe your [14:06] application as a rails app why would you [14:09] do that why would you well I'm working [14:11] on a rails app no you're working on an [14:13] accounting app you may happen to be [14:15] using rails but you're working on an [14:17] accounting app I'm I'm using spring what [14:20] is the architecture of your application [14:21] well architecture of my application is [14:23] that we're using Java and spring and uh [14:26] hibernate and that's not an architecture [14:30] that's a bunch of [14:31] tools what is the architecture of your [14:33] application and I started thinking about [14:36] this what do architectures tell us so I [14:40] I got some blueprints out and I looked [14:42] at a library blueprint now look at that [14:44] blueprint that's a library you can tell [14:47] it's a library it's got bookshelves it's [14:49] got desks it's got computers sitting [14:51] over here it's got tables that you can [14:53] sit on that looks like a library the [14:55] blueprint is trying to tell you I am a [14:57] library and if you you don't believe [14:59] that that's a [15:01] church and you can tell it's a church [15:04] it's screaming at you I am a church and [15:07] it it occurred to me that [15:09] architecture is about [15:12] intent it's about the intent of the [15:14] system it's not about the tools it's not [15:16] about the Frameworks those are details [15:19] details that should be hidden not [15:23] exposed and then that reminded me of [15:27] decades long past P that reminded me of [15:32] this guy who had the same message in [15:36] 1992 this is iar yakobson and in 1992 he [15:40] wrote this book or published this book [15:42] and actually it was a bunch of his [15:44] employees who wrote the book and then he [15:46] took credit for it but that's [15:50] okay objectoriented software engineering [15:53] was the title of the book and the [15:54] subtitle was a use case driven approach [16:00] and I remember how excited we were in [16:03] the early 90s when this book was was [16:05] about to come out and it was it was [16:08] eventually uh first released at a uh a [16:11] conference I was at and everybody lined [16:14] up at the bookstore to get their first [16:16] copies of Yar yakobson book and it was a [16:20] a classic it remains a classic today [16:24] does anybody remember use cases yeah and [16:27] and and how excited we were and Al and [16:29] then how awful it was [16:32] later what it and what had happened to [16:34] use cases of of course was that we had [16:36] the invasion of the consultants and the [16:37] Consultants came in and every consultant [16:39] has to outdo the last consultant so uh [16:42] the Consultants at first were saying oh [16:43] yes I can do Jackie Jacobson's use cases [16:46] certainly I can and then they had to uh [16:49] create forms so you could download on [16:51] the internet which was new Those Days [16:53] downloading things on the internet you [16:55] could download from the internet uh PDFs [16:58] or actually it was it was probably uh uh [17:00] postcript files in the early days [17:02] postcript files of uh of forms that you [17:06] could fill out to I to identify your use [17:09] cases if you wanted to make a use case [17:11] you download a use case form and then [17:13] every consultant had to have a better [17:14] form than the one before him and so the [17:16] forums got more and more elaborate and [17:18] eventually you had to fill out all the [17:20] primary actors and the secondary actors [17:22] and the tertiary actors and the [17:23] preconditions and the postconditions and [17:25] and it we just turned into a nightmare [17:27] and the poor people who are supposed to [17:28] be doing use cases all want no we can't [17:31] do this and that was just at the point [17:33] where the agile Revolution started and [17:34] everybody said let's do user stories [17:37] instead and nobody quite understood that [17:40] there's no difference between a user [17:41] story and a use case of the same thing [17:43] but at least we didn't have the [17:49] form a yakob sonian use case looks like [17:53] this very simple straightforward kind of [17:56] thing if you looked in his book you'd [17:58] see something very very much like this [18:00] um and this is the use case for a um the [18:04] create order use case uh in an order [18:08] entry system and it tells you what the [18:11] incoming data looks like there's a [18:13] customer ID and a shipment destination [18:16] and payment information notice that I am [18:18] not describing the structure of this [18:21] data I'm just saying it kind of exists [18:25] I'm not going to tell you what it is [18:26] because the use case is not the [18:28] appropriate place [18:29] to identify the structure of the data [18:31] it's just a nice place to say yeah this [18:33] data exists notice that I'm also not [18:36] saying that it's coming from the web [18:38] although this might be a web driven [18:40] system why would the use case say a [18:43] thing like that it's just telling you [18:45] that this data is going to come in from [18:47] somewhere and then the use case says [18:50] well this is what happens if everything [18:53] goes right the primary course that's [18:55] what yakobson called it and the this is [18:57] just a a set of steps which tells you [19:01] the processing steps that the use case [19:03] might go through in very general terms [19:06] the order clerk issues the create order [19:08] command with the above data that's [19:10] that's not even part of the use case [19:12] that that's the manual step that kicks [19:14] the use case off two the system [19:16] validates all the data I'm not going to [19:18] tell you how it validates all the data [19:20] or give you any of the validation [19:22] criteria that's for later right the [19:25] system creates order and determines [19:27] order ID this is probably a database uh [19:31] operation but I'm not going to say that [19:32] here because maybe it's not you know who [19:34] might have determined that and finally [19:37] the system delivers the order ID to the [19:39] clerk probably on a web page but why [19:42] would I say that here that's a a very [19:45] typical kind of yakobian use case and at [19:48] the end of that there's also all of the [19:50] courses that would take place if there [19:52] were errors any kind of exceptions [19:54] usually there's only one or two of [19:56] those now what y obson said about this [20:01] and the topic of his [20:03] book was that you can take that and turn [20:07] it into an object remember the name of [20:10] his book was objectoriented software [20:13] engineering you can take that use case [20:16] and you can turn it into an object [20:18] because clearly it takes data in it [20:21] processes the data and it puts data [20:24] out I call these objects interactors he [20:28] called them control objects I didn't [20:31] like that name because it's a little too [20:33] close to model view controller and I [20:36] didn't want the ambiguity there other [20:38] people have said you know Bob you should [20:40] have called it a use case object and [20:42] that might have made some sense but it's [20:43] too late [20:45] now the interactor [20:49] object contains application specific [20:53] business [20:54] rules now what does that mean what's an [20:56] application specific business rule [20:59] well there are two kinds of business [21:00] rules there are business rules that are [21:05] true across many [21:07] applications and then there are business [21:10] rules that only have a place in a [21:12] particular application they're related [21:15] to that application so for example um [21:18] the the process of pricing an order [21:23] adding up all the prices of all the [21:25] elements you might see that in an order [21:28] system you might also see that in an [21:30] order processing system so that's an [21:34] that's not an application specific [21:36] business rule on the other hand the [21:39] process for creating an order is [21:43] specific to the order entry system [21:46] that's the only system that can create [21:48] an order interactors are always [21:51] application specific they are the use [21:54] cases of the application and use cases [21:58] aren't shared between applications [22:00] because every application has a [22:02] different use [22:04] the the use the the business rules that [22:08] are not application specific yakobson [22:11] put into objects which he called [22:14] entities nowadays we would call these [22:15] business [22:16] objects these entity objects or business [22:19] objects um contain all of the [22:23] application Independent [22:26] Business and usually what happens is [22:29] that an interactor will actually talk to [22:32] several entity objects a create order [22:35] interactor would probably talk to an [22:37] order entity and a line item entity and [22:41] a customer entity there's be several [22:43] entities to talk [22:44] to and then yakobson said there's one [22:47] more kind of [22:49] object and he called them boundary [22:51] objects at first he called them [22:52] interface objects later he changed the [22:54] name to boundary objects and I have [22:56] drawn them here as in [22:58] interfaces because these boundary [23:01] objects are actually implemented by [23:05] either an interactor or something else [23:07] on the far [23:08] side um these two boundaries are [23:11] interfaces they're Java like interfaces [23:13] they're abstract classes data comes into [23:16] the interactor through the through this [23:19] boundary which is which is implemented [23:21] by the interactor data goes out from the [23:24] interactor through this boundary to some [23:26] other object on the far side [23:29] now I can show that in more detail [23:33] here there's an interactor talking to [23:36] several entities this is the input [23:40] boundary because the interactor derives [23:42] from it and that is the output boundary [23:44] because this thing here whatever it is [23:48] derives from it this is the delivery [23:50] mechanism and I called it a delivery [23:52] mechanism because I didn't want to say [23:55] web this it might not be the web might [23:57] be a console app might be a thick Client [24:01] app might be a service oriented [24:02] architecture I don't care what it is [24:05] this is just the part of the system that [24:08] communicates outwards to whatever the [24:11] business rules are trying to talk to and [24:14] here's the user of the system so watch [24:17] the flow here the user has pushed some [24:21] button maybe he's filled out a web form [24:24] and he's pushed the submit button and [24:26] the delivery mechanism whatever that is [24:29] probably the web but maybe not the [24:31] delivery mechanism's responsibility is [24:33] to take whatever data was created in [24:36] that moment and stick it into a data [24:40] structure called a request model if [24:42] you've done any web work before this is [24:44] something like an HTTP request except I [24:48] don't want the words HTTP in there at [24:50] all I don't want any I don't want it to [24:53] derive from some Base Class which is [24:55] tied to the web I don't want any field [24:58] in it that have any webbish kind of [25:00] knowledge to it in fact what I want this [25:02] data structure to be is a plain old Java [25:05] object or a plain oldet object or [25:07] whatever language you're using with a [25:09] bunch of Primitives in it and no no [25:11] dependencies at all on any [25:16] framework that gets passed into the [25:18] input boundary and since the interactor [25:21] deres from the input boundary the [25:24] interactor receives that request model [25:26] and it decodes it says Ah this is a [25:28] create order request model and gets all [25:31] the data out of it and then it uses that [25:33] data to control the dance of the [25:35] entities it pumps out to the entities [25:37] says well you've got to create an order [25:39] and you've got to add the line item to [25:40] the order and you got to add the [25:41] customer to the order does all that [25:43] stuff and when it's done with that it [25:46] continues to control the dance of the [25:48] entities but this time in the opposite [25:51] direction as it gathers up all the [25:53] results to create yet another data [25:55] structure called the result model [25:59] and then the result model is passed back [26:01] out through the output boundary to the [26:03] delivery mechanism that implements it [26:05] and somehow delivers to the [26:10] user could you test those [26:15] interactors prob yeah you can test them [26:18] right cuz all you have to do is hand the [26:21] interactor a request model and then get [26:23] the response model from it and look at [26:25] it they have to have the web running in [26:27] order to test that use case no do you [26:31] have to have the application server up [26:33] and running you don't have to have [26:34] anything up and running all you need is [26:36] the interactor with its entities and you [26:40] pass data in through the boundaries and [26:41] you collect data out from the [26:43] boundaries you don't have to have the [26:45] database running we'll talk about the [26:47] database later you don't have to have [26:48] the web running you don't have to have [26:49] anything running and by the way this is [26:51] a fundamental goal of writing tests when [26:54] you're writing tests especially tests of [26:56] your use cases you do not want to to [26:58] have the whole system running especially [27:01] any part of the system that is slow or [27:04] any part of the system that makes it [27:06] difficult to communicate with the [27:07] business rules like a web server does [27:10] you don't want to have any of that stuff [27:12] running you'd like to just talk right to [27:13] the business [27:14] rules which means that nothing here [27:19] knows about the web at all there's no [27:22] dependencies here about the [27:24] web I'll show you that a little bit [27:26] later but at this point you might be [27:29] wondering what about mod view controller [27:32] cuz I mean we're supposed to be using [27:34] mod view [27:35] controller web application should be [27:37] model view [27:38] controller I met the inventor of model [27:41] view controller a couple of years [27:45] ago that's him and I'm going to butcher [27:48] his name because I don't think any human [27:50] can say it it's trig Vin CAG at least [27:53] not an American [27:55] human um trig Vin [27:58] it was the inventor of model view [28:01] controller uh in [28:03] 197 something or other model view [28:06] controller may be the very first named [28:09] design pattern before the word design [28:12] pattern was ever [28:13] invented and I met him in Norway uh I [28:17] was in the speaker Lounge of a [28:20] conference there uh trying to find a [28:22] place to plug my laptop in and I was you [28:24] know rooting around trying to find some [28:26] place and this old grizzled guy reaches [28:29] over and hands me a power strip and I [28:33] you know I take the power strip from him [28:35] and and you know programmers don't make [28:36] eye contact right so I I took the power [28:39] strip from and then I looked up and oh [28:42] it's triv C and as I took it you know [28:46] our fingers just barely touched [28:49] I haven't washed that [28:53] hand the model view controller [28:55] architecture that that he came up with [28:57] so long ago for small talk looks like [29:00] this there's a model object which hides [29:03] some very small business rule there is a [29:07] controller the job of the controller is [29:10] to gather input from some device [29:13] keyboard mouse whatever and the [29:17] controller will then translate those [29:20] gestures from the device into method [29:23] calls against the model the model is not [29:26] allowed to know what devices control it [29:29] the model is not allowed to know where [29:30] it gets its input from that is the job [29:32] of the controller The View and notice [29:36] this funny arrow that I've drawn here [29:38] the reason I drew that funny arrow is [29:39] because that is an observer relationship [29:42] The View registers with the model and [29:44] the model calls back to the view through [29:47] an observer likee pattern um and tells [29:51] the view hey I've changed you better [29:52] redisplay me the job of the view is to [29:56] then present the data within the model [29:58] model appropriately to the [30:00] user input process output that was model [30:04] view controller and in those days model [30:07] view controller was used in the small [30:10] you had a model view controller for a [30:12] button you had a model view controller [30:15] for a single text box you had a model [30:18] view controller for a group of radio [30:20] buttons you did not have a model view [30:23] controller for a whole [30:25] page that's a much later invention [30:29] because what happens to any kind of good [30:31] idea in software is that people know [30:34] that it's a good idea and they also know [30:37] that their ideas are good and so that [30:39] they figure that their ideas are that [30:41] idea and they completely lose the [30:43] original idea so somebody somewhere came [30:47] up with the architecture for a web app [30:49] and they knew it was good because they [30:50] had done it and they called it model [30:52] view controller because model view [30:54] controller is obviously good and so [30:56] nowadays our web apps look like this [31:00] now here's the web sitting off to the [31:04] right and I won't talk about it much [31:07] except to say that through some magic [31:09] the web server eventually invokes one of [31:12] your controllers after you've after it's [31:15] received the URL or after it's received [31:17] an HTTP request to get or a put or [31:20] something like that uh the controller [31:22] will get that and the controller's job [31:25] is to unpack all the strin parameters [31:28] that came in through the URL or the web [31:30] form or whatever and then call all the [31:33] business objects that are in the model [31:36] area of the of the model view controller [31:39] uh architecture for the web and having [31:42] called all the business objects then the [31:44] controller passes control to The View [31:46] and the view queries all the business [31:47] objects and puts all that data on the [31:50] web that's typical kind of model view [31:52] controller architecture for the web and [31:55] one of the problems with this [31:56] architecture is that there are no solid [31:59] boundaries here what happens is that [32:03] controller like functions somehow wind [32:05] up in the business objects because [32:07] programmers are sitting there going oh [32:09] crap I got to get crap done and they [32:10] start writing code and they don't really [32:12] care where they're writing it so they [32:14] might write it here they might write it [32:16] there it works it's okay it works you [32:17] know leave me [32:19] alone and they view like stuff manages [32:23] to get into the business objects and and [32:25] business stuff gets into the views [32:27] anybody seen any business code in the [32:30] views that you know it's just there's [32:32] not good boundaries here and and you [32:34] kind of lose control of it if if you [32:37] cannot keep discipline and one of the [32:39] problems we have as an industry is that [32:41] we're not very good at keeping [32:43] discipline I mean hell half of us don't [32:45] write tests and that's unbelievable [32:48] right can you imagine telling your users [32:50] you know I don't write tests I just [32:52] write the [32:53] code and sometimes it even works and I [32:56] ship it to you and if bugs well you're [32:58] going to tell me aren't [33:00] you that's our [33:07] attitude in [33:09] jobson's [33:11] architecture the there's a boundary and [33:15] I I'm I'm confusing this with the [33:17] boundary object the boundary I'm talking [33:20] about is this double black line that [33:23] double black line is a hard [33:25] architectural boundary that separates [33:28] the business rules from whatever else [33:32] we're using the web in this case and I [33:36] want you to notice carefully whenever [33:37] you see these double black lines that [33:39] all of the dependencies point across [33:43] those double black lines in One [33:45] Direction always pointing towards the [33:47] business rules this is the fundamental [33:50] architecture of a [33:53] plugin this the web is a plugin to the [33:58] business rules do you use [34:01] plugins okay uh who's what languages do [34:04] you guys use who's doing [34:07] Java who's [34:10] doing.net what are the rest of you [34:13] doing Pyon python o python dynamically [34:17] typed lots of cool things what I I [34:22] APL not IP APL [34:25] really people still use a [34:28] wow do you have like special keyboards [34:30] with a million [34:31] Keys gooey got a gooey you [34:35] need get a tool everything what else PHP [34:40] P oh PHP of course accidental [34:43] language was never supposed to be a [34:46] language but it did it became one what [34:48] else tickle tickle tickle tickle [34:51] Graphics [34:53] language anybody else it's a little [34:55] JavaScript here a JavaScript of of [34:57] course we're doing JavaScript okay so so [35:01] um imagine that you've got an IDE like [35:04] eclipse or like Visual Studio or or [35:07] probably some of you are using [35:12] them and um um you've got some plugins [35:15] that you've added to it let's take a [35:17] particular case uh Visual Studio has a [35:21] very good plugin that makes it almost [35:23] usable which is called um resharper [35:28] now now re sharper makes it's very nice [35:31] to have resharper but I want you to [35:33] think [35:35] now they're built by different teams [35:38] there's the V Visual Studio team at [35:40] Microsoft and then there's the resharper [35:42] team at jet [35:44] brains which of those teams can [35:46] completely screw the other [35:48] team which of those two teams is [35:51] powerless and which of those two teams [35:54] is has absolute power [35:58] who could put the other out of [36:01] business well now clearly Microsoft [36:03] could put anyone out of [36:06] business [36:08] but in this case Microsoft would not [36:11] have to use any money to put them out of [36:13] business because they could put um [36:16] resharper out of business simply by [36:17] changing their interface every day oh we [36:19] changed it [36:20] again send them a little note oh we [36:22] Chang it again sorry you're not going to [36:24] be able to deploy and then every time [36:26] they're just about ready to deploy [36:27] change the interface on them you could [36:29] keep them from ever [36:30] deploying um plugins are vulnerable to [36:34] the things they plug into but the things [36:38] that they plug into are immune to the [36:41] plugins at compile [36:43] time now I want you to think about this [36:46] very carefully isn't it true that there [36:48] are things in your system that you would [36:51] like to be [36:53] immune from other things in your system [36:57] in particular wouldn't you want the [37:00] business rules to be immune from the [37:03] strange bizarre changes that occur to [37:07] the [37:08] UI and of course you do and that means [37:12] that you would like the UI to be a [37:13] plugin to the business rules because [37:16] that protects the business rules [37:18] entirely there's nothing the UI can do [37:20] to screw up the business rules cuz the [37:22] the UI plugs into the business [37:25] rules and the business rules can complet [37:27] completely screw the UI but who [37:30] cares cu the UI is going to change [37:32] anyway which of those two is going to [37:33] change more business rules or UI UI UI [37:37] UI is designed to change so there's [37:39] another rule if something changes a lot [37:41] it should be a plug-in if something [37:43] doesn't change very often it should be [37:45] plugged into this is the signature of a [37:48] plug-in plugins always have source code [37:51] dependencies that point One [37:56] Direction now look at What's Happening [37:58] Here the interactor is done it has [38:01] created the response model the dance of [38:04] the entities is over the response model [38:06] has been created and now the interactor [38:09] is going to take this response model [38:11] data structure and pass it out through [38:13] the output boundary to something called [38:16] a presenter which some of you have [38:18] probably used in the past if you've used [38:20] models VI presenter architecture or if [38:22] you've ever done mvvm architecture the [38:25] presenter is a a class or a set of [38:28] classes the presenter is an object in [38:30] this case whose job is to take the [38:33] response model and translate it into a [38:36] view model now what's the difference [38:38] between a response model and a view [38:40] model response model has data in it data [38:45] that was created by the interactor as [38:48] the result of a use case but this data [38:50] is not presentable a date for example [38:54] would be a date object a some current [38:57] would be a money object U you might have [39:00] other kinds of objects as well it is the [39:03] job of the presenter to take them and [39:05] turn them into formatted strings The [39:08] View model has in it nothing but strings [39:11] so it is the presenter that takes the [39:13] date object from the response model and [39:16] calls simple date format on it with just [39:18] the right Gunk and creates a nice little [39:21] string that the view model will hold and [39:24] it is the presenter that takes the [39:26] currency [39:28] data out of the response model and puts [39:30] it in the view model with the [39:33] currency character in it and the just [39:35] the right number of decimal points and [39:37] and maybe parentheses and all of that [39:40] stuff and if um if the currency should [39:43] be [39:44] read then the presenter knows that [39:48] somehow or another by looking here and [39:50] it says oh yeah negative currency that [39:52] should be read and so it it sets a [39:54] little flag in the view model it says [39:57] this field should be [39:59] red if there's a button on The View The [40:03] View model has a string that holds the [40:05] name of the button if that button should [40:08] not be active if it should be gray The [40:11] View model holds a boole for telling it [40:14] to gray it out if there are menu items [40:17] in the view the view model has the names [40:21] of all the menu items and their orders [40:24] and whether or not they are gray there [40:26] is no significant processing about the [40:29] presentation of the screen that is not [40:32] done by the [40:33] presenter the goal behind that is to [40:36] make the view so stupid you don't have [40:38] to test [40:39] it right literally the view is so stupid [40:43] has no real code in it all it's doing is [40:46] grabbing crap out of the view model and [40:48] sticking it on the screen without any [40:50] processing so there's no point in [40:52] testing it you're going to test it with [40:54] your eyes because you got to look at it [40:55] anyway but you can test that presenter [40:59] you can test everything it does you can [41:00] test that the formatting is done right [41:02] and that the booleans are set correctly [41:04] and the buttons are activated and the [41:05] menu items are activated you can test [41:07] all of that stuff without ever getting [41:09] to the view and then if you really want [41:11] to test the [41:12] view you hand it a view model and you [41:15] don't have to have the rest of this crap [41:16] while you're testing the [41:20] view that's one of the benefits of [41:22] having a plug-in architecture you can [41:24] test the elements of the plug-in [41:26] architecture independently [41:28] so here's the whole picture or at least [41:30] the picture from the interactor to the [41:31] left and here again is this nice little [41:34] boundary line and notice all the all the [41:36] arrows point in One [41:38] Direction now when I say all the arrows [41:40] I mean all the [41:42] arrows every arrow every import every [41:46] pound include every source code [41:49] reference no matter how trivial must [41:52] maintain this pointing in single [41:55] Direction This Is Not Just the big [41:57] objects that all point in that direction [41:59] every single object has to point in that [42:01] direction because your goal here is to [42:04] take this stuff and burn it into a dll [42:08] or a gem or a or a jar file or a shared [42:11] library and burn this stuff into a [42:14] completely separate dll or jar or gem or [42:17] whatever and ship them independently [42:19] deploy them independently and by [42:22] deploying them independently I mean you [42:24] might make changes to the gooey you're [42:26] going to recompile all the code on the [42:28] gooey side you're not going to recompile [42:30] any of this code you're not going to [42:32] build another jar file for this cuz [42:35] nothing there has changed and all you're [42:36] going to deploy is the guey jar file [42:39] because the guey jar file is a [42:41] plugin that's the [42:46] goal what about the [42:52] database does that look [42:55] familiar does that look familiar Amar [42:57] have you seen that picture before in [42:59] your mind as you sleep does the database [43:02] call you in your [43:04] sleep is the database the great God of [43:07] all the applications and all the other [43:10] applications surround it and worship it [43:12] as little [43:16] minions who would benefit from that view [43:19] DB DB certainly benefit from that view [43:23] they like that view a lot because they [43:25] get to stand on rocks and be high [43:26] priests [43:28] but you know that dbas there was a time [43:31] when there weren't [43:33] any where did DB come from who invented [43:38] the job function Oracle [43:41] yes yes [43:44] Oracle because there was this Stellar [43:47] moment this beautiful moment when [43:50] companies like Oracle back when Oracle [43:53] actually had to compete with people [43:55] um they [43:57] went to CEOs they came they had this [44:00] idea right this marketing idea they went [44:02] to CEOs and they said to the [44:05] CEOs are you protecting your data assets [44:09] what a beautiful word that is a data [44:11] asset no one had ever thought of this [44:12] word data and asset together a data [44:15] could be an asset are you protecting [44:17] your data assets CEO goes I didn't know [44:20] I had data assets oh yes you do and [44:23] you'd better be protecting them and we [44:24] have the system that will protect them [44:26] ah but in order to use our system you [44:29] have to change your organization you [44:31] need people to preside over the control [44:34] of your data assets and this massive uh [44:38] movement took place this was in the 70s [44:41] and in the 70s this all took place [44:43] gradually and people started really [44:45] putting dbas in place and database [44:48] administrators and and they were the [44:50] only ones with the keys to the database [44:51] and this whole this whole high priest uh [44:55] thing took place and D bases [44:58] became [45:01] significant and that's [45:03] ironic it's ironic because databases are [45:06] I [45:07] devices I mean [45:09] it's dis you store bits in a database [45:13] and and they have some interesting [45:15] features they're Frameworks they have [45:17] interesting features search features and [45:18] indexing features and Reporting features [45:20] and that's cool but they're just tools [45:24] they're not the architectural Center of [45:26] anything they IO [45:29] devices if you have a database that's in [45:31] the center of [45:32] everything you've made the same mistake [45:34] that rails has the web must be in the [45:38] center of everything and actually rails [45:39] does both the web and the database must [45:41] be in the center of [45:44] everything the database is a detail and [45:47] this should be burned into your mind [45:49] you're a programmer right and the [45:51] programmer's job is to separate the [45:54] things that are important from the [45:56] things that are not not we are [45:57] abstractors that's what we do for a [45:59] living we are detail [46:02] managers and the way you manage details [46:05] is to separate the details from the [46:07] things that are General this is what we [46:09] do for a [46:10] living and the database is a [46:15] detail why do we have [46:20] databases to to pers persist now we've [46:24] had persistent systems long before there [46:25] were databases [46:27] we've had uh directory structures unic [46:30] style directory structures they they [46:32] came long before there were uh [46:35] relational databases there were [46:37] hierarchical data structures databases [46:39] long before there were relational [46:41] databases why is it now that we and for [46:45] the last 30 years that we have been [46:48] preferring this table structure this [46:51] bizarre way of putting things into [46:54] tables including all of our pointers you [46:56] you know we have to have relationship [46:59] tables and and we have to have this this [47:02] actually truly horrible language called [47:04] SQL SQL um to access all of this data [47:09] which by the way has led to tremendous [47:12] security problems all over the place [47:13] because somebody had the great idea that [47:16] our data access language should be [47:19] textual and then interpreted inside the [47:21] code so that some guy out in Nigeria [47:25] could type select [47:27] star inside the date [47:35] field why do we have these Frameworks [47:39] these [47:40] engines and the reason we have these [47:42] engines is because discs are a pain in [47:45] the ass has anybody written a disc [47:49] driver oh yeah so a disc the structure [47:54] of a disc is a disc is a flat platter [47:58] right with a magnetic coating and [48:01] generally with discs you stack them [48:03] you'd have uh two or three or four or [48:05] five back in the old days you'd have 14 [48:07] or 15 platters all stacked up on top of [48:10] each other in a [48:12] spindle and you'd spin that thing and [48:14] you'd spin it at like 3600 RPM that [48:17] later once they got smaller we started [48:19] spinning them at 7200 RPM but never mind [48:21] that we'd spin them 60 times a second or [48:23] 50 times a second something like that [48:26] and um the you there'd be a head that [48:29] could move in and out and the head would [48:33] draw little magnetic dots in a circle [48:36] around the surface of the disc and it [48:40] would do that oh maybe uh 200 or 500 or [48:43] or a thousand of these little circles uh [48:46] spaced a half a millimeter apart [48:48] depending on the technology so we called [48:51] these circles tracks and the because the [48:55] platters were stacked on top of each [48:56] each other we called the [48:58] tracks in a all at the same radius we [49:02] called those [49:04] cylinders and what you would do is if [49:06] you wanted to write a bite onto the disc [49:10] you would you would send a directive to [49:12] the disc drive to seek into the [49:14] appropriate track and in the early days [49:17] you had to take account of things like [49:19] the momentum of the drive and you'd have [49:21] to send it accelerate and de accelerate [49:23] all kinds of horrible stuff that you had [49:25] to do in the early days and in the later [49:27] days you could just tell it go to track [49:28] go to go to cylinder number [49:31] 72 and then you had to wait by the way [49:34] how long did it take to do that [49:35] seek 100 milliseconds was fairly typical [49:38] in the older days a little bit later it [49:40] got shorter but a good back in the 60s [49:43] and 70s and the mid 80s 100 millisecond [49:45] seek time was pretty average so you'd [49:47] wait 100 milliseconds for the for the [49:49] head to get in position then you'd have [49:51] to wait for the rotational latency for [49:55] your bite to come around but you [49:56] couldn't just read your bite right you [49:58] had to read a whole sector the sector [50:00] would be like 4K bytes so you'd have to [50:03] read all 4K bytes and you have to wait [50:05] for it to come around and then you'd [50:07] read all 4K bytes then you'd find your [50:08] one little bite in there right that's [50:11] how you had to deal with the disc and [50:12] was it was a pain it was a [50:15] pain the job of the database was to get [50:19] rid of that pain to to make it efficient [50:23] to get data in and off this horrible [50:25] spinning [50:27] magnetic surface so we invented things [50:30] like uh indexes and we we used tables [50:33] because that was the easiest thing to do [50:35] and and we created these Frameworks that [50:37] would easily get data in and off this [50:40] spinning [50:42] thing has anybody got a laptops in here [50:46] does anybody have a laptop here with a [50:49] spinning disc in [50:51] it [50:53] no had I asked this question five years [50:55] ago [50:57] most of you would have had a spinning [50:59] disc there would have been two or three [51:01] guys here that would have been sitting [51:02] here [51:05] going no and we would have hated [51:08] them had I asked that question 10 years [51:12] ago nobody would have said that they did [51:14] not have a disc everybody would have had [51:16] a disc now no one has a disc the discs [51:19] are going away there will not be [51:22] discs Even in our server Farms there [51:25] will not be discs what do we have [51:27] instead we've got a huge linear array of [51:31] RAM and it looks like RAM and it's [51:34] addressable like RAM and why the hell [51:35] isn't it [51:38] Ram what if you [51:42] had a a 32-bit address space a 64-bit [51:46] address space of persistent [51:51] Ram how would your behavior [51:53] change now what do you do when you read [51:56] in a relational table you you go do the [51:58] sequel thing and fetch [52:00] the collect all the gunk and now you've [52:03] got what the Ado structure that [52:05] everybody likes to use now it's a bunch [52:07] of tables and then what's the first [52:08] thing you do with it you unpack it into [52:11] link lists and hash tables and binary [52:13] trees because those are the kind of data [52:15] structures we like to use we're [52:17] programmers we don't like these dumb [52:19] tables these dumb tables are dumb I want [52:21] to use pointers I don't want to use [52:23] relationship tables [52:25] right what if we just saved it as hash [52:29] tables and binary trees and linked [52:36] lists if I were [52:39] Oracle I would be pretty scared because [52:44] the reason for my existence is [52:45] evaporating out from underneath me now [52:48] there's a lot of momentum in databases [52:51] and there's a lot of social momentum [52:53] people like their sequel for some [52:55] strange reason and they they enjoy [52:58] relationship tables and they will tell [52:59] me over and over don't you understand [53:01] there's a relational algebra there's a [53:03] mathematics blah blah blah blah blah [53:05] okay fine I've heard it [53:07] before but the reason for the database [53:10] to exist is [53:12] disappearing and more and more we are [53:15] going to have access to in indefinite [53:18] amounts unlimited amounts of persistent [53:22] RAM and more and more we will be storing [53:25] our data in the way that is convenient [53:27] for us to store our data in trees and [53:30] hash tables and linked lists and other [53:33] very convenient forms not in relational [53:35] tables oh there will probably be some [53:38] relational tables that survive but now [53:41] there is some healthy [53:43] competition this is why there is a no [53:46] sequel [53:47] movement the nosql movement is born out [53:50] of this notion that we don't need the [53:53] discs anymore we don't need the [53:55] databases anymore [53:58] imagine that you are working on a system [54:02] 10 years from now and you are [54:05] inextricably bound to a relational [54:09] database because you've made huge [54:11] commitments today to a relational [54:14] database and you cannot get away from [54:16] them whereas the rest of the world is [54:17] happily dealing with a much nicer data [54:20] storage technique you don't have access [54:22] to because you bound yourself to a [54:24] relational database I don't think you [54:26] ought to do that [54:28] binding I'd like another boundary I have [54:31] a lot of reasons for wanting this other [54:33] boundary not just the idea that you [54:35] don't want to bind to the database but [54:37] here's the boundary here another one of [54:38] these lovely double lines which makes [54:41] the database a plugin to the business [54:44] rules that's nice anyway the database [54:47] should plug into the business rules [54:49] business rules should not be written [54:51] anybody write their um business rules of [54:53] stored procedures in the database this [54:55] is a bad idea we don't want to have SQL [54:58] as our business roles there are good [55:01] purposes for stored procedures data [55:03] integrity and back pointers and that [55:05] kind of thing but you don't want to be [55:07] doing male merges in SQL I actually saw [55:11] a SQL stored procedure that was an [55:14] entire mail merge never [55:17] mind so here's the [55:19] boundary and um the interactors will use [55:23] an interface which I call an entity [55:25] Gateway way the entity Gateway is just a [55:28] standard interface and it has a method [55:30] in it for every operation you want to [55:34] perform on the database if you want to [55:37] get every order that was posted since uh [55:42] December 1st there is a function here [55:46] that that says get orders since some [55:50] date every possible query every possible [55:53] create every possible delete every [55:55] possible update every possible query you [55:58] want to do there is a method here that [56:00] lets you do that query and down here you [56:02] implement those methods and this is [56:05] where the SQL lives and the SQL is sent [56:07] to the database through an API and life [56:09] is good there is no SQL up [56:13] here and if you look at the direction of [56:15] the dependencies you find that the [56:17] database is a plugin to the business [56:20] rules all of the SQL lives down here so [56:22] the SQL stuff plugs into the business [56:25] rules and you can separate your business [56:28] rules from the [56:31] database anybody notice that databases [56:34] can sometimes be a little [56:37] slow and do does anybody have a test [56:40] Suite that's really slow because it goes [56:43] through the [56:45] database well you don't have to do that [56:48] because you can replace the database [56:50] with anything you'd like here and most [56:52] of the time what we'd like to replace [56:54] the database with is a set of simple [56:56] little stubs every unit test will just [56:59] hand it another stub that says he when [57:01] it asks that question just return this [57:04] and all your tests can go in micros [57:10] seconds who's got a suite of tests a [57:12] really good [57:13] one yeah does execute [57:17] fast yes yeah one does one of them [57:22] does the rest of the unit tests are all [57:24] really slow but one unit test is really [57:25] fast [57:28] one of the design imperatives for a test [57:31] Suite is that it execute quickly and the [57:34] reason for that is very simple if it [57:36] doesn't execute quickly you're not going [57:38] to execute it you're going to put it in [57:40] some thing that is executed at [57:43] night you won't execute it because it [57:46] takes too long what's too long more than [57:49] a couple of minutes if it takes more [57:51] than a couple of minutes you're just not [57:52] going to run the tests yourself you will [57:55] defer that to the continuous build you [57:57] will defer that to later at night you [58:00] will defer that to some that runs on the [58:02] weekend but you won't actually see the [58:05] tests run yourself and that's a problem [58:09] because if you could run the tests [58:12] yourself and see them [58:16] execute in a minute or two or [58:20] three then you could make decisions [58:22] based [58:24] on what happened to those tests test for [58:27] example what decision can you make if [58:29] the test Suite [58:31] passes well that'd be a good decision [58:33] wouldn't it if the test Suite passes [58:35] ship it and what's the laughing [58:40] about well the Laughing is about yeah if [58:43] the test Suite passes we're going to [58:44] ship it [58:45] right because we know that the test [58:48] Suite doesn't cover [58:50] everything but if the test Suite doesn't [58:53] cover any everything then you can't make [58:55] any reasonable decision when it passes [58:58] passing is [59:00] useless now when it fails that's good [59:02] you know that something's broken but if [59:04] it passes you don't know that it works [59:08] you can't ship it and if you can't ship [59:11] it that passing is useless one of the [59:13] goals is to get the test Suite to be the [59:16] QA process test pass shipping and I mean [59:19] literally if the test pass you [59:22] deploy if you have a test Suite like [59:24] that [59:27] then you can do something [59:29] else especially if you can run it [59:31] fast you can clean the [59:34] code because if you've got a fast test [59:37] suite and it covers everything then you [59:40] see some messy code and you can clean it [59:43] push the button oh yeah still works [59:45] clean it a little more push the button [59:47] yeah still works clean it a little more [59:49] push the button O broke it put it [59:52] back oh yeah okay I know what to do push [59:55] the button [59:57] green if you have a test Suite that you [60:00] trust with your life you can clean the [60:04] code how many of you have put cat up on [60:07] your screen and looked at it and thought [60:11] that's ugly I should clean it and your [60:12] very next thought was I'm not touching [60:15] it cuz you know if you touch it you will [60:18] break it if you break it it becomes [60:22] yours so you walk away from the C I [60:26] submit to you that if that is your [60:29] reaction to the code the only thing that [60:31] can happen to that code is that it will [60:33] rot oh you will add more features to it [60:36] you will fix bugs but everything you do [60:38] will make the code worse and worse and [60:41] worse because you will not make it [60:45] better the only way to make it better is [60:49] to have a sweet of tests that you trust [60:51] and then it's easy then you make it [60:54] better all you want and it cost you [60:55] nothing [60:57] pardon me so here we've got our [61:01] database and I can make my tests run [61:03] really fast with this so that I can test [61:06] all these business rules in [61:09] seconds maybe a [61:16] minute tell you a story and then I'll be [61:22] done in about 2002 [61:27] my son and I began a project called [61:29] Fitness some of you have probably used [61:31] it Fitness is a Wiki it's a Wiki that [61:35] integrates a framework that Ward [61:37] Cunningham wrote in 1999 that allows you [61:41] to write tests in [61:43] HTML um and uh uh you can see the [61:47] results right in those test tables they [61:49] turn green it's very [61:52] nice Fitness was a Wiki and we thought [61:55] all right we're going to build this um [61:58] now we need a place to store the pages [62:00] and that means a [62:02] database and there was only one open- [62:04] source database at the time that was [62:06] MySQL so we thought all right we're [62:08] going to have to get MySQL we'll have to [62:09] invent a schema we'll have to get it [62:11] working and all of that Gunk and we we [62:13] were about to do that when somebody said [62:16] well you know we don't really need to um [62:19] to get my SQL working yet because [62:21] there's this whole other problem of um [62:25] translating the wiki text into [62:28] HTML the markup language of the wiki [62:31] into HTML we thought oh yeah that's a [62:32] good idea we could do that instead and [62:34] so we invented a little mock object [62:37] called mock wiki page it der derived [62:40] from the Wiki page class the Wiki page [62:43] class had a save function on it which we [62:45] just mocked out to do nothing and it had [62:47] a 2 HTML function which is the one we [62:49] worked on took us three months or so [62:51] working on all the different parsing and [62:53] and producing HTML eventually we got [62:56] that [62:57] working and then we're doing test driven [63:00] development of course so our test [63:02] started requiring more than one page [63:05] that we could fetch from some data [63:07] storage and we said okay well now we [63:09] need my SQL let's get my SQL running and [63:12] somebody said well we really don't need [63:14] to do that yet because we could just [63:16] keep all the pages in memory and access [63:20] them through a hash table and we thought [63:22] yeah that's probably a better idea cuz [63:24] we don't need the database yet and so we [63:26] actually worked for a year this way um [63:29] pretending like we had a database it's [63:31] just this in memory page used a hash [63:33] table instead but the query technique [63:35] was still the same the save method still [63:37] worked all of that stuff still worked it [63:39] just worked in memory and for about a [63:42] year we developed Fitness that way we [63:44] got most of Fitness working this way and [63:46] now it could run tests and we could [63:48] build P couldn't save anything that was [63:50] a problem but it didn't matter to us [63:52] because we weren't shipping it yet and [63:54] we were just writing tests and making [63:55] pass and writing test and making them [63:57] pass life was [63:59] good eventually however we got to a [64:01] point in our testing where we really [64:04] needed persistence for the tests the [64:06] tests needed to have persistence we were [64:08] beginning to test the um the bootup [64:10] procedure and the boot up procedure had [64:12] to bring in pages from a file and and we [64:15] needed [64:16] persistence so um we thought okay Now's [64:19] the Time for my Sequel and Michael [64:20] feathers was there at the time and [64:22] Michael said well you probably don't [64:25] need the database yet because you could [64:27] probably take that hash table and write [64:30] it out to a simple file system um and [64:32] that'd be kind of a Clues but but it [64:34] wouldn't take very long and and you [64:36] wouldn't need to mess with the database [64:38] and he actually came back a day later [64:39] with that all working so we just fine [64:42] great now we have something that appears [64:45] to be persistent it's not the database [64:47] yet but um it's fine and we worked for [64:50] another 3 months on it and we we [64:52] actually could take it on the road now [64:54] we could demonstrate it to people we [64:55] could save our our Pages uh we could [64:58] actually do real projects and test real [65:00] projects with it and about 3 months in [65:02] we looked at each other and said you [65:04] know this is good enough we we don't [65:05] need the database at [65:07] all we pushed the database decision [65:10] right off the end of the [65:15] planet [65:18] until a client of ours came to us about [65:21] a year later and said uh we really have [65:24] to have a database [65:26] and and we said why the file system [65:28] works fine it's fast enough everything's [65:30] good and and besides you can actually [65:31] take your test and check them into [65:32] source code control that's one we hadn't [65:34] thought of when we were thinking of the [65:36] database and he said [65:39] know all of our artifacts are data [65:42] assets they've got to be in a [65:46] database so we said well all right um [65:50] here's how [65:52] um here's the structure and all you're [65:55] going to have to do is replace the file [65:57] system page with a MySQL page uh and he [66:00] actually came back a day later with the [66:02] whole thing working in MySQL and he gave [66:03] us the code we used to ship it with [66:05] Fitness as a plug-in but nobody ever [66:06] used it so we stopped even he stopped [66:08] using it after a while once people got [66:10] smart [66:11] there um the end result of that story [66:15] and the reason I told you that story is [66:17] because of this we took what would [66:20] normally be considered as a primary [66:23] architectural decision and we deferred [66:26] it and deferred it and deferred it right [66:30] off the end of the [66:32] project and that leads to a fundamental [66:35] conclusion good [66:37] architectures allow major architectural [66:40] decisions to be deferred the job of an [66:44] architect is not to make decisions the [66:47] job of an architect is to defer [66:49] decisions as long as possible to allow [66:52] the program to be built in the ab [66:56] of decisions so that decisions can be [66:59] made later with the most possible [67:02] information there are some decisions you [67:04] can't defer like what language you're [67:05] going to use that one you're probably [67:07] going to have to decide early but you [67:09] don't have to decide what database [67:12] you're going to use early now you may [67:14] already know what database you're going [67:16] to use early because your company has [67:17] invested $2 trillion in some worthless [67:20] license to a database and you've got to [67:22] use it but you can still pretend like [67:27] you don't know what database you're [67:29] going to use and defer that decision as [67:31] long as possible and um you don't need [67:35] to know what web framework you're going [67:38] to use ah we need to build a web [67:40] application all right get out Ruby and [67:43] rails guys who know rails if the first [67:46] thing you type at the beginning of your [67:48] project is rails you've lost the war [67:52] that is the last thing you should type [67:55] or one of the last things you should [67:56] type get all the business rules working [67:58] first get all the flows working first [68:01] and then oh yeah maybe we should use [68:03] rails here or maybe not you have the [68:07] decision you can make the decision late [68:10] a good architecture maximizes the number [68:13] of decisions not made and you achieve [68:16] that by using a plug-in [68:19] model and a picture of the plug-in model [68:22] is [68:23] here where you have all of your use case [68:26] and then everything around the outside [68:28] the UI the database I'll talk about [68:30] Frameworks briefly are [68:33] plugins to the use cases the use cases [68:36] don't know they exist and the use cases [68:38] are utterly independent of all this [68:41] stuff I put Frameworks there anybody [68:45] using a dependency injection [68:48] framework good cool isn't it you know [68:50] you can inject these dependencies [68:52] woohoo has anybody lost control of the [68:55] dependency injectors because you had [68:57] some horrible XML file or a whole bunch [68:59] of at Auto wir or something like that [69:01] and the system crashes and you don't [69:03] know why and it's because of some [69:04] horrible line in an XML file deep down [69:06] inside the the uh injection [69:12] configuration people have gotten kind of [69:14] um emotional about um dependency [69:18] injection it's a wonderful idea it's a [69:19] good idea it's an old idea and the [69:21] Frameworks have kind of helped with it [69:23] um but you probably don't want to inject [69:27] everything what you'd really like to [69:29] inject are [69:31] factories you'd like to inject factories [69:34] and then let the rest of the business [69:35] rules use those factories you'd like to [69:37] inject a few strategies the number of [69:39] things you inject should probably not be [69:41] more than a dozen or maybe two dozen and [69:45] that would be an awful lot and then [69:47] where you want to inject is also [69:50] critical because you don't want to [69:52] inject anything in here you'd like to [69:54] inject some you you'd like to make your [69:57] injections into a [69:58] plug-in and then have the plug-in [70:01] distribute the objects through normal [70:02] means like a factory or something like [70:05] that and the reason for that is simple [70:08] you don't want your use case code to [70:11] know about your injection framework you [70:14] don't want your use case to know your [70:16] use case code to know much about any [70:18] framework why because framework authors [70:21] are out to screw [70:23] you that's not quite true [70:25] they're not exactly out to screw you but [70:27] they will screw [70:28] you because the framework authors have [70:31] their interests in mind not yours think [70:34] of the relationship between you and the [70:36] framework author you make a tremendous [70:40] commitment to that [70:43] framework the author makes no commitment [70:46] to you at all and the author will change [70:48] the framework anytime the author feels [70:51] like it and tough tough beans guys I [70:55] changed the framework and you are bound [70:58] to me [71:01] ha a good software developer looks very [71:04] carefully at Frameworks and understands [71:05] that Frameworks come with benefits but [71:07] also with costs and those costs can be [71:09] pretty high so you want to take your [71:11] Frameworks you use you want to use them [71:13] just they're powerful but you also want [71:15] to kind of keep them at arms length and [71:17] and the most important thing about that [71:19] is you do not want to follow the [71:21] examples that the framework author wrote [71:23] for you cuz the framework author is [71:26] perfectly com comfortable coupling to [71:29] his [71:31] framework but you should [71:33] not so you want to think about that and [71:35] go I don't really want to couple to that [71:37] guys's using rails you how would you [71:39] ever not use [71:41] rails if you coupled strongly to it how [71:44] would you ever break away from rails if [71:45] you felt you had [71:47] to and [71:48] yet you should be free to break away [71:51] from rails you should you should [71:53] architect your system so that if rails [71:55] becomes a liability it is not impossible [71:59] to break away from [72:01] it so be careful with your Frameworks [72:04] and I think that's about all I have to [72:07] say here other than that one last last [72:09] little picture which I [72:11] like but that's contact information and [72:15] gee it's kind of late and I really want [72:16] some beer so are there any [72:22] questions good [72:25] oh oh all right all right all right one [72:28] question something I've seen quite a lot [72:30] um on your side with the databases [72:33] something where stor procs can be used [72:34] to Great effects is to abstract the [72:38] whole database thing from below the [72:41] repository away from the programmers so [72:43] they don't get any access to tables they [72:45] can't do any SQL right programmers love [72:47] it the dbas love it so they can protect [72:50] their little [72:52] domain yeah exactly um one of the things [72:55] I didn't talk about there was that the [72:58] entities are not um instances of [73:01] database tables they're usually [73:04] constructions from many database tables [73:06] and maybe some operations as well um and [73:10] I don't need to go into any more detail [73:11] on that but yeah good [73:13] point all right [73:21] beer thank you