Rob Cook


Home | Donate

Clean architecture digested


2024-06-06

Robert Martin's book Clean Architecture was published eight years ago. Since then it has been the subject of a lot of discussion, videos, talks, and blog posts. Here I attempt to present a digested form of its key concepts, along with some observations and commentary of my own.

Why build software?

In short it has value. Typically this is expressed as value to our end users, but there is a problem with this view. It ignores the "soft" part of software, the thing that makes it malleable. Therefore we should consider software as having two values:

As developers we should be advocating for the latter value. It is the other stakeholders that will (usually loudly) advocate for the former.

But what if we ignore structure? We get the Big Ball of Mud! Over time change becomes all but impossible, progress drops to near zero, and the morale of developers is eroded.

How to structure software?

SOLID principles can guide us at the level of classes and methods. A new set of principles are needed at the level of "components" - a set of architecture principles.

A brief recap on SOLID:

The principles for components are a little different. But what is a component?

This is a bit of a sparse description, but it is the best the book has to offer. Components have principles governing cohesion and coupling. Many are not much more than restatements of SOLID principles for a higher level of code organisation.

Cohesion

Reuse / release equivalence states that the unit of reuse (a DLL for example) is the unit of release. Therefore each component should be versioned and be independently deployable.

Common closure instructs us to collect together in one component the things that change at the same time and for the same reason.

Common reuse on the other hand is merely a restatement of interface segregation for components. Don't depend on what you don't need.

You should hopefully see a tension between these last two principles. In-fact all three are in effect pulling your components in different directions. This is expressed in the book as a triangular diagram, with components falling somewhere in its area. Where that is will change over time.

Coupling

Allow no cycles in the dependency graph. This one should be obvious to anyone who has worked on code with an acyclic set of dependencies! We want a reproducible build process.

Depend in the direction of stability. That is, depend on units that have fewer reasons to change themselves.

Have stable abstractions. There are metrics given in the book for calculating stability and abstraction, but I found these to be somewhat academic. You may derive some use from them however.

What is architecture?

The overall shape of the software is its architecture (with shape being how things are organized and talk to each other). The principle goal of architecture is to keep as many decisions deferred for as long as is practical. This will maximise the flexibility of the system.

Separate the "policy" from the "details". Enterprise and application logic should be kept ignorant of concerns such as the database, UI, or frameworks in use. We can then change the details without affecting the policy.

In effect there is an inside and an outside to our system, with the former kept insulated from the latter.

Independence

There are three main modes of decoupling our components:

The first on that list can be seen as contradicting the earlier definition of a component as a deployable unit of code. If all our code is nicely organised but deployed in one artefact, it is essentially one component. But, we can be pragmatic about this. It is possible to have one monolithic component that is internally sporting a clean architecture.

The ideal level of decoupling will change as the application grows. Over time, it should be possible to move from a monolith to a set of services (and back again). All of this is easier said than done.

Boundaries

Architecture is largely the process of applying boundaries between components. The rule here is that "low-level" components (that is, components closer to the input and output of the system) may cross boundaries to "high-level" components, but not the other way around. (To my eye, this is just a restatement of the earlier idea of policy and details and how we separate the two.)

We are ultimately aiming for a plug-in architecture. One where business rules are ignorant of lower level concerns, and we can swap them out as the need arises. Decisions on implementing them can all be deferred.

Boundaries are expensive to implement however, with interfaces and data transfer objects on either side. The book briefly described using the strategy or facade patterns to implement partial boundaries, but it wasn't quite clear to me how this was achieved.

The advice for when to implement a boundary was "watch and wait" and "implement at the right time". So, more art than science here.

One notable observation from this section of the book was that services - whilst being the highest level of decoupling - don't necessarily guarantee architecturally significant boundaries have been drawn. Anyone left tending the dreaded distributed monolith can attest to that!

Business logic

Finally on the point of architecture, a distinction is drawn between two types of business logic:

The former logic is applicable across the whole enterprise and not just a single application. Think of a compound interest calculation for a banking application. These are typically modelled as "entities".

The latter is application specific stuff that takes input, orchestrates the actions of entities, and generates some output. Think of a rule to validate some input.

The clean architecture

Finally, a diagram! The arrows on the diagram represent the direction of dependencies. Interface adapters turn raw external inputs and outputs into a format more suitable for the use cases to consume.

Details

Now we can talk about the details. Things such as the database, the web, and frameworks. Yes, all these things are considered details by the clean architecture. Things that can and should be deferred as long as possible whilst we focus on our business logic.

The database is a detail. Your data is important, not the database. The shape of the data in memory (your data model) is important, the shape on disc is not.

The web is just another UI, and we keep UIs separate from our business logic.

Frameworks are a detail. Don't marry your framework - it's a one-way relationship. Your solution should scream the architecture not the framework. (Opening a solution and going "ah, this an MVC application" isn't telling you very much that is useful about the architecture.)

Conclusion

I think the book presents a somewhat idealized view of architecture. The same things are stated in several different ways throughout the text. Abstracting everything away as "details" is a noble endeavour, but not without its costs.

The principle things to take away for me are:

end