📜 ⬆️ ⬇️

Matthias Noback on Ideal Architecture - Layers, Ports, and Adapters (Part 2 - Layers)

In 2017, Matthias Noback (by A year with Symfony ) published a cycle of three articles in which he described his views on the ideal corporate application architecture that had been formed over many years of practice. The first part is introductory and does not represent much interest (you can read the original ) . The second translation is this article. Translation of the third will be available soon.


For me, one of the mandatory requirements for a “clean” architecture is the intelligent separation of application code into layers . The layer itself does nothing, the whole point is in how it is used and what restrictions are imposed on the components that belong to it. Let's philosophize a little before considering the final practical techniques.


Why layers are needed



Personally, I have never met code-lasagna, but I have seen a lot of pods. The truth happened that I wrote code in which I made serious architectural errors and incorrectly divided the application into layers, which brought some problems. In this article, I describe, in my opinion, the best set of layers, most of which are described in the book Vaughn Vernon "Implementing Domain-Driven Design" (link below). Please note that the layers do not have a tight binding to DDD, although they make it possible to create pure domain models, if desired by the developer.


Directory and Namespaces Structure


Inside src/ I have directories for each context ( Bounded Context ), for example, which I highlight in my application. Each of them also serves as the root namespace for the classes belonging to it.


Within each context, I create directories for each of the layers:



 src/ {BoundedContext}/ Domain/ Model/ Application/ Infrastructure/ 

I will briefly describe each of them.


Layer 1 - Domain (model / core)


Domain layer contains classes for known DDD types / patterns:



Inside the Domain folder, I create a Model subfolder, inside it — directories for each of the aggregates (Aggregate root). The folder with the unit contains all the related items (value objects, domain events, repository interfaces, etc.)


Please note that the code from the domain layer does not come into contact with the real world. And if it were not for the tests, then no one could refer to his objects directly (this is done through the upper layers). Tests for the domain model should be extremely modular. Since the domain layer does not interact directly with the file system, network, database, etc., we get stable, independent, clean and fast tests.


Layer 2 - (domain wrapper): Application layer


The application layer contains classes of commands and their handlers . A command is an indication of something that needs to be executed. This is a normal DTO (Data Transfer Object) containing only primitive values. There should always be a command handler that knows how to execute a particular command. Usually the command handler (also called the application service ) is responsible for all the necessary interactions — it uses the data from the command to create (or retrieve from the base) an aggregate, performs some operations on it, can save the aggregate after that.


The code for this layer can also be covered by the unit tests, but at this stage you can start writing and accepting. Here is a good article on this topic Modeling by Example from Konstantin Kudryashov.


Layer 3 (application wrapper) - Infrastructure


The code written in the previous layer is also not called by anyone except tests. And only after adding the infrastructure layer, the application becomes really usable.


The infrastructure layer contains the code necessary for the application to interact with the real world — users and external services. For example, a layer may contain code for:



The code of this layer should be covered with integration tests (in the terminology of Freeman and Pryce ). Here you are testing everything for real - a real base, a real vendor code, real external services. This makes it possible to verify the performance of those things that are not under your control but are used in your application.


Libraries and Libraries


All frameworks and libraries interacting with the outside world (file system, network or base) must be called in the infrastructure layer. Of course, the domain code and application layer often needs the functionality of the ORM, HTTP client, etc. But he should use it only through more abstract dependencies. As required by the rule of dependencies .


Dependency rule


The dependency rule (formulated by Robert C. Martin in The Clean Architecture ) states that at each application layer you should depend only on the code of the current or deeper layer. This means that the domain code depends only on itself, the application layer code depends on its code or domain, and the code of the infrastructure layer may depend on everything. Following this rule, it is impossible to make a dependence on the code from the infrastructural domain layer.


But blindly follow any rule, not understanding what its true meaning is - this is a rather stupid idea. So why should you use the dependency rule? By following this rule, you ensure that the clean code of the layers of the application and domain layers will not be tied to a “dirty”, unstable and unpredictable infrastructure code. Also, by applying the dependency rule, you can replace anything in the infrastructure layer without touching or changing the code of the more spongy layers, which gives us rich opportunities for rotation and portability of components.


This way of reducing the connectivity of modules is known for a long time as Dependency Inversion Principle - the letter "D" in SOLID formulated by Robert Martin: "The code should depend on abstractions, not on implementations." The practical implementation in most oop languages ​​is to select a public interface for all things that you can depend on (the interface will be an abstraction) and create a class that implements this interface. This class will contain details that do not matter for the interface, hence this class will be the implementation, which is referred to in the inversion principle.


Architecture: delaying technology solutions


Using the proposed set of layers along with the dependency rule, you can get a lot of buns during development:



All this looks extremely tempting: I like the possibility of a problem-free replacement of application components + I like to make important architectural decisions not before starting the project (based on my past experience and guesses), but when real cases of using different parts of the application begin to clear up, and I have the ability to choose the appropriate solutions based on existing needs.


Conclusion


As mentioned earlier, this version of the application bundle gets along well with any framework, as its place is clearly defined in the infrastructure layer.


Some believe that in my version of "too many layers." I do not understand how 3 layers can be considered too large, but if this confuses you, you can remove the applied one. You will lose the ability to write acceptance tests (they will become something like system tests - slower and more fragile) and you will not be able to test the same functionality called for example from the web interface and the console command without duplicating the code. In any case, you will greatly improve the architecture of your project due to the separation of business logic and infrastructure.


It remains to consider the infrastructure layer in more detail. So we smoothly move on to the topic of hexagonal architecture (ports and adapters). But all this, in the next part.


Further reading



You can also familiarize yourself with Deptrac , a tool that helps you follow the rules for using layers and dependencies.


')

Source: https://habr.com/ru/post/351982/


All Articles