Book cover

All rights reserved. Version for personal use only.

This web version is subjected to minor edits. To report errors or typos, use this form.

Home | Dark Mode | Cite

Software Engineering: A Modern Approach

Marco Tulio Valente

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 (based on a similar figure from the following post on The Clean Code Blog).

Clean Architecture

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 rules, 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 Case classes implement system-specific business rules. For example, our academic system may have a ClassDiary class that stores the list of Student objects enrolled in a Course offered in a particular semester. A business rule defines that a Student should only be included in a ClassDiary if they have have the prerequisites of the associated Course.

To avoid confusion, we would like to mention that use cases in a Clean Architecture do not have correspondence, at least direct, with use cases for documenting requirements or even less so with UML Use Case diagrams, as we studied in Chapter 3.

1.3 Adapters

In the third layer, from inside to outside, we have classes and interfaces called Adapters. They mediate the interaction between the outermost 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 its clients. 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 clients.

1.4 External Frameworks

In the outermost layer, we have classes from libraries, frameworks, and external systems. For example, it is in this layer where databases and libraries for sending emails, accessing payment providers, communicating with particular hardware, etc., are located.

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 book Clean Architecture, see how this layer is described (Chapter 22):

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, classes in a given layer should not know any class implemented in an outer layer. In his book, Uncle Bob emphatically states (also in Chapter 22):

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 use cases, it is true that they sometimes need to be updated. However, we want to avoid that these updates 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 flow from outside to inside is implemented naturally, as this follows the direction of the Dependency Rule. For example, a method in an outer layer can create an object of a type defined in an internal layer and then call a method of this object.

However, in some scenarios, a use case may have to call a method of a class 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: the use case must declare a variable of a class defined in an outer layer, which violates the dependency rule!

The solution is to have 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, other classes in this layer can call send without violating the Dependency Rule.

Now, 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 a an outer layer (MailServiceImpl) is using a code element from an inner layer. In this case, this element is an interface (MailServiceInterface).

The following class diagram illustrates the solution we have described.

Dependencies in a clean architecture

1.7 Conclusion

A Clean Architecture applies 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.

The main recommendations of Clean Architecture are the following:

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 certainly 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 outer layer should not be mentioned by the code of an inner layer? What’s the main benefit of this rule?

2. We intentionally did not mention the layer of the MailServiceImpl class in the article.

  1. If we want to have a code fully following the Clean Architecture principles, why can’t MailServiceImpl belong to the Adapters layer?

  2. In which layer would you then implement MailServiceImpl?

3. Assuming a Library System that uses Clean Architecture, answer the questions below:

  1. In this system, there is a Book class. It should belong to which layer?

  2. In this system, there is also an interface with business methods. For example, a method that returns a list of books borrowed by a certain user. This interface belongs to which layer?

  3. Regarding the interface mentioned in the previous item, there is a class that implements its methods using SQL queries. In which layer should this class be implemented?

  4. Finally, the database system (which can be MySQL, PostgreSQL, SQLite, etc.) is located on 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 scenario, do you think the adoption of a Clean Architecture might still be useful? Justify.

5. When is it not worth using a Clean Architecture?