Clean Architecture : A Brief Introduction

Image for post
Image for post

I will be explaining this topic in two parts. In the first part i.e in this article we will be covering the concept behind Clean Architecture and in the second part we will be looking at how to implement the same in an Android project. Now before we dive into clean architecture let’s take a look at the necessity of a good software architecture in a project.

  1. It creates a solid foundation for the software project.
  2. Makes your platform scalable
  3. Avoids codes duplicity.
  4. Higher adaptability.
  5. Better code maintainability.

and much more…

Now we know the importance of software architectures and hence it is very vital and crucial to implement the right kind of architecture for our software project. The right software architecture will help us achieve all the ones mentioned above and also much more than that. But choosing the right architecture will largely depend upon the size of the project and the problems the software is trying to solve. For smaller projects I would suggest it is not required to implement heavy software architectures. Because implementing architecture means writing lots of boiler plate code which helps you write less code in the future. But if you are developing a large software, you are definitely going to need one because you will need to scale the project and there is going to be a lot of feature implementations and refactoring involved.

Image for post
Image for post

The architecture which we will be discussing here is called the Clean architecture, it is the brain child of the Legendary Robert C. Martin aka “Uncle Bob”. I personally found it to be very beautiful in terms of implementation and the way if differentiates the different software components. In fact clean architecture has made my life as a Software Engineer much simpler

Let’s try to understand the basics

Before applying clean architecture

You can consider each thread as different modules of the project for now i.e from UI to the business logic to the data, everything that you can assume in the project and each of these are having some kind of dependency on each other. i.e all them are entangled with each other in different ways.

After applying clean architecture

All the different aspects of the project are separated clearly. So now the code becomes more readable, maintainable and scalable. The code base becomes as simple as the untangled and separated thread we see in the above picture.

How does clean architecture achieve cleanliness?

Image for post
Image for post

The above diagram represents the clean architecture.We can see the software is divided into layers. i.e the concerns are separated.The inner layers are high level implementations. i.e policies and outer layers are low level implementations. i.e mechanisms (Actual working code happen here). The inner layers become general layers as we go deep and are also more abstract.

We can see arrows moving from the outer layer to the inner layer they indicate one of the main rules of clean architecture the dependency rule. The dependency rule states that the inner layers can have no knowledge about the outer layer and no names of any function, variable or entity can be mentioned in any of the inner layer which belongs to the outer layer. That is the elements in the inner layer is going to be a general implementation which can be used by just about anyone in the outer layer for their own usage. And therefore the components in the inner layer will never know who is going to use them.

Now let’s dive into each of the layers.

1. Enterprise Business Rules (Entities):

Entities are enterprise business rules which can be any methods, data structures etc. They do not know anything about the outside layers and are for general usage by anyone in the enterprise. Entities are the ones which are least likely to change in the entire project when there is changes in the external layers. Since entities does not contain any dependency on the outer layers. The reason we call it the enterprise business rule is because either single or multiple applications of the same enterprise use this code for some general purpose usage. And this code is totally independent of who uses it. It knows only about itself.

Consider you are working on a cross platform language to develop an android application. One day within your enterprise there came a requirement to develop an iOS version of the current app. Since the language is cross platform one can still use the Enterprise business rules defined here since it is independent of any other external libraries which are not cross platform supportive hence both the iOS and the Android applications of the same enterprise shares this business rule code base. Hence it called as enterprise business rules.

2. Application Business Rules (Use Cases):

Use cases encapsulates and implements all the use cases of the system. i.e flow of data to and from the entities takes places here. This layer directs the entities to get the job done and the entities return the result back to the use case if any. The reason we call it as application business rule is because each use cases are specific for the different applications present in the enterprise.

Now Application business rules are specific to the applications. Mostly the way the use cases created are going to be different for different applications if there are more than one application in the project. Since each of those application are different they will have their own specific use cases different from each other. Therefore the application business rules are specific to the applications.

3. Interface Adapters (MVC, Gateways):

The main work of the interface layer is to transform data. i.e it converts data from one form to another that is suitable for the database, the web or something that is waiting for this data to be presented to them in a different form. This layer may consists of the controllers and presenters if any. The use case pass the result to this layer and this layers converts it into the format suitable for the application to display to the users.

But the notable thing is depending on the project this layer can also be merged with the higher framework layer. For example when implementing in Android we merge this and the framework layer into a single layer although the modules are separated. But we will come to that later.

