Hexagonal Architectures — the sequel

Global Technology
McDonald’s Technical Blog
7 min readDec 13, 2023

Last week, we introduced you to the concept of Hexagonal Architecture and explained how to set up a domain model. Now, let’s make that model get to work.

by Adam Rice, Manager, Solutions Architect

Introducing ports and adapters
So far, we have created our domain model. Now, we want to connect some cool stuff to it and make it do actual work. First, it’s important that we have a common understanding of ports and adapters:

  • Port — an interface or contract that specifies the attributes for interacting with the domain (this tells the world what what’s needed to operate with the domain)
  • Adapter — a class/function that is customized for the technology being implemented (this defines the work and adaptations necessary to interact with the outside world)

Ports
Another way to think about a port is an agreement that must be followed to interact with what is inside the box, or the hexagon, in our situation.

Let’s say a website user wants to see a list of the programs for which they have access to upload files. The domain needs to know at least the username of the person requesting the information.

Wait a second, something big just happened.

We have just defined the first port — a very simple port, but a port, nonetheless.

The port (interface) might look something like this:

Username — username of user who wants a list of programs they have access to.

ProgramActiveFlag — true, if the domain should return only active programs. False, if the domain should return both active and inactive programs.

By defining this port, we are telling the outside world — if you can’t give me these two things, then we can’t do business (hence the contract).

In TypeScript code, this can be expressed as an interface as follows:

Different programming languages have different ways of specifying interfaces or abstract classes. It doesn’t really matter what language you use. The idea is the same; different integration points need to agree on the information being shared.

Now that we have that all cleared up let’s get back to our diagram. I will be using the following to specify ports:

Let’s see how that has changed our hexagon:

Applying what we just discussed about this port, related to our domain, we now have:

We are now open for business, right? No…class is not over yet.

There is still one more component to talk about: Adapters.

Adapters
This is where all our hard work starts to pay off. We can start writing code that can use the new domain we just created.

To make this domain easily accessible by almost any website, we added an API layer in between the calling websites and the hexagon functionality, letting us abstract even more, the content and technology used inside versus outside the hexagon.

Here is why we want an API layer

If you remember, we mentioned that our domain layer and port are written in TypeScript. If we do not include an API layer in between the calling websites and hexagon, then the calling application would need to be written in TypeScript or JavaScript and have direct integration points within our application code. Let’s share this thing!

An API layer provides a common language layer over which outside applications and the internal hexagon can communicate. This is the language or the Internet — HTTP.

Okay, where were we? Oh yes, adapters.

Peppering in some AWS

In our new scenario, our hexagon needs to accept a request from AWS’s API Gateway via a Lambda function handler. If you are not familiar with Lambda, the basic idea is that a Lambda function has an entry point called a handler, which accepts requests from many different types of AWS services, API Gateway being one of them.

Because our hexagon port does not know what a handler is and only cares about a username, we call upon our adapter to do the adapting to make it work. This is the adapter’s main job; take input from one format and convert it into another format to do work.

Our adapter needs to strip out all the unneeded information from the API Gateway endpoint, only keeping the username and ProgramActiveFlag. Once complete, our adapter has successfully adapted the incoming input into what is needed for the port/domain.

Here is how I will designate an adapter in our diagram:

Our adapter needs to adapt a request from AWS API Gateway into a form that matches our port. In our case, it needs to adapt the incoming API Gateway Lambda handler and strip out everything except the username and ProgramActiveFlag.

To keep this simple, I am going to pseudocode, like this:

The above class does some very important things. It adapts the muckity muck from the API Gateway into a form that the domain understands, which is a simple contract/interface of username and ProgramActiveFlag.

Ok, let’s have a look at our diagram to see what we have done.

Beautiful!

But we are still not done. Right now, we can successfully take a request from the API Gateway, but it still does not do anything because we haven’t hooked up the other side — the driven side — of the hexagon.

In Hexagonal Architecture, we have the concepts of driving and driven, and they mean exactly what they sound like. There is a driving side that initiates actions and a driven side that accepts those actions and does something with them.

Until now, we have only been working on the driving side of the hexagon. Let’s update the diagram to reflect what I mean.

We have configured our hexagon architecture in a way that accepts the correct requests. Now we need to take that request and query our database for the information.

Wait what? You never said anything about a database.

As you might suspect, the exercise we went through before applies to the driven side in the same way, except the flow of information is reversed. Meaning, we are now sending a username and ProgramActiveFlag to a database query to get the stored information for the user requesting it.

Since we are now well versed at configuring ports and adapters, let’s quickly setup our new port and adapter in code and pseudocode.

Our port on the driven side looks almost identical to the one we created earlier for the driving side.

In the real world, this can happen but as you might guess, not always.

Introducing our driven port
Below is the actual interface that we create in TypeScript, and it looks exactly like the interface we made earlier.

Another commercial break.

I didn’t mention it until now but, we have a Postgresql database that just happens to have our program information in it. We need to connect to this and query for the user-related programs.

Good news. We don’t need to change anything about our port (IProgramManagerDriven). We press on with creating an adapter that knows how to talk to PostgreSQL.

In quick order, we just defined our:

  1. Port — IProgramMangerDriven
  2. Adapter — ProgramAdapterForPostgresql

Let’s look at our diagram now to see the complete picture.

To summarize we:

  1. Accepted a request from the API Gateway from a website.
  2. Stripped out the username and ProgramActiveFlag through the driving side of our hexagon, using our driving” port and adapter.
  3. Our domain did some domain things.
    Our driven side of our hexagon port and adapter queried our Postgresql database and returned a list of programs to the API Gateway, which returned it to the calling website.

Going through this process has:

  1. Created a domain that is encapsulated and not affected by outside technology.
  2. Provided a way to write automated tests of our domain logic.
  3. Setup a repeatable design pattern.
  4. Modularized all the components for easier deployment later.

Another theoretical situation
Let’s say your company has decided not to use PostgreSQL. Now, we have a problem because our adapter was all cozy with PostgreSQL. Don’t panic. We can write a new adapter that interacts with the new database technology in the same way the PostgreSQL one did. Because we have decoupled everything, we can code our new solution adapters do the same thing, just for a different database. Our domain with all our business logic is not impacted.

.

--

--