Matthias Noback About Ideal Architecture - Layers, Ports, and Adapters (Part 3 - Ports and Adapters)
- Transfer
Matthias Noback (author of A year with Symfony ) published a three-article series describing his views on the ideal enterprise application architecture that has evolved over many years of practice. The first part is introductory and not of much interest (can be found in the original ). The second translation is here . And since he caused a RAPID hype (as many as TWO people debated with me in the comments), not translating the third would be a crime.
In the previous article, we discussed a reasonable project stratification system consisting of three layers:
- Domain
- Application layer
- Infrastructure
Now, we will consider in detail the infrastructure layer.
Infrastructure
The infrastructure layer containing everything necessary for the application 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. Infrastructure code connects the core of our precious application with:
- File system
- Network
- By users
- Orm
- Framework
- Third party
- ...
Ports
A layered system in itself already shares responsibility fairly well. However, you can go further and consider in detail all the points through which our application communicates with the outside world. Alistair Cockburn calls them “ports” in his Hexagonal architecture article . Port is an abstract concept, it may not have any representation in the code (except for the namespace / directory, as I will show below). It may be called something like:
- UserInterface
- API
- Testunner
- Persistence
- Notifications
In other words, for every place through which your application can receive requests (via the web interface, API, etc.), there is a port, just like for every way that the application can output data "out" (saving to disk or to the database, sending data over the network, sending notifications in push or mail, etc.). I often use the terms input and output ports ( input the and output ).
What to consider or not to consider a port is a matter of taste. In extreme cases, you can select a port for absolutely every user case, making hundreds of ports for different applications
- Alistair Cockburn
Adapters
For each of the abstract ports, you need code that will do the "real" work. We need a code for the direct processing of 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) to save and receive our data. Code written for working 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 real low-level code, which is why they differ from ports (which exist as an abstraction). Since the adapter code is the code that connects the application with the real world, the adapter is part of the infrastructure code, and it is located on the corresponding layer. Here, "ports and adapters" are great combined with 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 code from the infrastructure in runtime , since it has access to everything that has been indexed through the constructor arguments. However, the classes themselves must depend on more abstract things, for example, on interfaces defined on the same layer with them. This is what is meant by observing 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 with them during the development process. You will be able to 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).
Directory structure
Knowing which ports and adapters are or will be in your application, I would recommend reflecting them in the following namespace / directory structure:
src/
/
Domain/
Model/
Application/
Infrastructure/
/
/
/
...
/
/
/
...
...
/
...
Integration of Bounded Contexts
Especially for DDD fans - when integrating 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 Vaughn Vernon's Implementing Domain-Driven Design.
In short: imagine that there is a Identity & Access
person responsible for the identification and access levels. And there is one CollaborationСontext
that defines various types of roles: authors, creators, moderators, etc. In order not to violate the consistency, one CollaborationСontext
should always ask Identity & Access
if a specific user really exists and if he has enough rights for this or that role. To verify this, CollaborationСontext
you need to pull the REST API Identity & Access
over HTTP.
In the terminology of ports and adapters, the interaction between these contexts can be represented as follows: a port IdentityAndAccess
inside CollaborationСontext
with an adapter for this port - for example, HTTP
or any other data transfer technology. The folder / namespace structure can be as follows:
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 sneak right into the Identity & Access code or database to get the information you need. In some cases, this may be a reasonable decision if you clearly understand what risks may arise if the delineation of contexts is not tight enough. In the end, contexts help to avoid the “big ball of dirt” in situations where the boundaries of the domain model are not clear.
Conclusion
This post completes my article series, Layers, Ports, and Adapters. I hope that 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 this in the comments to the post.
Only registered users can participate in the survey. Please come in.
In general, are you interested in translating articles on software architecture: SOLID, DDD, GRASP, CQRS, etc., etc.?
- 94.7% Yes, go on. 90
- 5.2% No, not necessary 5