Ports and Adapters is an architecture pattern that aims to decouple your application from the details. By details here, I mean things like:
- Your web framework of choice
- Your database of choice
- Your favorite (or least favorite) ORM
- A third-party API you use to support application use case(s)
- The application layer protocol a client and server will use to communicate
My experience confirms this claim. On a recent project, our team decided to start with a Ports and Adapters architecture, which paid off as our team grew. Our team was building a handful of services that needed to integrate. Ports and Adapters let us push off some integrations and decouple our domain models from our database schema, which afforded our team flexibility and insulated our application from such details.
But there was a cost. Our architecture choice meant that folks joining our project faced a learning curve. Most folks were unfamiliar with the pattern; or perhaps had heard of it, but did not have hands-on experience yet.
My own path to understanding Ports and Adapters was meandering. I encountered many diagrams on the Internet visualizing the pattern using a hexagon, but it was hard to come by a concrete example to help me form my own mental model.
If you have had a similar experience and given up, or are currently trying to make sense of Ports and Adapters, I offer this.
A Color Coded Guide
To understand Ports and Adapters, I think it is helpful to see it beside a more common architecture pattern for web applications, like Layered Architecture.
Most web application frameworks I have encountered, along with their associated tutorials, promote this pattern. It may look familiar.
This is what your typical Rails app might look like. A controller interacts with your business logic, and business logic interacts with the database. This is a fine pattern. It is simple and it can get you up and running really fast!
But the layered architecture may not always be the most suitable. For example, on our project, a layered architecture might have been easier for newcomers to pick up, but we would have lost nimbleness.
Ports and Adapters
Ports and Adapters is like a layered architecture, but it inserts ports to invert the direction of dependencies. It inserts ports between your controller and your application, as well as between your application and your database adapter or ORM.
A port is a metaphor for an Operating System port. In this example, a port is simply an interface. However, you could also substitute a duck type, no problem.
The ports in the diagram are the red and yellow boxes. The difference in color here is intentional because there are two kinds of ports: an incoming port, and a outgoing port.
Incoming ports will be the interface(s) that your application implements. Outgoing ports are the interfaces that your application depends on. "Incoming" and "outgoing" are the terms that our team adopted. "Driving port" and "driven port" is the terminology you may find in other literature.
That leaves us with adapters. Just like ports, there are two kinds of adapters: incoming, which are represented in blue; and outgoing, which are represented in purple. The distinction here is incoming adapters depend on the incoming port, and outgoing adapters implement the outgoing port.
Mix and Match
Ports and Adapters isn't just for web frameworks. The incoming adapter and the outgoing adapter are not limited to being fulfilled by a controller and a database adapter. They can be fulfilled by any type of adapter.
For example, a command line interface could fulfill your incoming adapter, and an HTTP client could fulfill your outgoing port. My example depicts only one adapter and port on the incoming and outgoing sides, but you can have as many as you need!
A Color Coded Example Application
Let's look at a concrete example inspired by the SmallerWebHexagon, which is referenced in Alistair Cockburn's blog post on Ports and Adapters.
Let's say we have a web application that will accept a number and apply a rate to it. I've color-coded the components according to the role that each plays in the Ports and Adapters diagram.
The components break down as follows:
RatingUseCaseis an incoming port. It is the interface that the
RatingApplicationwill implement, and the
KtorHttpAdapterwill depend on.
RatingApplicationcorresponds to the "Application Use Case" in the diagrams. This where our critical business logic lives. This is arguably the most important component, and it is kept free of details like which web framework or ORM we are using.
RatingProviderin yellow is our outgoing port. This is the abstraction that the application depends on to fetch the correct rate, and it is the interface that the outgoing adapter will implement.
InCodeRateris the outgoing adapter. It implements the
RatingProviderinterface and fetches the rate for the application. Imagine a future where we need to fetch this rate from a file or a database. We can add this behavior without modifying our core application!
KtorHttpAdapteris the incoming adapter. It depends on the incoming port, and it will accept it through its constructor. This is the adapter that will drive our application.
Lastly, there is the
mainfunction that configures the
RatingApplicationand starts the server.
And finally, if we shift our boxes a bit and add a hexagon around the ports and application, we start to see a familiar diagram.
This resembles the diagrams in the original Ports and Adapters blog post and the hundreds of images presented if you image search "Hexagonal Architecture." Throughout this post, I use "Ports and Adapters" instead of "Hexagonal Architecture," but they are the same. They are both fun names, but the essence of the pattern is to invert your dependencies by depending on abstractions.
I hope seeing Ports and Adapters beside a Layered Architecture, along with color-coded example, has unlocked this architecture pattern for you. Reading Getting Your Hands Dirty on Clean Architecture helped me get a solid grasp on this pattern. The concrete examples were exactly what I needed, and I recommend it to anyone looking for a deeper dive on the subject.
A full runnable version of the source code can be found here.