Software Engineering: A Modern Approach
1 Building Software with a Clean Architecture
1.1 Introduction
Clean Architecture is an architectural pattern proposed by Robert Martin—also known as Uncle Bob—aiming to achieve reusability, cohesion, technology independence, and testability. If we follow the classification used in Chapter 7, Clean Architecture can be considered as a layered architecture.
Typically, Clean Architecture is illustrated using the following figure, which is based on a similar figure from the following post on The Clean Code Blog.
Next, we discuss each of the architecture layers.
1.2 Entities and Use Cases
At the center of the architecture, we have the classes responsible for business concerns, which can be of two types: Entities and Use Cases.
Entities are classes shared by many systems in an
organization. Suppose, for example, a university with academic,
financial, outreach, etc., systems. All these systems have classes such
as Student
, Instructor
, Course
,
Department
, etc. They are then called Entities. Besides
data, entities can implement generic business rules. For example, a rule
in our university defines that every Instructor
must belong
to exactly one Department
.
Use Cases are classes that implement operations relevant to the system domain. For example, the system of our university should have use cases for creating a new class for a certain discipline, enrolling a student in a class, canceling a student’s enrollment, storing the subjects covered in a class, etc.
1.3 Adapters
In the third layer, from inside to outside, we have classes and interfaces called Adapters. They mediate the interaction between the external layer of the architecture (External Frameworks) and the central layers (Use Cases and Entities).
Assume, for example, a system that uses a REST API for communication with the frontend components. In this system, the adapters should implement the API’s endpoints. That is, they must receive the requests and forward them to the corresponding use cases. Additionally, they are responsible for the reverse path: they receive the results returned by the use cases and convert them into JSON documents to be sent to the frontend.
1.4 External Frameworks
In the most external layer, we have classes from libraries, frameworks, and external systems. For example, the databases are located in this layer as well as libraries for sending emails, accessing payment providers, communicating with particular hardware, etc.
In our example, the university may have a system for managing extension courses, which accepts payment via credit cards. For this, the system uses a third-party provider, which offers classes for payment processing. Therefore, such classes are located in the outermost layer of a Clean Architecture.
In the Clean Architecture book (Chapter 22), this layer is described as follows:
The frameworks and drivers layer is where all the details go. The web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.
1.5 Dependency Rule
In a Clean Architecture, the classes in a given layer should not reference any class implemented in an outer layer. Also in Chapter 22 of his book, Uncle Bob emphatically states that:
Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes functions, classes, variables, or any other named software entity.
Thus, in a Clean Architecture, the central layers are more stable— less subject to changes—than the outer layers. For example, the entities rarely need to be modified. Regarding the use cases, it is true that they sometimes need to be updated. However, this should not occur due to changes in the technologies used in the application, such as databases, frameworks, and libraries.
In summary, the Dependency Rule ensures that entities and use
cases are classes that should be clean
from any
technology or external frameworks.
1.6 Inversion of Control
In a Clean Architecture, control that flows from outside to
inside
is implemented naturally, as it follows the Dependency Rule.
For example, a method in an outer layer can create an object of a type
defined in an inner layer and then call one of its methods.
However, in some scenarios, a use case needs to call a method
implemented in an outer layer. To give an example, suppose that a use
case needs to send an email. First of all, let’s assume that there is a
class, implemented in an outer layer, called
MailServiceImpl
with a send
method:
public class MailServiceImpl {
public void send(String msg) { ... }
}
However, this example implies a control flow from inside to outside. Particularly, the use case must declare a variable of a class defined in an outer layer, which violates the dependency rule.
Thus, the solution is to define an interface in the use case layer
called MailServiceInterface
with a
send(String msg)
method.
package UseCases;
public interface MailServiceInterface {
void send(String msg);
}
// other classes
This interface works as an abstraction for the mail service. That is, it prevents the use case to rely on a concrete implementation of this service.
Furthermore, since MailServiceInterface
is part of the
Use Case layer, the other classes in this layer can call
send
without violating the Dependency Rule.
Finally, MailServiceImpl
should implement the
MailServiceInterface
.
import UseCases.MailServiceInterface;
public class MailServiceImpl implements MailServiceInterface {
public void send(String msg) {
// calls external service to send an email
}
}
This implementation does not violate the Dependency Rule, as a class
from an external layer (MailServiceImpl
) is using a code
element defined in an internal layer. In this case, this element is an
interface (MailServiceInterface
).
The following class diagram illustrates the solution we have described.
1.7 Conclusion
A Clean Architecture considers several concepts we studied in Chapter 5, including design properties like cohesion, coupling, and separation of concerns, and design principles like Single Responsibility and Dependency Inversion. It also uses the Adapter design pattern, studied in Chapter 6.
In summary, the main recommendations of Clean Architecture are the following:
When implementing an application, think on its Entities, which are classes that mainly store data and that could be reused in other systems you will build in the future.
Then, think about the Use Cases, which implement business rules related to the Entities of your system. But make the classes representing Entities and Use Cases
clean
of any technology. Remember,the Web is a detail; the database is a detail.
Finally, think about the Adapter classes, which function as translators enabling communication between the internal classes and the external world.
By following these recommendations, you will define an architecture that separates two types of interests (or requirements): business interests and technology interests. Thus, it will be easier to test your system and adapt it to new technologies that will appear in the future.
More Information
If you want to learn more about Clean Architecture, you can check the book of the same name by Uncle Bob.
Exercises
1. In a Clean Architecture the name of an element declared in an
external layer should not be mentioned by the code of an internal
layer
? What’s the main benefit of this rule?
2. We intentionally did not mention the layer of the
MailServiceImpl
class in the article.
If we intend to fully follow the Clean Architecture principles, why can’t
MailServiceImpl
be implemented in the Adapters layer?In which layer should we implement
MailServiceImpl
?
3. Assuming a Library System that uses a Clean Architecture, answer the questions below:
In this system, there is a
Book
class. It should be implemented in which layer?In this system, there is also an interface that abstracts the database. For example, it defines a method that retrieves a list of books borrowed by a certain user. This interface belongs to which layer?
There is also a class that implements the interface mentioned in the previous item. Particularly, this class uses several SQL queries. In which layer should it be implemented?
Finally, the database system (which can be MySQL, PostgreSQL, SQLite, etc.) is located in which layer?
4. Suppose that a system uses technologies X, Y, and Z. And suppose that we are sure that they will never change in the future. In this case, do you think the adoption of a Clean Architecture is still useful? Justify.
5. When is it not worth using a Clean Architecture?