Demystifying Clean Architecture

Armando Picón
5 min readFeb 1, 2022

--

¿Buscas la versión en español de este artículo? Haz click aquí

Before We Start

If this is your first time reading about Clean Architecture, you need to keep the following in mind,

  • Clean Architecture is the title of a book by Robert C. Martin, aka. Uncle Bob (2017). There is an article in their blog with the same title.
  • “Clean Architecture” is not actually architecture itself, but a guide with a specific set of principles. The author tries to synthesize elements in common on several architectures.
  • SOLID is an acronym created by Uncle Bob for his first five object-oriented design principles. “Clean Architecture” uses those principles as its foundation.
  • Uncle Bob’s guide leads you to structure your app with the Domain layer as its center. This is where you will find the application’s rules.
  • Developers often confuse N-layer-based architecture with “Clean Architecture.”
  • “Clean Architecture” is a mainstream phrase that developers use as an easier way to describe application structures to others.

What is architecture?

Architecture is a structure, a way to distribute your application components in various groups. There is not a single way to organize your projects because it could depend on several aspects like project state, team size, time constraints, etc.

Clean Architecture on Android

Many developers point out Separation of Concerns and Testing as two of the main aspects of Clean Architecture. However, they forget that it is imperative to have a well-implemented Domain layer. Why? Because all of the application and business rule components are located in the Domain layer; they control the entire application.

How then would a project based on these principles be structured? There are two considerations to take into account before defining the structure:

  • The Dependency Rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything about something in an outer circle.
  • There are data that need to cross boundaries, from out to inner layers and vice-versa. Because we have to respect the Dependency Rule, we solve this issue by using the Dependency Inversion principle.

Now, we can create a basic structure using three modules‒ Presentation, Domain, and Data. They will represent each one of the layers.

What components go into each layer?

Remember, when we speak about architecture, we are speaking about structure and organization. This depends on the purpose of every component I added to the application.

The following describes a three-layered architecture represented in three modules:

  • The Domain module contains components with the business and application rules like Interactors, Use Cases, and Domain classes, along with, the abstractions of repositories represented by interfaces. This module will be without reference to the Android Framework, and it should be created as a Kotlin Gradle module.
  • The Presentation module contains every UI element like Views or Composable functions. Under patterns like MVP or MVVM, components like Presenters or ViewModels reside here. Because this module will use dependencies from the Android Framework, this module must be created as an Android Gradle module.
  • The Data module contains every component related to consuming external services and the local persistence of data. Client implementations of Room, Retrofit, SharedPreferences or/and any third-party dependency will be placed in this module. Additionally, if we declare the abstractions of repositories in the Domain layer, the concrete implementations will be into the Data module too. This S sounds obvious, but this module must be created as an Android Gradle module too.

Dependency Inversion Principle

Previously, we mentioned data needs to cross boundaries between layers. How do you consume components from the Data layer into components of your Domain layer? The answer is the Dependency Inversion principle.

This principle establishes two rules:

  • High-level modules should not import anything from low-level modules. But they should depend on abstractions (interfaces).
  • The abstractions should not depend on details (concrete implementations), but details should depend on abstractions.

According to these rules, we need to add an interface to the Domain module. This represents the operations that will be available in the Repository class. Adding an interface to the Domain module isolates the Use Case from any specific implementation. Following these rules, we also make the Data module depend on the Domain module, which helps us keep the concrete implementation separated from the Domain.

In order to ground these concepts, we will see how to put them in code. These definitions below t will be placed in the Domain module.

interface BookRepository {
suspend fun getBooks():List<Book>
}
class GetBooksUseCase(
private val bookRepository: BookRepository
) { /*...*/ }

Meanwhile, in the Data module, we will find the concrete implementation of that repository.

class BookRepositoryImpl (...) : BookRepository {
override suspend fun getBooks(): List<Book> { /* ... */}
}

Thus, having the repository abstraction will help us inject this repository into the Use Case component.

Final thoughts

  • There is no single way to structure a project, and each architecture has its characteristics.
  • Even though you do not define an architecture, following the SOLID principles will lead you to maintain a well-organized code.
  • The revamped Android Architecture official guide offers you a structure-based in layers without a clear definition of a business layer.
  • Having a well-separated domain layer helps you define your Presentation and Data layer as you wish. For example, you could use a View-based UI system or Compose in your Presentation module, or use Retrofit, Volley, or Ktor in your Data module. Every change you made in any of these modules should not affect others.

References

--

--

Armando Picón

lifelong learner • 👨🏽‍💻 Android eng (ex: @uber ) • 🇵🇪 @ 🇨🇱 • @gdgopen • content creator @devpicon | @primosauda