William Liu

Clean Architecture

Notes from “Clean Architecture: A Craftsman’s Guide to Software Structure and Design”

Summary

When software is done right, it requires a fraction of the human resources to create and maintain. Our goal is to minimize the human resources required to build and maintain our required systems.

Section I

1 - What is Design and Architecture

Good vs Bad Design

Good design quality means that you have low effort and this effort stays low throughout the life of the system. Bad design is if the effort grows with each new release.

2 - Two Values (Behavior and Architecture)

Every software system provides two different values to the stakeholders: behavior and structure

Eisenhower Urgent-Important Matrix

Eisenhower said “I have two kinds of problems, the urgent and the important. The urgent are not important, and the important are never urgent”. Those things that are urgent are rarely of great importance, and those things that are important are seldom of great urgency. We can arrgant into quadrants with actions:

‘Behavior’ is always urgent, but not always particularly important (Important Urgent, Not Important and Urgent) ‘Architecture’ is always important, but not always particularly urgent (Important Urgent, Important and Not Urgent)

Is is the responsibility of the software development team to assert the importance of architecture over the urgency of features. Making messes is always slower than staying clean.

Section II - Start with the Bricks: Programming Paradigms

There have been a few ‘revolutions’ in programming already.

3 - Paradigm Overview

There are three programming paradigms:

Each paradigm imposes discipline on something; it removes some type of capability from the programmer. Each imposes some kind of extra discipline that is negative in its intent; the paradigm tells us what NOT to do more than they tell us what to do.

Structured Programming

Structured Progrmaming imposes discipline on direct transfer of control. Basically, goto statements were harmful and should be replaced with if/then/else and do/while/until.

Notes:

Object-Oriented Programming

Object-Oriented Programming imposes discipline on indirect transfer of control. Basically, polymorphism was discovered through the disciplined use of function pointers.

Notes:

What is Object-Oriented Design (OO)?

Answers:

Encapsulation

Encapsulation hides data by wrapping data and the methods that work on data within one unit in order to prevent the accidental modification of an object’s components. Publically accessible methods are provided in the class with getters and setters.

Inheritance

Inheritance is the redeclaration of a group of variables and functions within an enclosing scope. While modern programming languages make this a lot easier, it was possible with non OO languages (just more difficult to do/tricky)

Polymorphism

Polymorphism means that objects of different types can be accessed through the same interface. Each type can provide its own, independent implementation of this interface. At its core, polymorphism is an application of pointers to functions (we just don’t explicitly use pointers to functions to create polymorphic behavior is because it is dangerous). OO languages makes polymorphism trivial.

Instead of requiring an inheritance relationship, we can now point to the opposite direction (dependency inversion). This means that any source code dependency, no matter where it is, can be inverted. Any source code dependency can be turned around by inserting an interface between them. Software architects have absolute control over the direction of all source code dependences in thes system (we do not have to align these dependencies with the flow of control).

With Polymorphism, we can have components deployed separately and independently (independent deployability)

Functional Programming

Functional Programming imposes discipline upon assignment. Basically, the values of symbols do not change; there is no assignment statement.

Notes:

3 - Design Principles

The architecture doesn’t matter if the bricks are bad. We use the SOLID principles to tell us how to setup our bricks (i.e. how to arrange our functions and data structures into classes, and how those classes should be interconnected). This is for mid-level software structures (think modules, class design).

So what is a class? A class is simply a coupled grouping of functions and data. So what is our goal? With SOLID, we want to be able to create mid-level software structures (i.e. for modules) that are:

SOLID Principles

The SOLID principles are:

SRP: The Single Responsibility Principle

The Single Responsibility Principle means that a module should have one, and only one, reason to change. This is NOT the same as a function should do one, and only one thing. Instead, a better way of thinking about this principle is that a module should be responsible to one, and only one, actor.

So what is a module? This can be a source file or a cohesive set of functions and data structures.

What happens if you violate this principle?

Facade Pattern

You can separate the data from the functions. You can have three classes that are not allowed to know about each other, but they share access to say an EmployeeData class, which is a simple data structure with no methods.

With the Facade Pattern, we can have an EmployeeFacade that has little code, but is responsible for instantiating and delegating to the classes with the functions. Each of the classes that has a family of methods is a scope. Outside of that scope, no one knows that the private members of the family exist.

In my experience, this can get a little out of hand (where there are way too many classes).

OCP: The Open-Closed Principle

The Open-Closed Principle means that a software artifact should be open for extensions but closed for modification. You should be able to extend the software’s behavior without having to modify that artifact.

A good software architecture can reduce the amount of changed code to the barest minimum (ideally zero). The goal is to make the system easy to extend without incurring a high impact of change. We can accomplish this goal by partitioning the system into components, and arranging these components into a dependency hierarchy that protects high-level components form changes in lower-level components.

Summary: You should be able to extend a classes behavior, without modifying it.

LSP: The Liskov Substitution Principle

The Liskov Substitution Principle says that subtypes are defined as the following substitution property:

If for each object 01 of type S there is an object 02 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when 01 is substituted for 02 then S is a subtype of T.

Summary: Derived classes must be substitutable for their base classes.

The Square/Rectangle Problem

Say you have a Rectangle and Square. Square is not a subtype of Rectangle because a Rectangle has independently mutable height and width. A Square by definition has the same height and width that changes together.

What happens if you violate this principle? You might be tempted to put in if-statements to substitute say a REST endpoint (e.g. if endpoint ends with dest from acme.com instead of the regular destination). However, the better scenario would be to add a configuration database keyed by the dispatch URI (e.g. acme.com -> use dest endpoint)

Substitution works great, until a simple violation of substitutability, which can cause a system’s architecture to be polluted with a significant amount of extra mechanisms.

ISP - The Interface Segregation Principle

The Interface Segregation Principle

It is harmful to depend on modules that contain more than you need. Depending on something that carries baggage that you don’t need can cause you troubles that you don’t expect.

Summary: Make fine grained interfaces that are client specific.

DIP - The Dependency Inversion Principle

The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions. When you import a library, the source code dependencies should not refer to concrete modules (e.g. a module in which the functions being called are implemented).

In reality, you cannot abstract away say the String class. Changes to that class are rare and tightly controlled. We can rely on the operating system and standard libraries; it is the volatile concrete elements of our system that we want to avoid depending on.

So why the interface? Interfaces do not change as often as the concrete implementation. We should follow:

Summary: Depend on abstractions, not on concretions.

Factories

Use an Abstract Factory to manage the undesirable dependencies listed above.