As we know, the maintenance operation is always more expensive than building the actual software. Whatever we do, the only thing, that never changes, is maintenance cost, isn’t it?
One of the most important things that will affect the cost is to choose software architecture. Based on past my experiences, as a series of two articles, I will try to mention some topics like “what we should consider in order to design a good architecture” and “what clean architecture is“.
― Robert C. Martin describes a good architecture in the Clean Architecture book as follows;
Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity.
These are our main goals when designing an architecture, right? I think the importance of clean architecture begins with these goals. In my opinion, there are two basic concepts, that we should pay attention, in order to design a good architecture. These are “Coupling” and “Separation of Concerns“.
Let’s briefly recall the definition of coupling. Coupling is a measure of the degree of dependence between modules. Modules are either loosely or tightly coupled to each other.
If we want to develop an application with good architecture, we should develop our application/modules as loosely coupled.
In a nutshell, modules should not be “tightly” coupled with each other. They must be easily “extendable” and “replaceable”.
Separation of Concerns (SoC)
Another important concept is the separation of concerns(SoC). The main goal here is to prevent handling different concerns, in one place. Actually this topic is highly related to the “Single Responsibility” principle.
For example, we might be handling data access operations or UI representation operations in the business logic. After a while, god classes that contain thousands of lines codes, and spaghetti codes come up.
I guess spaghetti is a good thing when we are able to eat it.
Such operations violate SoC. Remember, good architecture should be easily “maintainable”, “less coupled”, and easily “extendable”.
SoC is an important concept especially to be able to design a layered architecture. We should make sure that our modules are loosely coupled and as cohesive as possible, that is, the responsibility relation within a module should be as high and relevant as possible. They shouldn’t deal with different concerns.
I guess the layered architecture has a great place in the life of many software developers. At least for me, it really is.
One of the main principles behind the layered architecture is SoC. The goal is to prevent the database, domain or UI codes from getting mixed up and to separate their responsibilities.
Layered architecture usually comes in a form like the one above. The dependency flow here is from the presentation to the data access layer. In fact, although we try to provide SoC and loosely coupling, there is a hierarchy/dependency between layers.
One of the main principles behind clean architecture is Dependency Inversion principle. With this principle, it is possible to make components in our architecture more decoupled from each other and abstracted from certain technologies.
Let’s look at the picture from this point of view.
The concept that Uncle Bob calls “Clean Architecture” or the design that Alistair Cockburn calls “Hexagonal Architecture” is as above. It is a domain-centric design on which dependency inversion is applied.
Instead of a hierarchical flow from top to bottom, it places the application domain, which contains business logic and domain models, in the middle of the architecture as the core. It also reverses the dependencies of all modules. Dependencies always point inwards (core layer). We can think of it as a plug-in-based structure. Plug it, out.
This approach gives us the ability to easily extend, test and replace any part as we wish.
Why Clean Architecture?
- Hard to change: Usually, business needs never end. We always need to add new features to our application. If we don’t design a good architecture, it will be difficult for us to implement new features and changes and it could make the project more complex. In addition, our project becomes more fragile and harder to maintain.
- It is hard to test: In a poorly designed architecture, testability is also difficult. Either business logic is duplicated everywhere or intertwined with the UI, or the components are tightly coupled to each other. When the fragility is too high, extensibility becomes more difficult, and also it is difficult to see and test what is the impact of the changes on the project.
- Independent of databases, frameworks, and external libraries: Another important topic is to keep the core application domain layer as isolated from databases, frameworks or external libraries as possible. This approach will give us the ability to move forward with a different method and a different framework at any time.
Can you imagine we are trying to replace the scissors with a knife?
What is a well designed architecture?
We usually expect a well designed architecture to cover the following concerns.
- Testability: Each code/component parts, that we develop, must be easily tested individually.
- Maintainability: First of all, the sustainability of an application is more important. We should be able to perform maintenance operations easily.
- Extendability: We should be able to easily extend existing modules/components without breaking/modifying them.
- Reusability: The modules we have developed must be reusable.
- Readability: When a new developer joins the team, it is as important as the other elements that he/she can easily adapt to the project.
If we look at the above picture again, we can see it how it is more easy to replace the scissors with the knife without affecting the other tools.
Clean Architecture Concept
Well, now let’s look at the details of the clean architecture concept.
First, the Hexagonal architecture that we have seen below is an applied instance of clean architecture concept.
The “Application Domain” is the innermost core layer. The heart of the architecture. It doesn’t have any dependencies. It is located in the middle of the architecture and is isolated from other frameworks, databases, UI, etc. It mainly contains domain entities, use-cases and external interfaces.
There are also “input” and “output” ports around it. Implementations which are around it are called “adapter”. These adaptors implement ports.
- Business objects of an application.
- Should not be affected by anything.
- Each use case represents business actions.
- They implement and encapsulate business rules.
- Each use-case follows the single responsibility principle.
- If a data needs to be accessed in a use-case, “input” ports should be used for it. Use-cases do not care where the data comes from.
- In order to persist or send a data to anywhere, “output” ports should be used for it.
For example, if we look at the above project structure, there is a “MovieService” class under the “Services” folder. Well, what responsibilities are this service performing? Do you think is it clear enough?
Now let’s look at these services which are applying the single responsible use-case approach.
Under the “MovieUseCases” folder, there are “CreateMovieHandler” and “GetBestMoviesForKidsHandler” use-cases. Now their responsibilities are much more clear, right? God classes vs single responsible classes.
- These are implementations of interfaces that we define in the core layer.
- It can be a part that will be responsible for persisting or retrieving a data. We can think it like a translator between “Domain” and “Infrastructure“.
Let’s take a look at the above picture again. We can see ports are interfaces that are defined in the domain layer in order to invert dependency.
The ones around are implementations of ports, called adapters. It can be a SQL Server in the “Infrastructure” part or a NoSQL implementation. It can be a Web UI in the “Presentation” part or a REST API.
As we can see the core domain layer locates the middle of the architecture completely as isolated and decoupled from the other modules and technologies. The whole dependency flow points to inward.
At this point, I have tried to explain the “why clean architecture” topic and I tried to mention the general concept of it. I know this article a bit abstract, that the reason is in the next part of this article, I will mention about the implementation of clean architecture in .NET Core.