Matthias Noback (author of 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 (can be found in the original ). Translation of the second - by reference . And since he caused a FURIOUS hype (TWO people had a discussion with me in the comments), it would be a crime not to translate the third.
In the previous article we discussed a reasonable system of project stratification consisting of three layers:
Now, consider in detail the infrastructure layer.
An infrastructure layer containing everything you need to communicate with the outside world (users, third-party services, hardware, etc.) can quickly become very bold. As I said, usually this code is complex and unstable. The infrastructure code connects the core of our precious application with:
The layered system itself is already well shared responsibility. However, you can go further and examine in detail all the points through which our application communicates with the outside world. Alistair Cockburn calls them "ports" in his article Hexagonal architecture . A port is an abstract concept, it may have no idea in the code (except for the namespace / directory, as I will show below). It may be called something like:
In other words, for each place through which your application can receive requests (via the web interface, API, etc.) there is a port, just like for every way the application can output data "outside" (saving to disk or to the database, sending data over the network, sending notifications by pushing or by mail, etc.). I often use the terms input and output ports.
What to count or not count as a port is a matter of taste. As a last resort, you can allocate a port for absolutely every usecase, making hundreds of ports for different applications.
- Alistair Cockburn
For each of the abstract ports, you need code that will do the "real" work. We need code for directly processing HTTP messages, which would allow our application to communicate with the user via the web. We need code to interact with the database (possibly speaking SQL) in order to save and retrieve our data. Code written to work with ports is called an "adapter." We always write at least one adapter for each of the ports of our application.
Adapters are as specific as possible. They contain a real low-level code, which is how they differ from ports (which exist as an abstraction). Because the adapter code is the code that connects the application to the real world, the adapter is part of the infrastructure code, and it is located on the corresponding layer. Here the "ports and adapters" are great combined with a layered architecture.
If you remember the dependency rule from my previous article, then you know that the code of each layer may depend on the code of the same or deeper layers. Of course, the application layer can call the code from the infrastructure in runtime , since it has access to everything that was transferred through the constructor's arguments. However, classes themselves should depend on more abstract things, for example, on interfaces defined on the same layer with them. This is what is meant by the principle of dependency inversion.
If you apply this principle to all ports, then you can easily write alternative adapters for them and "juggle" them during the development process. You can run and experiment with the Mongo adapter side by side with the MySql adapter. You can speed up application-level tests by replacing the real adapter with a faster stub (for example, a fake adapter that does not access the real file system or network, but simply stores the necessary information in memory).
Knowing which ports and adapters are or will be in your application, I would recommend reflecting them in the following namespaces / directories structure:
src/ <BoundedContext>/ Domain/ Model/ Application/ Infrastructure/ <Port>/ <Adapter>/ <Adapter>/ ... <Port>/ <Adapter>/ <Adapter>/ ... ... <BoundedContext>/ ...
Especially for DDD fans - with the integration of Bounded Contexts, I realized that it makes sense to allocate ports for each interaction between contexts. You can read a good example with the REST API in Chapter 13 of the "Integrating Bounded Contexts" book of the "Implementing Domain-Driven Design" by Vaughn Vernon.
In brief: let's imagine that there is Identity & Access
responsible for the identification and access levels of users. And there is Collaborationontext
, defining various types of roles: authors, creators, modders, etc. In order not to violate consistency, Collaborationontext
should always ask Identity & Access
whether a particular user really exists and whether he has enough rights for a particular role. To verify this, Collaborationontext
needs to pull the Identity & Access
REST API over HTTP.
In the terminology of ports and adapters, the interaction between these contexts can be represented as follows: the port of IdentityAndAccess
within the Collaborationontext
with the adapter for this port — for example, HTTP
or any other data transfer technology. The folder / namespace structure may be:
src/ IdentityAndAccess/ Domain/ Application/ Infrastructure/ Api/ Http/ # Serving a restfull HTTP API Collaboration/ Domain/ Application/ Infrastructure/ IdentityAndAccess/ Http/ # HTTP client for I & A's REST API
In principle, you can even make an adapter that will not make network calls, but will secretly climb right into the code or database of Identity & Access to get the necessary information. In some cases, this can be a reasonable solution if you clearly understand what risks may arise if the contexts are not sufficiently rigid. In the end, contexts help to avoid "big coma of dirt" in situations where the boundaries of the domain model are not clear.
This post completes my cycle of articles Layers, Ports and Adapters. I hope the knowledge gained here will be useful to you in the next project, and maybe you can apply (partially) them in the current one. I will be glad to hear about the real practical experience of their use. If you have something to tell, you can do it in the comments to the post.
Source: https://habr.com/ru/post/352700/
All Articles