Layers, ports & adapters - Part 3, Ports & Adapters
Matthias Noback
In the previous article we discussed a sensible layer system, consisting of three layers:
- Domain
- Application
- Infrastructure
Infrastructure
The infrastructure layer, containing everything that connects the application’s use cases to “the world outside” (like users, hardware, other applications), can become quite large. As I already remarked, a lot of our software consists of infrastructure code, since that’s the realm of things complicated and prone to break. Infrastructure code connects our precious clean code to:
- The filesystem
- The network
- Users
- The ORM
- The web framework
- Third party web APIs
- …
Ports
The layering system already offers a very useful way of separating concerns. But we can improve the situation by further analyzing the different ways in which the application is connected to the world. Alistair Cockburn calls these connection points the “ports” of an application in his article “Hexagonal architecture”. A port is an abstract thing, it will not have any representation in the code base (except as a namespace/directory, see below). It can be something like:
- UserInterface
- API
- TestRunner
- Persistence
- Notifications
In other words: there is a port for every way in which the use cases of an application can be invoked (through the UserInterface, through an API, through a TestRunner, etc.) as well as for all the ways in which data leaves the application (to be persisted, to notify other systems, etc.). Cockburn calls these primary and secondary ports. I often use the words input and output ports.
What exactly a port is and isn’t is largely a matter of taste. At the one extreme, every use case could be given its own port, producing hundreds of ports for many applications.
— Alistair Cockburn
Adapters
For each of these abstract ports we need some code to make the connection really work. We need code for dealing with HTTP messages to allow users to talk to our application through the web. We need code for talking with a database (possibly speaking SQL while doing so), in order for our data to be stored in a persistent way. The code to make each port actually work is called “adapter code”. We write at least one adapter for every port of our application.
Adapters, which are very concrete and contain low-level code, are by definition decoupled from their ports, which are very abstract, and in essence just concepts. Since adapter code is code related to connecting an application to the world outside, adapter code is infrastructure code and should therefore reside in the infrastructure layer. And this is where ports & adapters and layered architecture play well together.
If you remember the dependency rule from my previous article, you know that code in each layer can only depend on code in the same layer or in deeper layers. Of course the application layer can use code from the infrastructure layer at runtime, since it gets everything injected as constructor arguments. However, the classes themselves will only depend on things more abstract, i.e. interfaces defined in their own layer or a deeper one. This is what applying the dependency inversion principle entails.
When you apply the principle everywhere, you can now write alternative adapters for your application’s ports. You could run an experiment with a Mongo adapter side by side with a MySQL adapter. Also, you can make the tests that exercise application layer code a lot faster by replacing the real adapter with something faster (for example, an adapter that doesn’t make network or filesystem calls, but simply stores things in memory).
Directory structure
Knowing which ports and adapters your application has or should have, I recommend reflecting them in the project’s directory/namespace structure as well:
src/
<BoundedContext>/
Domain/
Model/
Application/
Infrastructure/
<Port>/
<Adapter>/
<Adapter>/
...
<Port>/
<Adapter>/
<Adapter>/
...
...
<BoundedContext>/
...
Testing
Having specialized adapters for running tests is the main reason why Cockburn proposed the ports & adapters architectural style in the first place. Having a ports & adapters/hexagonal architecture increases your application’s testability in general.
At the same time, when we start replacing real dependencies with fake ones, we should not forget to test the real thing. This kind of test is what Freeman and Pryce call an integration test. It thoroughly tests one adapter. This means it tests infrastructure code, limited to one port. While doing so, it actually uses and calls as many “real” things as possible, i.e. calls a real external web API, it creates real files, and it uses a real database (not a faster SQLite replacement one, but the real deal - how would you know the persistence adapter for MySQL works if you use SQLite instead?).
Integrating Bounded Contexts
Now, for the Domain-Driven Design fans: when integrating bounded contexts, I find that it makes sense to designate a port for each context integration point too. You can read a full example usng a REST API in chapter 13, “Integrating Bounded Contexts” of Vaughn Vernon’s book “Implementing Domain-Driven Design”. The summary is: there’s the Identity & Access, which keeps track of active user accounts and assigned roles, and there is a Collaboration context which distinguishes different types of collaborators: authors, creators, moderators, etc. To remain consistent with Identity & Access, the Collaboration context will always directly ask Identity & Access if a user with a certain role exists in that context. To verify this, it makes an HTTP call to the REST API of Identity & Access.
In terms of ports & adapters, the integration relation between these two contexts can be modelled as an “IdentityAndAccess” port in the Collaboration context, together with an adapter for that port which you could call something like “Http”, after the technical protocol used for communication through this port. The directory/namespace structure would become something like this:
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
You could even use a “faux” port adapter if you like. This adapter would not make a network call but secretly reach into Identity & Access’s code base and/or database to get the answers it needs. This could be a pragmatic and stable solution, as long as you’re aware of the dangers of not making a bounded context actually bounded. After all, bounded contexts were meant to prevent a big ball of mud, where the boundaries of a domain model aren’t clear.
Conclusion
This concludes my “Layers, ports and adapters” series. I hope it gives you some useful suggestions for your next project - or try to apply it in (parts) of your current project. I’d be happy to hear about your experiences in the field. If you have anything to share, please do so in the comment section below this post.
Also, I would be stupid not to mention that I offer in-house training on these topics as well, in case you want to experience layered/hexagonal architecture hands-on.