The Only Clean Architecture Intro You Will Ever Need
Browsing on the internet, you would find quite a few introductions about Clean Architecture. I've read most of them in the past as I got into the Clean Architecture myself, but I couldn't grasp the overall picture.
To understand a certain topic or concept, you should read more than just one take. So why do I say that this is the only CA intro you'll ever need?
I think most of the introductions about Clean Architecture focus too much on the technical nuances of each layer and low-level details. My take is focused mostly on the concept, the core idea, and how to quickly get started no matter the language or framework you're using.
What is Clean Architecture?
Clean Architecture as a concept was first presented in 2017 by Robert C. Martin better known as Uncle Bob in his book.
He acknowledged that while the concept might seem new, it was inspired by existing architectures such as Hexagonal Architecture, Boundary Control, Entity Architecture, and others.
A common theme across these architectures including the Clean Architecture is a strict separation of concerns[1]. Not to be confused with Single Responsibility Principle, the separation is focused at the code-base level instead of individual classes.
The goal of Clean Architecture is a system where the business logic is at the core and completely independent from the technical details such as a framework or a database.
By designing the system this way and keeping a strict separation between business logic and the technical details, you can in theory swap framework or database that prevents vendor locking. On top of that, tests become arguably easier to write.
5 main principles of a Clean Architecture
Framework independent - The architecture should not depend on the existence of a framework or external library. The framework should be used as a tool rather than designing the whole system, especially your core application logic, around it.
Database independent - The database should be seen same as the framework, just as a tool or technical detail. It can be swapped for different Database implementation or storage mechanism if needed.
UI independent - The UI can and will change. This change should not affect the entire system however. Furthermore, the UI could be replaced as well, for instance replacing web UI with console, etc.
Independent of external agency - The core of your application, business logic, shouldn't know anything about system and how it interacts together. In a nutshell, you should never write business rules that would behave in a certain way when called from a specific class.
Easily testable - Business logic, which sits at the center of your application, defines business rules. Since these are not dependent on framework, database, or UI, they are easy to test.
Layers of a Clean Architecture
If you open up Google and search for Clean Architecture in images, you will usually find out an image with four circles or layers.
Beware that the outer two can sometimes be represented as one, resulting in three layers. See the following image:
As mentioned before, the core part of Clean Architecture is business logic. This is represented by the two inner most circles or layers.
Manifestation of business logic is a business rule. The business rules can be divided into two categories - application independent and automation rules.
The smallest circle, or the one at the center, represents Domain
or Entities
(depending on the image you're looking at), or in other words application independent business rules. Let's start with that.
1. Domain/Entities - Application independent business rules
The business rules independent of the entire application sit at the very core of the Clean architecture. These rules should be defined by the actual rules your business operates on even if computers wouldn't exist.
For example interest calculation, accounting rules, local laws, etc. These rules would have to be applied even if a client fills out a paper form.
As with all other layers, they might know about the inner layers but never about outer layers. That means the Domain
layer should never call or access Application
layer (Use cases) nor any other layer.
The Domain layer should also not depend on a framework or database. Any import from a framework library or 3rd party package here is a red flag. If done right, it can be easily and extensively tested since it doesn't depend on anything.
2. Application/Use cases - Business automation rules
Just to be clear, the word automation describes the use of a system, for instance, an API for calculating interest for loans. Basically what was done manually in the past and now happens with computers. Not to be confused with some API or programmatic automation.
This layer defines a complete business logic process, orchestrates the underlying Domain
layer or Entities
and authorizes/validates whether a specific business operation can be completed by given actor.
Beware that the Application
layer shouldn't care about how the business process is implemented in the outer layers. In practice, it shouldn't care about whether a specific business operation is called via HTTP
endpoint vs GraphQL
mutation.
As with the previous layer, the Application layer should also not depend on any framework or database specifics. Any import from framework is again a red flag and violates the separation of layers.
You can pass dependencies to this layer via dependency injection, which makes it possible to either swap or mock the implementation. If done right, this layer would also be easy to test.
3. Interface adapters - Controllers, presenters, gateways
We're getting to the layer that's placed outside of the business logic. This layer in Clean Architecture can exist either separately or co-exist with the Infrastructure/Framework layer depending on the project needs.
To put it simply, Interface adapters
are the middleman between the outside world and your business logic. Controllers handle input, presenters prepare output, and gateways interface with the database.
4. Frameworks and drivers - Framework, DB, UI
The outermost layer in Clean Architecture represents everything that should be considered a detail. Having a strict separation between your business logic (two inner-most layers) and this layer is what you should aim for if you want to stick to the CA principles.
Keep in mind that while your outer layers can import stuff from inner layers, the opposite is not true.
Is Clean Code same as Clean Architecture?
"Good software systems begin with clean code. On the one hand, if the bricks aren’t well made, the architecture of the building doesn’t matter much. On the other hand, you can make a substantial mess with well-made bricks." — Uncle Bob
Based on the quote from the author of the book itself, the answer is no. The difference between Clean Code and Clean Architecture is fairly simple, and you can see that in following analogy:
Clean Code - Are the individual bricks and materials well made?
Clean Architecture - Is the architecture of the whole building well thought out?
As Uncle Bob said, you can make a mess with well made bricks if the architecture wasn't thought out well. The concepts of Clean Architecture should be applied on the whole system, not on individual components. Vice-versa, Clean Code should be applied to individual components.
How to start implementing Clean Architecture?
As with any software architecture you may have come across, the actual implementation might differ from what is intended. Businesses have limited resources for making code perfect or sticking to the architecture.
On the other hand, having a half-baked solution in place might be more confusing than helpful. Imagine having half of your code base following CA principles and the other half doing whatever else.
Talking from experience and with a pragmatic mindset, implementing Clean Architecture is first and foremost about decoupling the business logic from your tech stack.
Practical steps to start implementing Clean Architecture:
Create a new folder outside of your existing project
Start creating your business entities (classes) representing business rules in the language of your choice (do not add any external dependencies such as framework or database). The business entity should not rely on any table representation (columns) or reflect the database relations, at this point, you only care about what it does
To better assess the business entities, think of it's purpose outside of a system. User with role
manager
is essentially a manager in the office. If there are some real-life rules belonging to this role that are applicable in your product, you should represent them in your business entities as constants or methods, etc.Unit test the business entities, at least their core functionality
Start creating use cases, i.e. business automation rules. The use case might be represented as a single class with one purpose - executing a specific business rule. Think of assigning a budget to an employee. Does the current user have permission to do so? Is the employee eligible for a budget in a given amount and in given period?
Do not import any external dependencies for the use cases as well. Instead, define interfaces. These interfaces will be a glue between your business logic and the outside layers. Your use cases can already interact with interfaces without any actual implementation.
You have now finalized the business logic, or in other words two inner-most layers. You have business entities, use cases and interfaces. All of this without any dependency. Do you start to see a benefit?
Now it's up to you how much you want to deal with the outer-most layers. To keep things pragmatic however, and assuming your project follows some framework structure, you can drop-in your business logic and start calling importing your use cases in controllers while injecting dependencies to them (manually or automatically via your framework).
Well done! ✅
Of course, there are more things to do, and it's also up to you or your team how far you want to go. However, by extracting the business logic into the separate layer(s) and keeping strict line between dependencies, you've decoupled it from the tech stack, which can potentially change.
You've also made the core functionality much easier to test and maintain because you can swap or mock the implementations.
Switched from Firebase to something else?
All good, your use case depends only on an interface, so your service implementation just needs to fulfill the requirements of an interface and can be injected into the use case.
Did you change some columns for a specific table?
All good too, since the result of service/repository calls is mapped to the specific interface. As long as the values are mapped right in your infrastructure layer (repository implementations) after the change, you don't have to worry about the business layer at all.
Should I use Clean Architecture?
Well, as with everything within software development, it depends. While Clean Architecture delivers great benefits if implemented right, it can be an overkill for a small project.
You might also be better off following a framework structure if you're not senior enough. Practical examples of implementing Clean Architecture are scarce in some languages, so you could easily get stuck.
Finally, even within a team and on bigger project, the decision depends. Clean Architecture is as much of a mindset as actual implementation of some of its principles.
Educating your teammates is crucial, and it might take time until they really see things through. Furthermore, it also takes time to switch the mindset from having no strict dependency rules to having a hard line between the business layer and all the rest.
Conclusion
Clean Architecture isn't a magic solution that automatically delivers all its benefits, nor does it fit all projects. As already mentioned, its proper implementation depends heavily on a mindset and requires a strict line between your tech stack and business logic.
It might feel counterintuitive at first, but if done right, you'll be able to decouple the business logic thus making it easier to maintain and test. Changes to the technology, framework, or database won't affect the core and if needed, you would be able to swap it with something else.