Many developers are aware of the clean code principles but there is a further way to level up, clean architecture. Not many are aware of the concept of clean architecture, although it is definitely worth exploring. My own experience proves that implementing clean architecture principles in your project can solve a number of issues.
In this article I decided to take a closer look at what clean architecture means and why it can be important.
Problem statement
Probably every developer and project manager has met this situation at least once in a career: a project shows rapid growth in the beginning then slows down or even becomes completely stuck. New developers coming to this project want to re-develop everything from scratch, to add new features in a timely manner, as it is easier than understanding the current solution.
Do any of the issues below sound familiar?
- It is painfully hard and time consuming to add new features.
- Multiple bugs are discovered only in production.
- A new feature or even a bug fix produces new bugs that are not directly connected with the initial change.
- It is impossible to get rid of the outdated legacy library, framework or database without significant development effort.
- Time-to-market delivery is very slow and becomes even slower in time.
- The backlog of bugs grows faster than the team can handle it.
If the answer is ‘yes’ to any of these, it is highly likely (but not definite) that the solution or software architecture has not been designed or followed properly.
The above-described problems are well known and have been for decades. Applying clean architecture design principles can help to avoid these issues.
What is clean architecture?
Clean architecture is a set of design principles that divides software components/modules into onion ring layers as in the image below. The main idea is that code dependencies are supposed to only go from the outer layers to the inner ones. There can be more than three levels of layers in some cases.
Layer 1: domain layer, all the business entities and business rules should be here.
Layer 2: interface adapters layer, here are the controllers, interfaces, APIs, connectors.
Layer 3: infrastructure and presentation layer, all the low-level technical implementations should live here: frameworks, libraries, databases, user interfaces, external interfaces and APIs.
>> Learn more about API-first design and check our data enginnering services.
Main benefits of clean software architecture
Independence from frameworks and libraries
Usually we build solutions for customers to be used for years but new frameworks and libraries and their new versions appear daily! There should be the option to switch libraries or frameworks easily if necessary without drastic development efforts.
Independence of the user interface
While nowadays UI is most often completely technically separated from the solution (as in a JS web app or mobile application), there can be a logical dependency or business rule dependency that is implemented within, for example, a mobile application. Following clean architecture principles will eliminate these cases.
Independence from databases and storage
Any solution in the long term should be independent from any specific data storage or database. Of course, it is not possible to design a universal solution but proper design of the database or data storage interface should make it easy to switch from one database/data storage option to another in a seamless way.
Testability
This benefit is the most important for long-term solution survival. Often old projects lose key experts and analysts and when the time comes to refactor or improve the solution, the proper tests can be the only source of sanity for the solution’s business logic. Properly-designed components can also easily be covered with automated tests.
>> If you find this article interesting, check out our other related publications:
- Microservices vs. monolith: choosing the best for your project
- Benefits of microservices. What should you know?
- Best practices for microservices you should adopt
Clean architecture principles
It is possible to divide all the clean architecture principles into two groups, based on how you should treat design in general and how you should manage the components of your project.
Common design principles
- Dependency inversion principle
- Interface segregation principle
- Single responsibility principle
- Open-closed principle
Components organization principles
- Release-reuse equivalence principle
- Common closure principle
- Common reuse principle
- Acyclic dependencies principle
- Stable dependencies principle
- Stable abstractions principle
Dependency inversion principle
I believe this one is the most important. It means that dependencies should always go in one direction only: from high-level components with the business rules and entities through the interfaces to the low-level components like frameworks and databases. In practical terms: no business logic or entities should ever be dependent on technical implementation and its frameworks, databases, interface, or anything else.
Interface segregation principle
This principle requires segregating the interfaces within their minimal possible functionality. If there is one object that uses the functionality of another, it should use the maximum of the provided interface. If the module has methods 1, 2, 3, 4, 5 ,6 and one object uses methods 1, 2 and 3 and another object uses methods 4, 5, 6 then probably it makes sense to have two separate interfaces.
Single responsibility principle
This principle is the most difficult to follow. Each module of the solution should have one and only one reason for change. Let me give you an example: one class has two methods: to calculate taxes and to send calculated taxes with an email. This class violates the single responsibility principle because there can be two reasons for changing: a change in tax calculations or a change in communications method - this means that this class should be split into two classes.
Open-closed principle
In this principle, software components must be open for extensions but closed for modifications. Sounds weird, but let me try to explain. The structure of the code should be designed so that you can change the behavior of the class without modification of existing code but only by adding new code. You can achieve that by creating an additional abstraction level. For example, instead of multiple ‘if else if else’, create separate objects that can handle the same logic. Be careful with this principle, implement new abstract layers only when you actually need them, never when you just feel that you need them.
Release-reuse equivalence principle
This principle is quite simple: every component can be re-used fully and it should be packaged and versioned properly to be re-used by other components. Practically, you cannot release a component or package if only part of it can be reused. As for versioning and releases, there are plenty of tools for that and most developers have this principle in their blood even if they do not know what it is called. :)
Common closure principle
This is a continuation of the more abstract single responsibility principle defined above. It means that you package functionalities into one component only if it really makes sense. One component should have only functionalities that are changed for the same reasons and at the same time. The simplicity of maintenance and changes is a key point of any component, it is always better to change something in one place and test one component than do multiple changes and testing in different places.
Common reuse principle
Design your components so that your users will not be dependent on functionality they do not need. This saves space and memory, optimizes performance, and decreases dependency. Nowadays, not following this principle can make it impossible to publish the application via an application store. For example, it often happens that the developers take some available library and use just one function from it.
The application store then refuses to publish the application because the library requires additional permissions for the app that do not make sense to retrieve from the user as they refer to a functionality not actually used in the app.
Acyclic dependency principle
This principle says that circular dependencies between components are not allowed and should be eliminated. While some programming languages (like Golang) take care of cyclic imports on their own and that mostly solves the issue, it can be a very non-trivial task for other languages to eliminate this kind of dependency.
Stable dependencies principle
All the dependencies should be directed from less stable modules that change often to more stable modules that change less often.
Stable abstractions principle
The stability of any component is proportional to its abstraction level. More abstract entities are usually more stable, while any specific implementation that is changed more often is less stable. See the picture above.
Disclaimer
Clean architecture is a development philosophy and while it helps a lot to develop and maintain long-lasting projects properly, it is not a silver bullet to solve all the technical and design challenges. It is good to know clean architecture principles but it is better to use them wisely otherwise your solution can become overcomplicated with too many unnecessary abstractions and interfaces. The main goal of any architect is to solve the business problem in the most optimized way, not to create an ideal technical solution. :)
Conclusion
Clean architecture principles can be applied to any solution or software, no matter what programming paradigms it uses. Actually, clean architecture can be used by any other architecture that follows clean principles. This means you can combine programming paradigms to create, for example, clean microservices architecture or any other architecture of your choice. Following clean architecture while designing and developing your solution makes it testable and more independent from libraries, frameworks, databases and provides better maintainability in the long term.
I have tried to give a brief overview of the main and the most important elements of clean architecture but this is just the tip of the iceberg. If you are interested in exploring these principles more deeply, check out the original source: the book ‘Clean Architecture’ written by Robert C. Martin (a.k.a. Uncle Bob). You can also discover tons of articles and videos using the keywords ‘clean architecture’.