Layers, Bulbs, Hexogons, Ports and Adapters - All About One
Translation of Mark Seemann's article on popular software development architectures and what they have in common.
One of my readers asked me:
TL; DR If we apply the principle of dependency inversion to a layered architecture, we will ultimately get Ports and Adapters .
In the book, I describe typical pitfalls that arise when working with Layered Architecture . For example, a popular mistake in its construction:
The arrows indicate the direction of the dependencies, i.e. the User Interface depends on the Domain, which in turn depends on Data Access. This is a gross violation of the Dependency Inversion Principle , because Domain is dependent on Data Access, but
The scheme looks almost the same as the previous one, however, it is important to note that the directions of the dependencies have changed, and now Data Access depends on the Domain, and not vice versa. This means that the scheme complies with the Dependency Inversion Principle - details (UI, Data Access) depend on abstractions (Domain Model).
The previous example is quite simple, as it includes only three components. Let's imagine a more realistic project in which the Dependency Inversion Principle would be respected :
Despite the large number of components, all dependencies are directed inward. If you think in "layers", you can select and depict them like this:
These layers really resemble onion layers, it is not surprising that Jeffrey Palermo called this architecture Onion Architecture .
The Dependency Inversion principle is still respected, since dependencies go only in one direction. However, you can notice that the UI (red) and Data Access (blue) components are located in the same layer (I also added several yellow components, which, for example, can symbolize unit tests). It seems that a mistake was made somewhere here, but in fact, everything is true, because all these components of the outer layer are at the borders of the application. Some boundaries (UIs, APIs) look outward, others (databases, file systems) look inward (operating system, database servers).
As you can see from the diagram, components can depend on other components of their layer, but does this mean that UI components can directly access Data Access components?
Despite the fact that the traditional Layered Architecture has experienced the peak of its popularity, this does not mean that all its principles have lost relevance. The idea of allowing UI components to access Data Acess directly is not acceptable. Direct interaction between them can cause data changes to bypass important business logic and disrupt consistency.
You may have noticed that I grouped the orange, yellow, and blue components into separate groups. This is done so that the UI components are not dependent and do not communicate directly with Data Access components and vice versa. Let's separate these groups graphically:
The result is exactly six sections (three empty). Let this be a good liner to the concept of Alistair Cockburn Hexagonal Architecture ( hex = hexagon ):
You might think that I cheated on creating exactly 6 sections, making the diagram hexagonal, but there is nothing to worry about, since the hexagon has nothing to do with the hexagonal Architecture ( however, the source explains the connection between these concepts of a translator ). The name of the pattern does not reflect its essence. Therefore, I like to call it Ports and Adapters .
In the diagram above, we see a too deep hierarchy of dependencies. When the diagram consisted of clearly defined circles, we had 3 onion layers. The hexagonal dependency diagram still has these intermediate (gray) components, but, as I previously tried to explain , a flat dependency hierarchy is better nested. Let's try to make it as flat as possible inside the hexagon:
The components inside the hexagon have only a few (or do not have at all) dependencies on each other, while the components behind the hexagon work as Adapters between the internal components and the boundaries of the application - its Ports .
In my book, I did not come up with a name for the architecture I am describing, but in fact I got Ports and Adapters . In addition to the options described above, other architectures that follow the Dependency Inversion Principle are described there , but the basis of the book “Dependency Injection in .NET” is certainly Ports and Adapters .
I did not use the names Onion Architecture , Ports, and Adapters in my book because I did not know about them at that time. But without realizing it, I described precisely these patterns. Only later, having familiarized myself with them, did I recognize my architecture in them. And it confirms that Ports and Adapters - An example of a real, canonical pattern, because one of the signs of a pattern is that it can manifest itself in different, independent environments and situations, after which it “opens” as a pattern.
One of my readers asked me:
Vernon, in his book Implementing DDD, talks a lot about the architecture of Ports and Adapters, as a more advanced layer of Layered Architecture . I would like to hear your opinion on this matter.If you do not go into details, then in my book I describe this particular architectural pattern, although I never call it that name.
TL; DR If we apply the principle of dependency inversion to a layered architecture, we will ultimately get Ports and Adapters .
Layered Architecture
In the book, I describe typical pitfalls that arise when working with Layered Architecture . For example, a popular mistake in its construction:
The arrows indicate the direction of the dependencies, i.e. the User Interface depends on the Domain, which in turn depends on Data Access. This is a gross violation of the Dependency Inversion Principle , because Domain is dependent on Data Access, but
“Abstractions should not depend on the details. Details must depend on abstractions. ”Dependencies in this scheme should be inverted as follows:
- Agile Principles, Patterns, and Practices in C #
The scheme looks almost the same as the previous one, however, it is important to note that the directions of the dependencies have changed, and now Data Access depends on the Domain, and not vice versa. This means that the scheme complies with the Dependency Inversion Principle - details (UI, Data Access) depend on abstractions (Domain Model).
Onion Architecture
The previous example is quite simple, as it includes only three components. Let's imagine a more realistic project in which the Dependency Inversion Principle would be respected :
Despite the large number of components, all dependencies are directed inward. If you think in "layers", you can select and depict them like this:
These layers really resemble onion layers, it is not surprising that Jeffrey Palermo called this architecture Onion Architecture .
The Dependency Inversion principle is still respected, since dependencies go only in one direction. However, you can notice that the UI (red) and Data Access (blue) components are located in the same layer (I also added several yellow components, which, for example, can symbolize unit tests). It seems that a mistake was made somewhere here, but in fact, everything is true, because all these components of the outer layer are at the borders of the application. Some boundaries (UIs, APIs) look outward, others (databases, file systems) look inward (operating system, database servers).
As you can see from the diagram, components can depend on other components of their layer, but does this mean that UI components can directly access Data Access components?
Hexagonal Architecture
Despite the fact that the traditional Layered Architecture has experienced the peak of its popularity, this does not mean that all its principles have lost relevance. The idea of allowing UI components to access Data Acess directly is not acceptable. Direct interaction between them can cause data changes to bypass important business logic and disrupt consistency.
You may have noticed that I grouped the orange, yellow, and blue components into separate groups. This is done so that the UI components are not dependent and do not communicate directly with Data Access components and vice versa. Let's separate these groups graphically:
The result is exactly six sections (three empty). Let this be a good liner to the concept of Alistair Cockburn Hexagonal Architecture ( hex = hexagon ):
You might think that I cheated on creating exactly 6 sections, making the diagram hexagonal, but there is nothing to worry about, since the hexagon has nothing to do with the hexagonal Architecture ( however, the source explains the connection between these concepts of a translator ). The name of the pattern does not reflect its essence. Therefore, I like to call it Ports and Adapters .
Ports & Adapters
In the diagram above, we see a too deep hierarchy of dependencies. When the diagram consisted of clearly defined circles, we had 3 onion layers. The hexagonal dependency diagram still has these intermediate (gray) components, but, as I previously tried to explain , a flat dependency hierarchy is better nested. Let's try to make it as flat as possible inside the hexagon:
The components inside the hexagon have only a few (or do not have at all) dependencies on each other, while the components behind the hexagon work as Adapters between the internal components and the boundaries of the application - its Ports .
To summarize
In my book, I did not come up with a name for the architecture I am describing, but in fact I got Ports and Adapters . In addition to the options described above, other architectures that follow the Dependency Inversion Principle are described there , but the basis of the book “Dependency Injection in .NET” is certainly Ports and Adapters .
I did not use the names Onion Architecture , Ports, and Adapters in my book because I did not know about them at that time. But without realizing it, I described precisely these patterns. Only later, having familiarized myself with them, did I recognize my architecture in them. And it confirms that Ports and Adapters - An example of a real, canonical pattern, because one of the signs of a pattern is that it can manifest itself in different, independent environments and situations, after which it “opens” as a pattern.