by Daniel Oliveira
How to write robust apps every time, using “The Clean Architecture”
As developers, we can’t keep from using external libraries and frameworks in our systems. The community’s hands build marvelous tools, and using them is only natural. However, everything has a downside.
Careless teams and individuals can get in a dangerous situation by structuring their systems around the tools they use. Business rules get mixed up with implementation details. This can result in a brittle system, hard to extend and maintain. What should be a quick change in the GUI ends up turning into a bug hunt that lasts for hours. But it does not have to be like this.
Software Architecture proposes models and rules to determine the structures (like classes, interfaces, and structs) in a system and how they relate to each other. These rules promote reusability and the separation of concerns for these elements. This makes it easy to change implementation details such as the DBMS or front-end library. Refactors and bug fixes affect as little parts of the system as possible. And adding new features becomes a breeze.
In this article, I will explain an architecture model proposed in 2012 by Robert C. Martin, Uncle Bob. He is the author of classics like Clean Code and The Clean Coder. In October of this year, he’ll launch another book, Clean Architecture.
The model has the same name as the book, and it’s built on simple concepts:
Divide the system’s composition into layers with distinct and well-defined roles. And restrain the relationships between entities in different layers. There’s nothing new in splitting your application in layers. But I chose this approach as it was the one that was the simplest to grasp and execute. And it makes testing use cases dead simple.
We just have to make sure the Interactors work properly, and we’re good to go. Don’t worry if the word “Interactors” seemed alien to you, we will learn about them soon.
From inside out, we are going to explore each of the layers a bit further. We’ll use a sample application that’s quite familiar to us: counters. It takes no time to understand, so we can focus on this article’s subject.
You can find a demo of the app here, and the code samples will be in TypeScript. Some of the code gists below use React and Redux. Some knowledge about these solutions can help in understanding them. Yet, Clean Architecture’s concepts are much more universal. You will be able to understand it even without previous knowledge of the mentioned tools.
Entities are in the diagram as Enterprise Business Rules. Entities include business rules that are universal to a company. They represent entities that are basic to its area of operation. They are the components with the highest level of abstraction.
In our counters sample, there’s a very obvious Entity: the
Use Cases are pointed out as Application Business Rules. They represent each of the use cases of a single application.Each element of this layer provides an interface to the outer layer and act as a hub that communicates with other parts of the system. They’re responsible for the complete execution of the use cases and are commonly called Interactors.
In our sample, we have a Use Case for
Note that the factory function for
ChangeCounterInteractor receives a parameter of the type
CounterGateway. We will discuss the existence of this type will later in the article. But we can say that Gateways are what stands between Use Cases and the next layer.
This layer consists of the boundary between the system’s business rules and the tools that allow it to interact with the external world, like databases and graphical interfaces. Elements in this layer act as mediators, receiving data from one layer and passing it forward to the other, adapting the data as needed.
In our sample, we have several Interface Adapters. One of them is the React component that presents the
Counter and its controls to
Note that the component does not use a
Counter instance to present its value, but an instance of
CounterViewData instead. We’ve made this change to decouple presenting logic from business data. An example of this is the logic of exhibition of the counter based on the view mode (Roman or Hindu-Arabic numerals). An implementation of
CounterViewData follows below:
Another example of an Interface Adapter would be our application’s Redux implementation. Modules responsible for requests to a server and the use of local storage would also live inside this layer.
Frameworks and Drivers
The tools your system uses to communicate with the external world compose the outermost layer. We don’t usually write code in this layer, that includes libraries such as React/Redux, browser APIs, etc.
The Dependency Rule
This division into layers has two main goals. One of them is to make clear the responsibilities of each part of the system. The other is to make sure that each of them fills their roles as independently from each other as possible. For this to happen, there’s a rule that states how the elements should depend on each other:
An element must not depend on any element belonging to a layer outside its own.
For example, an element in the Use Cases layer can’t have any knowledge about any class or module related to GUI or data persistence. Likewise, an Entity can’t know which Use Cases make use of it.
This rule may have raised questions in your head. Take a Use Case, for example. It’s triggered as result of user interaction with the UI. Its execution involves the update in some persistent data storage such as a database. How can the Interactor make the relevant calls to the update routines without depending on an Interface Adapter that’s responsible for data persistence?
The answer lies in an element that we’ve mentioned before: Gateways. They’re responsible for establishing the interface needed by the Use Cases to do their jobs. Once they’ve established this interface, it’s up to the Interface Adapters to fulfill their side of the contract, as shown in the diagram above. We have the
CounterGateway interface and a concrete implementation using Redux below:
You may not need it
Of course, this sample application was somewhat over complicated for an increment/decrement counter app. And I’d like to make clear that you do not need all this for a small project or prototype. But trust me, as your application gets bigger you’ll want to maximize reusability and maintainability. Good software architecture makes projects resistant to the passing of time.
Okay… So what?
With this article, we discovered an approach to decouple our systems’ entities. This makes them easier to maintain and extend. For example, to build the same application using Vue.js, we would only have to rewrite
CounterWidget components. The source code of the sample application is in the link below:
This story was translated to Portuguese by me! It is available here.
What pros and cons do you see in this approach? Have you used something similar in production? Share your experiences in the responses. If you like the article, please clap for me!