Out go the layers, in come the boundaries!
I/O
It might aid in the understanding of this architecture to put a real heavy emphasis on I/O to and from our application. Specifically, try to see everything but the most specific business logic as an I/O device.
- Database? I/O device.
- Messaging service? I/O device.
- GUI? I/O device.
- The web? I/O device.
We ditch the layers as we used them before and swap them for puzzle pieces of sorts, where our core code simply communicates with different types of I/O devices.
Once you think of it this way, the concepts below should just fall into place.
Port
There is a really nice definition right in the blog Iâm leaning on to write these posts:
A port is a consumer agnostic entry/exit point to/from the application. â source
Itâs a piece of our core application that dictates how we communicate with any given âI/O deviceâ, without implementing the actual communication. If we use a persistence port as an example, it should stay unchanged no matter if we use MySQL, MongoDB, or whatever else. It just knows we need to persist and what parts of our domain need persistence.
This will most likely take the form of an Interface.
Adapter
Nothing more than a class that adapts to or from a specific port.
So if we use Hibernate as our ORM, its adapter will implement our persistence port and translate our domain to whatever Hibernate needs to do its thing (and vice versa). This, unlike its port, will not stay if we swap ORM. Our domain ends where our adapter starts.
This way, we will decouple our domain from any particular technology we might be using.
This thought process should sound familiar, as it is in practice how you would implement a Repository from DDD.
Far Beyond Driven
You might have noticed that the example of a persistence port/adapter doesnât quite fit all types of infrastructure. There is indeed a difference between this and say, an API port/adapter: One is driven by our application while the other decides or drives what our application does.
The driven port/adapter combo will most likely apply (and present clear benefits) to any project, however small it might be. The driving side however might only be clearly beneficial for large scale modular applications. Letâs have a look:
- source
While Itâs still true that the port will more than likely be an interface, in the driving side its implementation will be a part of our business logic (think of Use Cases), while its adapter will have the port as an injected dependency. In this case the adapter is the one triggering a specific part of our application, unlike in the driven side.
Why all the fuss?
- It makes the whole thing more testable.
- It doesnât expose the inner workings of my application.
- Especially in large scale modular applications, it allows for a clear understanding of what a module can do without allowing for faulty usages by other modules.
Inwards dependency
It follows that we end up with our infrastructure depending on our domain and not the other way around. This is whatâs called inward dependency: My domain doesnât know a single thing about web clients, API calls, persistence, etc. It just knows about its own rules and logic, as well as how it should behave (e.g. Use Cases) and what to expect from whatever infrastructure is present (e.g. Persistence Port).
Now there is no code from external libraries mixed up with our business rules, and we can happily adapt to evolving technologies with no fear of major issues.
Considerations
This enforced isolation between our application and its run time dependencies is possibly the most important lesson Ports & Adapters can teach us. By structuring an âus vs. themâ approach it materialized the concepts of âcore business rulesâ that where indeed present before, but rather vague in their conceptualizations.
It makes our code inherently modular, independently testable and as technology-agnostic as possible.
Sure it only leaves us with only two concentric âlayersâ: Our code and the rest of the world. This is a problem that would be tackled three years later.
Hexagons
You might have noticed there is no mention to âhexagonal architectureâ in the post. It might have been the original name, but it does absolutely nothing to convey its content or structure.
The logic behind the name was the fact that it imposed boundaries and that the sides of the figure would mark the interaction between a port and its adapter. Might as well have been a circle or a dodecahedron.
âPorts & Adaptersâ is a better name, itâs more accurate and focuses your attention to what really matters and what defines this architectural design.