Last year, I was hired by a company to rewrite their application. I faced with an application that they can’t scale and maintain it anymore. This was my starting point to focus on the Clean Architecture. There I knew the benefit of good architecture in big projects.
In this article, I’ll share my knowledge and explain the concept of Clean Architecture and how to use it correctly.
Before we begin #1
It is advised to know a little bit about Uncle Bob Clean Architecture before reading this article and I did my best to add links for further reading throughout the post.
Although clean architecture can be a bit hard at first look, with this article and sample code it’s gonna be easy as pie :)
The Clean Architecture
Good architecture is a key to building a modular, scalable, maintainable and testable application. Though architectures all vary somewhat in their details, they are very similar. They all have the same objective, which is the Separation of Concerns. They all achieve this separation by dividing the software into layers.
If you have at least a couple of years of software development experience under your belt, then I can almost guarantee you’ve heard the term “Separation of Concerns”. It’s a principle that shows up quite often when talking about software architecture and the common topic that may hear is “Separate presentation from business logic”.
Clean Architecture separates your application code into layers and these layers define the Separation of Concerns inside the codebase. The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point in an outer circle. Nothing in an inner circle can know anything at all about something in an outer circle.
So, why should we use architecture?
As Uncle Bob said, architectures have the same objective, which is the separation of concerns. They all achieve this separation by dividing the software into layers. Each has at least one layer for business rules and another for presentation or interfaces.
By employing Clean Architecture, you can design applications with very low connection and independent of technical implementation details, such as databases and frameworks. That way, the application becomes easy to maintain and flexible to change. It also becomes intrinsically testable.
The benefits that clean architecture brings to us:
- Independent of Database and Frameworks. The software is not dependent on an ORM or Database. You can change them easily.
- Independence of UI. The UI can change easily, without changing the rest of the system and business rules.
- Testable. Now it is intrinsically testable. You can test business rules without considering UI, Database, Mock servers, etc.
- Independence of any external agency. In fact, your business rules simply don’t know anything at all about the outside world.
Clean Architecture Layers
Presentation Layer contains UI (Activities and Fragments) that are coordinated by Presenters/ViewModels which execute use cases. The presentation layer depends on the domain layer.
Domain Layer is the most INNER layer and contains UseCases/Interactors and Repository Interfaces. UseCases defines specific business rules in our application so by looking at use cases you can know what the application is doing. On the other hand, we have bare interfaces for repositories.
Use cases orchestrate the flow of data and the domain layer has no dependency on other layers.
The domain layer is at the center of the onion layers and the most important layer of our application.
Data Layer contains Repository Implementations and Data Sources. Database, Network APIs and other sources implement in this layer. Data Layer depends on the Domain Layer.
Dependency Rule
The Dependency Rule is the relationship that exists between the layers. It is important to follow it.
As you see, the Domain layer has no dependency on other layers but Presentation and Data layers have a dependency on the Domain.
Data and Presentation layers can replace easily but the Domain can be pure java or kotlin if you want to have this application on other platforms.
Explaining the Domain Layer by example
What is the problem that we need to solve?
“Load a post with its comments”
Ok. In the Domain layer, we need a use case for loading a post and its comments so we need two repositories, the first one is RepositoryPost and the second one is RepositoryComment, then we inject them to use in GetDetailUseCase.
class GetDetailUseCase @Inject constructor(
private val repositoryPost: RepositoryPost,
private val repositoryComment: RepositoryComment,
private val transformer: STransformer<DetailObject>
) : UseCaseSingle<DetailObject, Int>() {
override fun execute(param: Int): Single<DetailObject> =
flatMap(
{ repositoryPost.loadPost(param) },
{ repositoryComment.comments(param) },
{ post, comments -> DetailObject(post, comments) }
).compose(transformer)
}
So how can we work with these repositories? We just have bare repository interfaces in the Domain layer. The implementation of these interfaces is in the Data layer.
The question here is “How can we work with them in the Domain layer?” Or it’s better to say “why we have repository interfaces in the Domain layer? ”
Cause of Dependency Rule in Clean Architecture.
The Domain layer doesn’t have a dependency on the Data layer to work with repositories. So how can we access another module without dependency on it? The answer is Dependency Inversion (D Principle in SOLID) and Dependency Injection (DI). Jesus Christ!!!
As I said, we have repositoryImpls in the Data layer and they have to access the Domain repository interfaces to implement them. Let’s continue, Dagger (DI Framework) came to help us to complete this part. We should bind RepositoryImpls in the Data layer so our use cases can inject them.
*** The point is Use cases Orchestrate repositories ***
Interface Adapters
“The software in the Data layer is a set of adapters that convert data from the format most convenient for the use cases and entities to the format most convenient for some external agency such as Database or the Web.”
So we need interface adapters to map out data objects to use them by another layer and this job is handled by the Data layer.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Final recap
Remember to keep our domain layer with no dependencies.
Clean architecture is an Onion Architecture. The onion architectures rely heavily on the Dependency Inversion principle. The application core needs an implementation of core interfaces, and if those implementation classes reside at the edge of the application, we need some mechanism for injecting that code at runtime, so the application can do something useful.
The database is not the center. It is external
With onion architecture, there are no database applications. There are applications that might use a database as a storage service.
Done
By clicking on the link take a look for a better understanding of how to implement and use it.
Remember to follow, share, and hit the 👏 button if you’ve liked it! :)
Thank you for reading.