/clean-architecture
Notes on Uncle Bob's Clean Architecture — what a good architecture is for, and what it makes cheap.
“The architecture represents the significant design decisions that shape a system, where the significance is measured by the cost of change.”
“If you think good architecture is expensive, try a bad architecture.”
Software architecture rules are independent from all other variables.
what is architecture?
The architecture of a software system is the shape given to this system by its creators. This shape lives in the division of the system into components, in the organization of those components, and in the way they talk to each other.
The primary purpose of architecture is to support the system life cycle. A good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The final goal is to minimize the life-span cost of the system — not to maximize programmer productivity.
development
A system that is hard to develop probably won’t have a long, healthy life. The architecture should facilitate the work of the developer teams.
Different team structures call for different architectural decisions. A small team often won’t think too much about it — early on, structure feels like an obstacle. That’s probably why so many systems don’t have a good architecture: they start without one. A bigger org with many small teams will tend to split components per team, which is good for the development schedule but rarely optimal for deployment, operation, and maintenance.
deployment
The bigger the cost of deployment, the less useful the system will be. Unfortunately, it’s rare for the deployment strategy to be considered during initial development. The result: architectures that make the system easy to develop and hard to deploy.
operation
The impact of architecture on operation tends to be less dramatic than on development, deployment, and maintenance. Operational cost can often be solved by adding hardware without much architectural impact — poor-architecture systems frequently work fine by simply throwing more storage or servers at them. Hardware is cheap, people are expensive.
That doesn’t mean operation should be ignored. It just bites less.
maintenance
Of all aspects of a system, maintenance is the most expensive. The sum of new features, inevitable faults, and corrections consumes huge amounts of human time.
A well-thought-out architecture mitigates these costs. When we separate the system into components and isolate them through stable interfaces, we mark the path for future work and drastically reduce the risk of unexpected behavior.
keeping options open
To keep your software soft, keep the maximum number of options open for the longest time possible. Which options? The details that don’t matter.
All software can be decomposed into two main elements: policy and details. Policy wraps the business rules and procedures — this is where the true value of the system lives. Details are the necessary items for humans, other systems, and programmers to communicate with the policy.
The architect’s goal is to design the system so it recognizes policy as the essential element, and treats details as irrelevant to that policy. This is what allows decisions about details to be delayed and deferred.
In developer terms: the high-level policy doesn’t have to care about your database choice, your framework, your servers.
Develop the high-level policy without committing to its details, then defer those decisions for as long as possible. The longer you wait, the more information you have to make them right.
The more you keep options open, the more you can experiment, the more you can test — and the more you’ll know when the decision finally can’t be deferred any longer.
What about decisions someone else already made? A good architecture pretends they were never made, and shapes the system as if they could still be delayed or changed for the longest time possible.
“A good architecture should maximize the number of decisions not made.”
independence
A good architecture must support:
- use cases and operations
- maintenance
- development
- deployment
use cases
The architecture must support the system’s intention. This is the first priority. The most important thing a good architecture can do is to expose this intention in a way that’s visible at the architectural level.
Those behaviors are first-class elements at the top: classes, functions, or modules with prominent positioning and descriptive names that make their function obvious.
development
Conway’s Law:
Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.
A system must allow teams to act independently without overlap. That requires precise partitioning of independent components.
deployment
A good architecture lets the system deploy immediately after build — also a consequence of clean component partitioning.
decoupling layers
The architecture wants to support every use case the system will need, but the architect doesn’t know what they all are yet. They do know the basic intention. So apply Single Responsibility and Open/Closed: separate things that change for different reasons; group things that change for the same reason.
Rules change at different rhythms and for different reasons. They must be separated so they can be modified independently.
This gives the familiar horizontal layering: UI, application-specific business rules, application-independent business rules, database, and so on.
decoupling use cases
Use cases are a natural way to divide a system. Every use case uses a little UI, a little application-specific logic, a little independent business logic, a little database. Dividing horizontally by layer AND vertically by use case means new use cases stop touching the old ones.
decoupling mode
The decoupling that helps use cases also helps operations — but to enjoy the operational benefit, the decoupling has to happen the right way. To run on separate servers, the separated components can’t share an address space. They must be independent services, communicating across the network.
A good architecture keeps options open. Decoupling mode is one of those options.
duplication
Architects and developers often fall into a trap: the fear of duplication.
Nobody likes duplicate code. We feel morally obligated to exterminate it. But there are two kinds:
- true duplication — every change to one instance forces the same change in all copies.
- false / accidental duplication — two pieces of code that look similar today but evolve along different paths, changing at different rates and for different reasons. Come back in a few years and they’re nothing alike.
When you split a system vertically by use case, you’ll meet this problem. The temptation will be to couple the use cases because they share UI patterns, algorithms, queries, etc. Resist. Don’t reflexively dedupe. Check first whether the duplication is real.
ways of decoupling
- source-code level
- deployment level
- service level
It’s hard to pick the right one at the start of a project. As the project grows, the best level can change.
A good architecture should let the system start as a monolith deployed as a single artifact, then grow into independent deployment units — services, micro-services — and still allow walking the path back to a monolith if needed.
A good architecture protects most of the source code from these mode changes. It keeps the decoupling mode open as an option, so big deployments can use one and small ones another.
conclusion
It’s complicated.