What are the benefits of a software architecture?
Defining a good architecture is the key to create a clean, understandable and reusable code. When you start a project without defining a nice architecture, your software will probably get messy at some point.
When you define an architecture, your code will follow a pattern, so when someone tries to understand what you did, when you have to change something or refactor, it is going to be much easier and much less time consuming.
Another important thing to point out is the possibility to write good tests. Tests are very important to have a long lasting, trustworthy and stable application. They will help you identify bugs before even releasing your code. However, trying to write tests for a poorly written code, a very coupled one for example, is a really hard (and sometimes impossible) task to do. When you write a code under a layered architecture you will have each thing in your place, each responsibility will be separated. In this way, you will be able to test your code in true isolation.
Summarizing, by adopting a clean architecture your code will become:
- Reusable: well written and organized code can be reused (even in other projects).
- Flexible: refactoring and maintaining your code will become easier and will demand little effort. You will not need to change your code everywhere when you want to replace a library for another, for example, because the whole code responsible for interacting directly with that library will be on a single layer.
- Clean: the code becomes clean and simple. In this way, if a new developer just got into your team he will not have troubles trying to understand the code. And also, years from now, when you do not remember what you did on that code, you will be able to catch it easily.
- Testable: as said before, with a clean architecture, testing your code will be an easier task because you will be able to provide the isolation needed.
- Less dependent of the technologies it were built with: replacing a database by another, changing the framework, choosing another template engine, replacing libraries and so on will not require you to rewrite your entire code. Those changes should be done with little code adjustments, allowing you to perform those changes easier and faster.
Some architectures such as The Clean Architecture, the Onion Architecture and the Hexagonal Architecture were meant to provide a clean, testable, reusable, flexible and less technology-dependent software by separating responsibilities and reducing coupling. Those architectures talk about organizing the application on layers, separating the core of the application from the others.
The image bellow represents an adaptation of the models The Clean Architecture and The Onion Architecture. It shows the organization of the software on layers and the responsibility separation.
On the image above, the circles represents the different areas of the software. Those areas interact between them following a dependency rule. This rule, defines that the dependency between the code only occurs from the most internal to the most external layer. No layer can have any knowledge about implementation inside an external layer. For example, a class defined inside the Application Services layer will never be called by a class inside the Domain Model layer.
At the core of the model, you can see the Domain Model. This layer contains only the application models or entities. The entities represent the business objects of the application. They do not access the other layers, they only interact between each other, establishing relationships with the other entities and declaring their own attributes. An example of an entity is shown bellow:
Right after the Domain Model layer comes the Domain Service. Inside this layer you can find the services that use the Domain Model elements like factories, repositories and interfaces.
Interfaces: they are very useful to reduce coupling between classes. They define a class and their methods without giving the details of the implementation. In this way, it is possible to access a method of a repository, for example, without knowing the concrete implementation of the class by only referencing its interface.
Bellow you can see the ICityRepository, an interface defining the functionalities of the CityRepository.
Because it is an interface, no implementation detail is given. Therefore, this class do not provide any information about where the data comes from or how they are generated.
Only the interface of the repository will be accessed by the Use Cases, not coupling the class to a concrete implementation. In this way, if the implementation of the repository changes you will not need to change the code in all places that use this repository because they will not know about the implementation.
Repositories: they are responsible for retrieving and saving dada to the database. Using them, the use case classes do not need to know the database. If you ever need to change your database solution you will only need to modify the repository and no other change is going to need to be done in the Use Case classes that use the methods to access the database, since the parameters and return data type stays the same.
This layer contains the classes that define the business rules specifics to the application. They encapsulate and implement the Use Cases of the system. This layer controls the data flow in and out of the domain layer and provides information to the application. This layer is not going to be affected due to changes of database, UI, template engines and so on. The Use Cases will only change, when the requirements of the business rules of the system change.
Together with the domain layers, the Use Case layer build the core of the application. Those layers are responsible for the business rules and all the other layers of the application depend on them. According to The Clean Architecture, they are the application and all the rest is only an extension or client that interacts with the real application.
Right after the Domain and Use Case layers is the Application Services. This layer is responsible for the implementation of the application. In a traditional MVC system, this would be the controller. It will be responsible for resolving the routes, answering and routing the requests to the specific controller’s action they need to access. Other application services are also executed on this layer such as authentication, translation and the return of the data to the view. The Domain and Use Cases should not depend on this layer so if, for example, changing the framework becomes necessary this could be done with minimum effort.
The Infrastructure layer defines the origin of the data in an application. It makes possible to save and retrieve data from a data source that could be either a relational database, an API or any other source.
The Infrastructure layer depends on the Domain once it is the Domain that provides a contract describing how the infrastructure should behave. This contract is made by using the interfaces. The Application Service layer also depends on the Use Cases, since the use cases will communicate with the other services of the domain through dependency injection.
External libraries are awesome. They can speed up the development process by reusing existing code and allowing you to focus on the important features of your application.
However, imagine what happens if you start using a lot of libraries without architecting its usage. You simply start calling those libraries inside your classes all around your code. Now Imagine that those libraries you used get deprecated or your needs changed and now you have to replace those libraries by other ones, according to those new requirements. How do you change those libraries? You will have to alter all the classes that use them! And the chances to break something, will be high!
That’s when the adapters come in!
Adapters: when you need to use external libraries, the best way to avoid that coupling trap is using adapters. They will provide you an interface to communicate with those libraries without letting you dependent on them.
To do so, firstly, you will need to create an interface like the one bellow:
Now, in your use case, instead of calling the external library itself, you will be able to call this interface and not to worry about how the implementation will be done.
Last but not least, you will have to create the adapter. It will encapsulate the external library and will be the one who knows how the library should work.
Now, if you need to change the library, you will only need to alter the adapter and, making sure it still receives and returns what it was used to receive and return before, you will be good to go.
Besides, if your application uses automated tests, it will not be necessary to worry about testing the external libraries, your tests will only be responsible to know if the libraries are being used accordingly, as they should be.
This layer will contain all you tests. Inside it, you will find the implementation of your unit tests, feature tests, integration tests and all kinds of tests you may have. No other service or layer should depend on it, however, it may depend on all the other inner layers.