In this article, I will try to show you the difference that I got while I was implementing this project.
I recently started my studies on Clean Architecture and then decided to put this knowledge into action. Most of my experience is with Java, but for some time I have been working with different programming languages, including Golang, and that’s where I had my first contact with this architecture, which sparked my interest in it.
But before that, I have to share with you a thought I had while working on other projects: “I have always worked with the MVC structure”, and that became even more clear to me when I started this project.
When you start in a project or in a company that does not have a defined architecture, you will usually encounter a structure like the example above, but let us explain a little about these packages:
- Controller layer: processes and validates the client’s request and sends it to the service layer.
- Service layer: executes all business rules, calls other services or not and could call the repository for CRUD’s operation in the database.
- Repository layer: is the bridge between the code and the database.
Most of the projects that I worked on were built on a microservice architecture that came from a monolithic platform, and there was always some maintenance on them, so it was a little bit harder even though we were trying to apply the SOLID principles. This approach can bring us a lot of advantages, mainly when we work with a small team or a small product.
But now try to imagine that the product or the company has grown and now has several teams creating different features, and it has become harder to do some maintenance because of the problems described below:
- Shared resources: even though they use interfaces between the layers, they could have some problems sharing the same object, and when another developer changes it, you will probably get some merge conflicts or even integration bugs.
- The difficulty of implementing the Open Closed Principle: within the layer of the service, it is common to have a method that is called by the layers of the controller. It is an orchestrator method. This method is the entry point for all the business rules in the file of a particular service and orchestrates all the other methods required to achieve the goal. The problem occurs when we need to change a specific behavior within this method.
For example, you need to add a new method that will only be called for a specific type of customer. This service file had multiple methods that contained many different business rules that did not belong together. This means that this service has become “God Class”. A “God Class” is an object that controls way too many other objects in the system and has outgrown any logic to become “The Class that Does Everything”. To add new rules, we then have to change a lot of other files or methods that have nothing to do with our change. This does not comply with the open-close principle, which states that “software entities (classes, modules, functions, etc.) should be open to extension but closed to change.”
- Frameworks and libraries have tightly coupled business logic: Inside this service, there are a lot of external dependencies that we need to change or sometimes update with the minimum impact possible. In most cases, it is common to see these external dependencies being imported directly into our service, thus creating a coupling with the business logic and even bringing a headache.
Due to these problems, Clean Architecture brings some tools to help us. However, it is essential to say that this approach was recommended for a big project because these patterns will probably appear when you are working with several services.
So before the start of the project, it’s important to mention something:
- The Clean Architecture is a system guideline proposed by Robert C. Martin (a.k.a. Uncle Bob) that deviates from many other architectural guidelines. You can see more in his blog.
- The summary of these guidelines is:
- Independent of Frameworks: The architecture does not depend on the existence of some library. In the real world, we should not mix our system’s business rules with the framework dependencies.
- Testable: The business rules can be tested without the UI, database, web server, or any other external element. If we can reach the first point, our business logic will be isolated from all external dependencies, just accessing them through an interface created in the same layer and not knowing who implements it, thus making possible the testability of just our business rules.
- Independent of the UI: The UI can change easily without changing the rest of the system. The UI should consume the service through an interface, then our service could change its behavior (RESPECTING the interface’s contract), not impacting the UI layer. Any change in the UI layer should not impact the business layer.
- Independent of the database: You can substitute Mongo, BigTable, CouchDB, or another database for Oracle or SQL Server. As well as the first point, we should consume the data source through an interface and our business logic is isolated from external dependencies, and the database is one of them. Then our business logic should not be bound to the specific database.
- As mentioned before, the system’s business rules should be isolated from anyone’s external dependency. We should work only with the programming language resources.
Through that, we can realize that these guidelines are strongly based on SOLID’s principles, mainly on three topics: Single Responsibility, Interface Segregation, and Open Close. Then, knowing about these topics, we can say that the focus of implementing this approach is to isolate our system’s business logic from any external dependency. To achieve the goal of this architecture, we need to understand the rule dependency, which is the core of Clean Architecture.
The Dependency Rule says that source code dependencies can only point inwards, which means that, as per the image above, from your level, you can only access the levels below you! But let’s explain a little bit more about those layers:
Robert C. Martin just showed us four layers:
- Frameworks and Drivers: Also known as the Infrastructure layer, it is represented by the orange layer in the image above. It is responsible for all configurations of the application, for example, dependency injections, setup of the database, log configuration, etc.
- It is represented by entry point and data provider the green-blue layer, which is also known as the Application layer. Here is the entry point of our application (Rest API or Jobs), database interfaces (a.k.a. repository), and integrations with other services (a.k.a. Network Devices).
- Use Cases: This layer contains specific business rules for our applications. For example, if you need to return a list of customer objects that are in the age range of 18 to 24 years, this rule will be inside of a use case. It’s important to say that for each behavior we must create a specific use case for it. For another example, if we are creating a CRUD project, each action of it will be a specific use case file, thus achieving the single responsibility principle.
- Entities: Also known as the domain, these are the entities that encapsulate the enterprise-wide business rules. In other words, it is the object of the domain that contains attributes and methods that can exist regardless of who uses them. For example, take the last example about customers: within the Customer class, there is an attribute called Addresses that represents a list of addresses for that customer. There are several attributes within the Address class, one of which is the Address state, which has only the values ACTIVE or NOT_ACTIVE. This results in a special rule that is necessary for a specific use case: creating a new method that filters only the activite address of a customer. The first thing we need to analyze is that the filter method cannot belong to the use cases class, but why?
Since this behavior exists independently of the use case, it is best to implement this method in the customer class and not in the use case class. Imagine that after creating these filters, you need to check if there is more than one active address. If there is one, we use the first one as the main address, but if there is none, we return an error message that the customer has no active address. These types of rules are business rules that are tailored to the use case. It’s a bit hard to understand, but with time it becomes easy.
In some cases, we can see the Use Case and Entities layers inside a folder that could be named Domain or Core.
All right, after explaining all these things, I could conclude that clean architecture becomes easy to work with when we understand the rule dependency flow. We need to respect it to reach its full potential. In the next part of this article, I will show you my approach to this topic in Java.