The Dependency Rule
The code we write is going to change. Let me repeat that, our code is going to change. That is the essence of software: software came into existence to easily be modified. And according to Robert C. Martin (Uncle Bob), For most systems, change is continual*. Keeping this principle in mind, Clean Architecture encourages us to structure our code in a way that these changes will take the least amount of effort to work on. We achieve this by segregating the different parts of the app.
However we cannot have a system with different components working apart from each other; we need to, somehow, make them interact as a coherent unit. But their interaction and knowledge between them should be limited, and ruled. Hence, the DEPENDENCY RULE.
Before we dive into the ocean of our main concern in this article, we should briefly have an overview of what layered architecture is. It basically splits our software into different layers, or sections. Adhering to the convention, let’s call them: Domain, Application, and Infrastructure.
In the domain layer we create the models, entities that will represent tables in our database. For instance, in a blog website, we would create the model for “BlogPost”, which will contain properties like: title, body, author, category, etc.
In the application layer we then implement what we call use cases, which are basically the actions that will be executed on the entities in the domain. For example: we create the functions to “create_blog_post”, “publish_blog_post”, “delete_blog_post, etc”.
In the infrastructure layer we put everything that will connect any external resource to our system. For example HTTP requests coming from an internet browser to our blog site, HTTP responses that will go back, and also the queries that will be sent to the storage system to retrieve information from it.
After this oversimplified overview of a layered architecture we can then see how these different parts of our software collaborate with the whole system.
The dependency rule
Think of the image above: It represents a layered, structured software. Notice the dependency arrow pointing inward through the circles, meaning that outer circles depend on the inner ones. In simple words that is: Functions, variables, classes, etc. declared in an inner layer can be used in an outer one, but not backwards.
For example, we will not have a method in our model (Domain) to connect to the database (Infrastructure) and save items there, the database is an external resource and code related to it should not be placed in our most inner layer**.
Although this code will work and serve the purpose, it will rapidly become hard to maintain if, for example, we decide to use a different database system, like MySql. Not only will 60% of the code need to change, this is the core of the app, thus, many other components may be affected by this change.
Instead we will create the connection to the database in our infrastructure layer, and the “save_blogpost” method in the application layer, which will use the Blogpost entity from the domain. like so:
Yes, I know, you noticed a 4th component that I have not mentioned: the “repository.py” which contains the implementation details to save the Blogpost in the database. This acts as an interface between the connection to the database and the Blogpost entity. Like this we separate the creation of these two from their usage; we call this practice dependency injection. Now neither of these two components directly depend on the other, rather, this interface will depend on both of them.
If a different database is now needed, the only changes required will be the connection to the new database in the infrastructure layer.
We need to prepare ourselves for the changes that will be required to the code we write. By structuring the project with a clean architecture and following the dependency rule, we ensure that those changes are applied efficiently by any developer in order to keep the business up and running.
*Clean code, page: 147 “Organizing for changes”.
**Some frameworks contrast with this approach, like Rails, which encapsulates the database interaction with the model.