DEV Community

Jonathan
Jonathan

Posted on

Hexagonal Architecture or Port & Adapters

Hexagonal Architecture, also known as Ports and Adapters, was introduced by Alistair Cockburn in 2005. Cockburn developed this concept to solve the problem of tight coupling between application logic and external dependencies (like databases, UI frameworks, and APIs). His main goal was to make systems more maintainable, testable, and adaptable by clearly separating core business logic from infrastructure concerns.
Hexagonal Architecture shares similarities with other architectural patterns designed to promote separation of concerns and modularity.

1️⃣ Layered Architecture (N-Tier Architecture)
2️⃣ Clean Architecture (by Robert C. Martin - Uncle Bob)
3️⃣ Onion Architecture (by Jeffrey Palermo)

Why is it Called Hexagonal Architecture?
The term Hexagonal Architecture comes from the hexagon shape used to visually represent the core system and its interactions with the external world.

🔷 Why a Hexagon?

  • The hexagon shape is a metaphor to visually represent the system’s core and its interactions.

  • Unlike a rectangle or circle, a hexagon does not imply a hierarchical structure (like traditional N-tier architecture).

  • It has multiple connection points (ports) where you can easily plug in different adapters, like a UI, database, or external APIs.

  • The number six isn't important, it simply provides enough sides for a clean and organized representation.

Image description

🔌 Ports: The Entry Points to Your Business Logic
In Hexagonal Architecture, ports represent interfaces that define the use cases or functionalities of the system. They specify what the application can do (e.g., add a product to the cart, remove a product, calculate the total), but not how it's done. Ports are the entry points to your core business logic.

public interface CartService {
    void addProduct(Product product);
    void removeProduct(String productId);
    double calculateTotal();
    Cart getCart();
}
Enter fullscreen mode Exit fullscreen mode

In this example, CartService is a primary port that defines the use cases for managing a shopping cart.

🔄 Adapters: Bridging Your Core Logic and the Outside World
Adapters are the implementations that interact with external systems and convert data between the outside world and the core business logic. There are two types of adapters in Hexagonal Architecture:

Primary Adapters: These handle input from external sources, such as user requests, UI, or API calls. They interact with the primary ports (use cases) of the system. For example, a REST controller that receives HTTP requests and delegates the actions to a service (like CartServiceImpl).

Example of a primary adapter (controller)

@RestController
@RequestMapping("/cart")
public class CartController {
    private final CartService cartService;

    @Autowired
    public CartController(CartService cartService) {
        this.cartService = cartService;
    }

    @PostMapping("/add")
    public void addProduct(@RequestBody Product product) {
        cartService.addProduct(product);
    }
}
Enter fullscreen mode Exit fullscreen mode

CartController acts as a primary adapter because it handles HTTP requests (external input) and delegates them to the CartService (the primary port).

Secondary Adapters: These handle output to external systems, such as databases, message queues, or APIs. They implement the secondary ports, which are interfaces for data persistence or external integrations.

Example of a secondary adapter (repository)

public class InMemoryCartRepository implements CartRepository {
    private Cart cart = new Cart();

    @Override
    public void save(Cart cart) {
        this.cart = cart;
    }

    @Override
    public Cart get() {
        return cart;
    }
}
Enter fullscreen mode Exit fullscreen mode

Primary Adapters ➡ Think of them as input handlers (Controllers, CLI, API Gateways) that take external requests and pass them into the system.
Secondary Adapters ➡ Think of them as output handlers (Repositories, API clients, message brokers) that take results from the system and pass them to the outside world.

An Example Package Structure in Java

com.company.cart
│── domain
   ├── model
      ├── Product.java
      ├── Cart.java
   ├── repository
      ├── CartRepository.java
│── application
   ├── service
      ├── CartService.java
│── infrastructure
   ├── persistence
      ├── InMemoryCartRepository.java
   ├── controller
      ├── CartController.java
Enter fullscreen mode Exit fullscreen mode

🏗️ How Do These Layers Interact?
When we need to interact with an infrastructure service from an application service, we follow the Dependency Inversion Principle. This principle suggests that rather than directly depending on concrete implementations, we inject the necessary dependencies through the constructor of the class. This approach promotes loose coupling and makes the system more flexible and easier to test.

⚖️ Ports vs. Adapters: Understanding the Difference
Ports are interfaces that define what the system should do. They represent the use cases of the application (e.g., CartService).
Adapters are the implementations of those ports, responsible for managing the interaction with the outside world (like HTTP requests in the case of primary adapters, or database interactions in the case of secondary adapters).

Does the Implementation of a Primary Port Need to Be a Primary Adapter?
No. The implementation of a primary port (such as CartServiceImpl) does not have to be a primary adapter. While primary ports define the use cases, their implementation is simply the business logic that satisfies those use cases. The primary adapter (like a controller) is the one that handles the external interaction (e.g., receiving requests, calling the service methods).

CartService is a primary port (interface).
CartServiceImpl is the implementation of the primary port, but it is not an adapter.
CartController is a primary adapter, as it handles HTTP requests and delegates to the CartService (the primary port).

Conclusion
In Hexagonal Architecture, ports define what the system does (use cases), while adapters define how the system interacts with the outside world. The implementation of a port does not have to be an adapter itself; rather, it's the business logic that fulfills the requirements set by the port. Adapters, on the other hand, are responsible for connecting the external world to the core logic via the ports.

This separation makes the system easier to maintain, test, and adapt to different external technologies.

What are your thoughts on Hexagonal Architecture? Have you used it in your projects? Let’s discuss in the comments! 👇💬

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video