In the above diagram we can see controllers and presenters in this same layer. How do they communicate with each other? This is where the magic lies. When we look at the diagram both are directed inward towards the use case. In the diagram we can see the controller is talking to the presenter via the use case. Well the controller can have direct access to use case since use case is an inner layer. But how will use case communicate with the presenter. It cannot directly communicate with the presenter since it will fail to conform with the dependency rule. Hence we will make use of interface (Use Case Output Port) which the use case will call and this interface will be implemented by the presenter to execute the result. Therefore the use case is not directly communicating with the presenter. Hence we do not violate the dependency rule.

Now let us look at how this communication without violating the dependency rule can be achieved

Assume that the use case which is the inner layer wants to communicate with the data layer which is an outer layer to get some data. But we know the use case cannot directly access any of the elements of the outer layer. Hence what we can do is create an interface which will interact with the outer layer. We are going to follow the “D” in the SOLID principle i.e Dependency inversion.

Basically we do it in 2 steps:

1. Inversion of control

But before doing dependency inversion we need to do inversion of control i.e instead of instantiating that class which lies in the outer layer in the use case, we will pass the instance of that class which lies in the outer layer to this Use Case through constructor (To avoid violation of the dependency rule). You can take a look the code below to get a clear idea. But we need to decide how to achieve this. For this we need to implement dependency injection. So that we can inject the class into this use case. You might have a doubt that aren’t we still accessing the elements of the outer layer and it causes violation to the dependency rule. Yes it does cause violation but we will come to that part on how we are going to solve that.

Before Inversion of Control:

class DataUseCase() {private val data = Data() val items = data.getData()}

After Inversion of Control:

class DataUseCase(private val data: Data) {val items = data.getData()}

2. Dependency Inversion

Now we know we are not allowed to directly access any thing in the outer layer. But by using the passed instance of the class which we discussed in the inversion control, we are at risk of directly accessing the methods of the outer layer hence violating the dependency rule. Therefore we create an interface and communicate with the methods existing in the class through it. Therefore we are completely free from violation of the dependency rule. Below code explain step by step process of achieving dependency inversion.

Achieving Dependency Inversion

Step 1 : Creating Data Interface to access the data elements in the outer layer

interface DataInterface {fun getData(): List<CustomDataItems>}

Step 2 : The Data class (lies in the data layer) now implements the DataInterface methods for the use case to use.

class Data : DataInterface {override fun getData(): List<CustomDataItems>}

Step 3 : Replacing the data class(outer layer) in the constructor with dataInterface(inner domain layer) hence preserving the rule.

class DataUseCase(private val dataInterface: DataInterface) {val items = dataInterface.getData()}

Remember that the dataInterface in the constructor of the Use Case here is injected but Due to the topic constraint I cannot go into the dependency injection details right now because this article is focused on clean architecture alone. But I will explain dependency injection in detail with code in another article.

This same technique is used to move across all the layers.Another important thing about the data which moves across the boundaries is that. These data’s are mostly data structures which hold information which are required the different layers to get the job done. But when we move a data from outer layer to the inner layer it is required that they must not know the exact data which is being sent to them since it will violate the dependency rule. For example there maybe be a requirement that we need a data from database which should be called in an api. But the database will return us a row object which is very specific. We must not send this row object directly to the inner layer because it will make the inner layer to know something about the outer layer and violates the dependency rule. Instead we must make sure the data has been converted to a much suitable general form for the inner layer.

4. Frameworks & Drivers (UI/DB/..)

This layer consists of the UI code, Database code or external libraries which will not harm the inner layers. We can change the code present here depending on our requirement and it will not affect the inner layers because nothing in this layer is know by any of the inner layers. All frameworks, device specific implementations can be found here. Sometimes we merge the presentation and framework modules into the same layer according to our project and technology being used to make things much simpler. When the presentation module and the framework module are in the same layer then The presentation module communicates with the driver or device specific library module in the framework layer via interfaces present in the inner domain layer to get access to the device specific implementation and vice versa.This is because the even though they are in the same layer they are in the opposite direction and the control flows inward. We will look at this in deep in the implementation section of clean architecture in the upcoming article.

Can there be more than 4 layers?

Yes of course. There can be layers as per the needs of your project. But it must follow the dependency rule. Basically, the clean architecture says that we have outer and inner layers, and that the inner layers shouldn’t know about the outer ones. This means that an outer class can have an explicit dependency from an inner class, but not the other way round. Projects can even merge some layers into a single layer to satisfy certain requirements to conform to the rules. Remember the golden rule of this architecture is the dependency rule.

In the next article we are going to see how we are going to implement the clean architecture in Android.

Written by

Software Engineer at FRND

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store