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—that aims to achieve reusability, cohesion, technology independence, and testability. According to the classification used in Chapter 7, Clean Architecture can be considered a layered architecture.
Clean Architecture is typically illustrated using the following figure, which is based on a similar figure from a post on The Clean Code Blog.
In the following sections, we will examine each layer of the architecture.
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. For instance, consider a university with academic,
financial, outreach, and other systems. All these systems have classes
such as Student
, Instructor
,
Course
, Department
, etc. These are called
Entities. Besides data, entities can implement generic business rules.
For example, a rule in our university might specify that every
Instructor
must belong to exactly one
Department
.
Use Cases are classes that implement operations relevant to the system domain. In the context of our university system, we would have use cases for creating a new class for a certain course, enrolling a student in a class, canceling a student’s enrollment, and recording the subjects covered in a class, among others.
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).
Consider, for example, a system that uses a REST API for communication with the frontend components. In this system, the adapters implement the API’s endpoints. Specifically, they 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 for transmission to the frontend.
1.4 External Frameworks
In the outermost layer, we have classes from libraries, frameworks, and external systems. For example, this layer includes databases, as well as libraries for sending emails, accessing payment providers, communicating with particular hardware, and other external services.
To illustrate, a university may have a system for managing extension courses that accepts payment via credit cards. This system uses a third-party provider, which offers classes for payment processing. These classes are therefore 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, while it is true that they sometimes need to be updated, this should not be 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 remain clean
from any specific 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 illustrate, suppose that a use case
needs to send an email using a class, implemented in an outer layer,
called MailServiceImpl
with a send
method:
public class MailServiceImpl {
public void send(String msg) { ... }
}
However, this scenario implies a control flow from inside to outside. Specifically, the use case would need to 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 serves as an abstraction for the mail service. In other words, it prevents the use case from relying on a concrete implementation of this service.
Furthermore, since MailServiceInterface
is part of the
Use Case layer, other classes in this layer can call send
without violating the Dependency Rule.
The MailServiceImpl
class should then 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 such as 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, consider its Entities, which are classes that mainly store data and can be reused in other systems you will build in the future.
Then, focus on the Use Cases, which implement business rules related to the Entities of your system. Ensure that the classes representing Entities and Use Cases remain independent of any technology. Remember,
the Web is a detail; the database is a detail.
Finally, design the Adapter classes, which function as translators facilitating 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. As a result, it will be easier to test your system and adapt it to new technologies that will emerge in the future.
More Information
If you wish to learn more about Clean Architecture, you can refer to the book of the same name by Robert C. Martin (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 is 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. In which layer should it be implemented?The system also includes an interface that abstracts the database. For example, it defines a method that retrieves a list of books borrowed by a certain user. To which layer does this interface belong?
There is also a class that implements the interface mentioned in the previous item. Specifically, this class uses several SQL queries. In which layer should it be implemented?
Finally, in which layer is the database system (which can be MySQL, PostgreSQL, SQLite, etc.) located?
4. Suppose a system uses technologies X, Y, and Z, and we are certain that these technologies will never change in the future. In this scenario, do you think the adoption of a Clean Architecture is still beneficial? Justify your answer.
5. Under what circumstances might it not be worthwhile to use a Clean Architecture?
Check out the other articles on our site.