
Chunks of 'Clean Architecture' - Part 1/3
A series of posts on what makes a clean architecture according to Robert Martin. Part 1/3
Welcome to my blog on the book "Clean Architecture" by Robert Martin. I have always heard and read about this book and its author, both favourably and against. I am also interested in getting to learn how to build and maintain enduring software architectures. So I decided to actually read it and record chunks of each chapter that I found the most interesting.
Some of these notes are as they appear in the original book, most tailored by me, but all ideas are a creation of Martin or authors Martin quotes in his book.
This is Part 1. Check Part 2 here, and Part 3 here.
Table of Contents
Foreword
Architecture represents the set of significant design decisions that shape the form and the function of a system, where significant is measured by cost of change.
Grady Booch (UML)
Time, money, and effort give us a sense of scale to distinguish the architectural stuff from the rest.
Not only does a good architecture meet the needs of its users, developers, and owners at a given point in time, but it also meets them over time.
If you think good architecture is expensive, try bad architecture.
Brian Foote and Joseph Yoder (Big Ball of Mud)
If change is expensive, change is eliminated (or avoided/supressed).
The cleanest path when building something is recognising the softness of software. As humans, operating with incomplete knowledge is something we are good at. A good architecture comes from understanding it more as a journey than as a destination.
Architecture is a hypothesis, that needs to be proven by implementation and measurement.
Tom Gilb
Part I: 🔰 Introduction
Right from the beginning, Martin suggests that being young is not enough for getting software right. I beg to differ (as if older programmers get it any better). Making this an issue of age is wrong and entices an unnecessary generational fight for who is smartest.
But he does remediate himself with an excellent point:
“When software is done right, it requires a fraction of the human resources to create and maintain. Changes are simple and rapid. Defects are few and far between. Effort is minimized, and functionality and flexibility are maximized.”
Have you worked on systems that are so interconnected and intricately coupled that every change, regardless of how trivial, takes weeks and involves huge risks? Believe me, I have, specially in Big Tech. From my experience in working at two of the largest tech companies in the world, I can say assuredly I both reviewed and wrote changes to production systems where the blast impact was large enough to take sleep from the on-call. This overhanging feeling of despair should be enough for teams to work smarter, to stay put to protecting a product's architecture, or to push for its improvement. We all have product teams wanting to release the next great thing, and we should enable them to do so, without blowing up the foundation in the way.
C1: 🎨 What is Design and Architecture?
The goal of software architecture is to minimize the human resources required to build and maintain the required system.
In a case study included as an example of what the goal of an architecture is, Martin looks at the productivity of a company in KLOC, a metric I find often misplaced when trying to measure a teams productivity. He implies that something is wrong when the size of a product measured in lines of code stalls over multiple life cycles. I cannot see the relation between a product serving its purpose and its size being incremental.
What if a major refactor actually decreases the lines of code of your product and actually serves your customers better by, say, reducing latency? KLOC would decrease in that case, but productivity (as if anyone on this planet could ever measure that) would increase. A similar conclusion could be said about the next figure he shares, “cost per line of code over time”. One could argue that it’s in the company’s interest to measure the Cost/LOC over time, but then again one should also have a precise, universal and transparent way of measuring both Cost and LOC, something very hard to do in practice.
This is not to say that we should disregard the impact of a clean architecture in a team’s metrics, but rather being ‘clean’ about software KPIs too (someone should write a book about that).
Nevertheless, Martin continues with a very true point that hits close to home: despite all the heroics, overtime, and dedication developers can put in a sprint/quarter, all their effort can easily be diverted away from features and consumed with managing exceptions and outages.
Our job is in a constant threat of moving further apart from adding value to end users, and closer to managing a mess from one place to the next, and the next, and the next, only so that we can add one more meager little feature.
"We can clean it up later; we just have to get to market first!" Of course, things never do get cleaned up later, because market pressures never abate. Getting to market first simply means that you've now got a horde of competitors on your tail, and you have to stay ahead of them by running as fast as you can. As developers we want to show how fast and confident we are, but this overconfidence can many times make us fall into the same mess as the beginning.
The only way to go fast, is to go well.
In this pain I find the value of this book. This is its hypothesis, and I buy it. Having ugly architectures makes you highly vulnerable of being trapped in a downward spiral of exceptions and debugging, which we all know lead to burnout and rage-quitting, and don’t help in building systems with long profitable lifetimes. In the end, as with most things, this is also about our mental health.
C2: ✌️ A Tale of Two Values
Behaviour and Structure
Every software system provides two different values to the stakeholders: behavior (money-making functional requirements) and structure. Unfortunately, developers often focus on the former, leaving the software system eventually valueless.
The second value of software has to do with the word "software", composed from "soft" and "ware". The word "ware" means "product"; the word "soft"... Well, that's where the second value lies.
Software was invented to be "soft." It was intended to be a way to easily change the behaviour of machines. If we'd wanted the behaviour of machines to be hard to change, we would have called it hardware.
When in a system the cost of change exceeds the benefit of change, the system is practically impossible to change.
Important vs Urgent
Behaviour is urgent but not always particularly important. Architecture is important but never particularly urgent.
Four couplets of problems sorted by priority:
- Urgent and important
- Not urgent and important
- Urgent and not important
- Not urgent and not important
Whereas behaviour occupies the first and third positions, architecture is in the top two.
The dilemma for software developers is that business managers are not equipped to evaluate the importance of architecture. That's what software developers were hired to do. Therefore it is the responsibility of the software development team to assert the importance of architecture over the urgency of features.
Fight for the Architecture
Effective software development teams squabble with all the other stakeholders as equals. Remember, as a software developer, you are a stakeholder. You have a stake in the software that you need to safeguard. That's part of your role, and part of your duty, and it's a big part of why you were hired (I realized this rather late in my career!).
Part II: 🧱 Programming Paradigms
C3: 🪂 Paradigm Overview
Interesting food for thought in this short chapter from Martin.
| Paradigm | What does it impose discipline on? |
|---|---|
| Structured | Direct transfer of control (removes goto) |
| Object-Oriented | Indirect transfer of control (removed function pointers) |
| Functional | Assignment (removes assignments) |
C4: 🛖 Structured Programming
Testing shows the presence, not the absence, of bugs
Dijkstra
A program can be proven incorrect by a test, but it cannot be proven correct. All that tests can do, is allow us to deem a program to be correct enough for our purposes. You can have 100% coverage, but if you missed an 'else' statement, test won't catch that, but customers will (yikes).
One should strive to define modules, components and services that are easily falsifiable (testable).
C5: ⭕ Object-Oriented Programming
The important learning here I think is the power polymorphism can grant to your architecture.
Note, however, that the source code dependency between ML1 and the interface I points in the opposite direction compared to the flow of control. This is dependency inversion, because goes against the execution flow. In other words, HL1 does not have to mention the name of ML1, that is, it does not have to import it explicitly.
In the future ML1 can be swapped with ML2. This polymorphism means that any source code dependency, no matter where it is, can be inverted.
In turn, this makes the three components independently deployable and independently changeable.
C6: 🏃♂️ Functional Programming
As an architect, you should be interested in the robustness of your architecture in the presence of multiple threads and processors. One thing to not lose of is mutable variables.
Mutability of variables open a box of problems: race conditions, deadlocks and all sorts of concurrent update problems. If we had infinite storage and infinite processing speed, immutability would be practicable. We know it's not, so we can still achieve practicable immutability with some compromises.
Segregation of Mutability
If we split an application (or its services) into mutable and immutable components, the latter will perform tasks in a purely functional way, much like a functional programming method that sums an array of strings. Mutating state is kept enclosed in mutable components that treat variables in memory the same way as database treats records on disk (transactional memory).
As an example, a Java for loop to calculate the square of the first 25 integers mutates the current index, say i, whereas a Clojure loop does not. Variables in functional languages do not vary, as weird as that sounds!
Event Sourcing
Imagine a banking application that stores account balances. It mutates those balances for each deposit and withdrawal. We can think of a scheme were instead of balances, the application stores all the transactions, therefore requiring no mutable variables.
Obviously, this approach needs unbounded processing power and storage. But perhaps we can make this work for the reasonable lifetime of the application. Perhaps we can save the state every midnight. This is the idea behind event sourcing, storing transactions, not state.
In this manner, applications are not CRUD, but CR. Also, without updates or deletions, there are no concurrency issues. As an example, this is exactly how source code control system works.
Comments
Thank you for your comment! Under review for moderation.