Optivem Journal

Share this post

Hexagonal Architecture - Ports and Adapters

journal.optivem.com

Hexagonal Architecture - Ports and Adapters

How do we derive the Hexagonal Architecture diagram presented by Alistair Cockburn? How do we apply Hexagonal Architecture in designing a sample eCommerce system? What about the layers inside?

Valentina Cupać
Mar 30, 2023
13
1
Share

What is the essence of Hexagonal Architecture? Inversion of Control.

What are Driver Ports & Adapters? What are the Driven Ports & Adapters? What is the Application? What are the Dependencies?

Thanks for reading Optivem Journal! Subscribe for free to receive new posts and support my work.

Inversion of Control is the essence.

The essence behind Hexagonal Architecture is the Inversion of Control. To illustrate the difference:

  • In Traditional Layered Architecture, the Presentation Layer calls the Business Layer, and the Business Layer calls the Database Layer.

  • Translating this to Hexagonal Architecture: the Presentation Layer calls the user-side interface exposed by the Business Layer (and the Business Layer implements the user-side interface); furthermore, the Business Layer also declares (and calls) the data-side interface (which represents abstract access to persistence); lastly, the Database Layer implements the data-side interface (e.g. ORM, SQL). But, more formally (since we will *not* use the word "layers") - on the user-side, the Driver Adapters call the Driver Ports, and on the server-side, the Driven Adapters implement the Driven Ports. The Application exposes both Driver Ports & Driven Ports.

From this, we can see that the essence of Hexagonal Architecture is that it breaks direct dependencies. So, the Presentation Layer does NOT depend on the Business Layer implementation. The Business Layer does NOT depend on the Database Layer implementation.

For a more formal overview, see Alistair Cockburn’s Hexagonal Architecture article.

Driver Ports

Examples of Driver Ports are:

  • Order Service Interface, exposing methods: Submit Order, Cancel Order, View Order Details

  • Product Service Interface, exposing methods: Sync Product Pricing, Get Product Details

We can write Unit Tests targeting Driver Ports to test use case / business logic.

Driver Adapters

Examples of Driver Adapters are:

  • Tests

  • REST API

  • SOAP API

  • GraphQL API

  • gRPC API

  • WebSockets API

  • RabbitMQ Consumer

  • Kafka Consumer

  • Job Scheduler

  • SPA App

  • Desktop App

  • Mobile App

  • MVC App

  • CLI App

  • FTP Server App

The Driver Adapters are dependent on the Driver Ports, more specifically they call/use the Driver Ports. For example:

  • The Test Adapter calls the Order Service Interface methods and asserts expected results

  • The REST API adapter has an Order Controller which is dependent on the Order Service Interface. The Order Controller has an HTTP POST method api/orders which accepts some JSON request and uses that to call the Order Service Interface, method Submit Order, and then returns some HTTP status code with JSON response. Thus, we can see that the REST API Order Controller is just a thin HTTP wrapper that delegates to the Order Service Interface.

  • Similarly, other Driver Adapters (might be HTTP adapters, or UI adapters, or anything else) delegate work to the Driver Ports

We can test the Driver Adapters through Integration Tests by “mocking out“ the Driver Ports.

Application

Previously, we listed examples of Driver Ports:

  • Order Service Interface, exposing methods: Submit Order, Cancel Order, View Order Details

  • Product Service Interface, exposing methods: Sync Product Pricing, Get Product Details

The Application (the Hexagon) externally exposes the Driver Ports, i.e. the Driver Ports are a facade to the Application. The Application internally implements the Driver Ports. Using the example above, the implementations could be:

  • Order Service Implementation implements the Order Service Interface

  • Product Service Implementation implements the Product Service Interface

It should be noted that Hexagonal Architecture does NOT prescribe any layering inside the Application itself (i.e., inside the Hexagon). This was also summarized by Juan Manuel Garrido de Paz:

Ports & Adapters pattern says nothing about the structure of the inside of the hexagon. You can have layers… you can have components by feature… you can have spaghetti code… you can have a Big Ball of Mud… you can apply DDD tactical patterns… you can have a single CRUD… it’s up to you.

This means that, in our example, we do not have any prescription regarding how Order Service Implementation and Product Service Implementation will be done.

Driven Ports

The Application implementation may need to access the external world - but how do we avoid coupling it to infrastructural concerns?

For example, Order Service Implementation is implementing the method Submit Order. To implement that method, we need to perform the following:

  • Check that the Products exist (so we need to retrieve information about products from some storage mechanism)

  • Generate a unique order ID

  • Create the Order, and calculate the order price by taking into account Product pricing, as well as discounts which are dependent on the day of the week

  • Persist the Order in some persistence mechanism

  • Authorize payment for the Order, and update the order status to determine whether the payment was successful or not

  • Contact the shipping mechanism so that shipping can be started

  • Publish an event that the Order was submitted

So, we may have the following Driven Ports, to abstract away I/O and sources of non-determinism. I/O abstraction is an essential aspect of Hexagonal Architecture. Non-determinism abstraction is needed for unit testability.

The following Driven Ports abstract away I/O concerns:

  • Order Repository Interface - this is an abstraction over the persistence mechanism (we don’t care which database is used or whether we use ORM or raw SQL or any other implementation)

  • Product Repository Interface - this is an abstraction over the persistence mechanism (once again, we don’t care about concrete database implementation)

  • Order Notifier Interface - this is an abstraction over some messaging publishing (we don’t care about which message broker we’ll use)

  • Payment Gateway Interface - this is an abstraction over the payment system that we’ll be using for payments

  • Shipping Gateway Interface - this is an abstraction over the shipping system we’ll use

The following Driven Ports abstract away sources of non-determinism:

  • Order ID Generator Interface - this is an abstraction over mechanism for generating random Order ID numbers

  • Time Provider Interface - this is an abstraction over system time

In this way, the Application is decoupled from both I/O and non-determinism. It means that we are able to unit-test Driver Ports, by substituting Driven Ports with Test Doubles.

Driven Adapters

The following are examples of Driven Adapters for the following ports (we list the Driven Ports, followed by their Driven Adapters):

  • Order Repository Interface

    • Fake Repository Implementation

    • SQL DB Order Repository Implementation (ORM)

    • SQL DB Order Repository Implementation (ORM Lite)

    • MongoDB Order Repository Implementation

    • Redis Order Repository Implementation

    • Cassandra Order Repository Implementation

    • Neo4J Order Repository Implementation

    • File Order Repository Implementation

    • Amazon S3 Order Repository Implementation

    • Azure Blob Order Repository Implementation

  • Order Notifier Interface

    • Fake Order Notifier Implementation

    • RabbitMQ Order Notifier Implementation

    • Kafka Order Notifier Implementation

  • Payment Gateway Interface

    • Fake Payment Gateway Implementation

    • PayPal Payment Gateway Implementation

    • Stripe Payment Gateway Implementation

  • Shipping Gateway Interface

    • Fake Shipping Gateway Implementation

    • DHL Shipping Gateway Implementation

    • ABC Shipping Gateway Implementation

  • Order ID Generator Interface

    • Fake Order ID Generator Implementation

    • UUID Order ID Generator Implementation

    • ULID Order ID Generator Implementation

  • Time Provider Interface

    • Fake Time Provider Implementation

    • System Time Provider Implementation

Note: You will see a “Fake“ Adapter in each of the above. A Fake is one type of Test Double. Other Test Doubles may be used instead. The choice of which Test Doubles we’ll use is outside of the scope of Hexagonal Architecture.

We can see above that with Adapters, we can implement I/O integration (e.g. which database will be used to implement the Order Repository Interface, which message broker will be used to implement the Order Notifier Interface, which REST/SOAP/etc. API will be used to implement the Payment Gateway Interface, etc.) Thus, we’re implementing any I/O concerns, e.g. disk access and any network communication.

Furthermore, we can see that with the Adapters we can implement any non-deterministic mechanisms. For example, access to the system time as well as using third-party libraries for random number generation.

Each Driven Port will have at least one adapter - the “Test Double” Adapter. There can be one or more real adapters, e.g. we may have MongoDB Order Repository Implementation for the Order Repository Interface; and for Payment Gateway Interface, perhaps we could have two simultaneous implementations (e.g. Payment Gateway Implementation, Stripe Payment Gateway Implementation).

Sometimes, we can implement a real adapter right away (e.g. the DHL Shipping Gateway Implementation because DHL already has some REST API), but sometimes the real adapter implementation will need to be deferred for months (e.g. the ABC Shipping Gateway Implementation cannot be implemented right now, but we need to wait for months because it’s being developed by some external company ABC).

Regardless of when real adapters are implemented (whether soon or deferred until later), we are NOT blocked because we have fake adapters. The fake adapters can be used for running the Application, and for Unit Testing the Application.

We can write Integration Tests targeting Driven Ports. So we write some Integration Tests for a Driven Port, and then at run time, those tests can be executed for every Driven Adapter for that Driven Port.

Interface vs Implementation Details

From the perspective of Hexagonal Architecture, let’s see what are the Interfaces vs Implementation Details.

The “interfaces” are the following:

  • Driver Ports are interfaces for the use cases

  • Driven Ports are interfaces for persistence, messaging, etc.

The “implementation (details)” are the following:

  • Driver Adapters are the implementation of presentation concerns; they use Driver Ports.

  • Driven Adapters are the implementation of Driven Ports.

  • Application (internals) is the implementation of the Driver Ports. So whether we use Transaction Script, or have Application Services + Domain Model, whether we have a Rich Domain or Anemic Domain, or any other choices for implementation the Application - all these are implementation details.

YouTube Live Streaming

You can view the full live streaming session recording here, as we went step by step in reaching the Hexagonal Architecture Diagram, as well as the examples above for the Ports & Adapters involved in the Pizza eShop case study - watch on YouTube:

For our next session, we plan to do some live coding to demonstrate Hexagonal Architecture practically. Subscribe to my YouTube channel to get updates.


Thanks for reading Optivem Journal! Subscribe for free to receive new posts and support my work.

13
1
Share
1 Comment
Rafael dos Santos Miguel Filho
Writes Chronicles of a Pragmatic Progr…
Apr 7

Wow, excellent post! Direct to the point and very educational. Thanks for sharing!

Expand full comment
Reply
Top
New
Community

No posts

Ready for more?

© 2023 Valentina Cupać, Optivem
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing