Hexagon Architecture

6 minute read

Published:

This article briefly explains the Hexagon Architecture (also known as Ports and Adapters Architecture), its components.

I. Ports and adapters architecture

Evolving from layered architecture

Layered architecture

  • Layered architecture:

    • Presentation layer: UI/Views
    • Business layer: business logic
    • Data access layer: database
  • What is matter is the Business Logic layer -> is what makes the Application
  • The top and bottom layers are simply entry/exit points to/from the Application

Ports and adapters architecture

Port

  • Port: an interface of an entry point and/or exit point with no knowledge of concreate implementation behind it, Application uses it

Adapter

  • Adapter: A class that transforms (adapts) an interface into another.
  • Example:
public interface A {
    void doSomething();
}

public interface B {
    void performAction();
}

public class BImpl implements B {
    @Override
    public void performAction() {
        System.out.println("Action performed");
    }
}

public class Adapter implements A {
    private B b;
    public Adapter(B b) { this.b = b; }

    @Override
    public void doSomething() {
        // Transform the request if necessary
        // Proxy the request to the inner object
        b.performAction();
    }
}

// Use cases
B bObject = new BImpl();
A adapter = new Adapter(bObject);

adapter.doSomething(); // This will call bObject.performAction()

Two types of adapters

Adapters

  • Primary/Driving adapters:

    • Role: Start actions within the application.
    • Location: left side
    • Function: Represent the User Interface (UI) or other entry points (like APIs).
    • Dependency: These adapters depend on a port (an interface) and get injected with a concrete implementation of this port.
  • Secondary/Driven adapters:

    • Role: React to actions initiated by primary adapters.
    • Location: right side
    • Function: Represent connections to backend tools, databases, or external services.
    • Dependency: These adapters are the concrete implementation of a port and get injected into the business logic, although the business logic only interacts with the interface.

Port and adapter usage:

Usage

  • On the Left Side (Primary Adapters):
    • Port: An interface that defines a use case or business logic.
    • Adapter: Depends on this port and gets injected with a concrete implementation of the port.
    • Concrete Implementation: The actual use case logic that implements the port.
    • Belonging: Both the port and its concrete implementation belong inside the application.
// PORT
public interface UseCase {
    void execute();
}

// Concrete Implementation (Use Case):
public class SomeUseCase implements UseCase {
    @Override
    public void execute() {
        // Business logic here
    }
}

// Primary adapter - UI
public class UIAdapter {
    private UseCase useCase;

    public UIAdapter(UseCase useCase) {
        this.useCase = useCase;
    }

    public void onUserAction() {
        useCase.execute();
    }
}
  • On the Right Side (Secondary Adapters):
    • Port: An interface that represents a dependency (e.g., a repository, a service).
    • Adapter: The concrete implementation of this port.
    • Concrete Implementation: An external tool or service wrapped to conform to the port interface.
    • Belonging: The port belongs inside the application, but its concrete implementation is external.
// PORT (interface)
public interface Repository {
    void save(Data data);
}

// Concrete Implementation (External Adapter):
public class ExternalRepository implements Repository {
    @Override
    public void save(Data data) {
        // Interact with external database or service
    }
}

// Usage in application
public class BusinessLogic {
    private Repository repository;

    public BusinessLogic(Repository repository) {
        this.repository = repository;
    }

    public void performSave(Data data) {
        repository.save(data);
    }
}

II. Hexagonal Architecture integration

Fundamental blocks of the system

Hexagonal Architecture

  • Application core = business logic, what the application does
    • It could use user interface (web app, mobile, …), but shouldn’t depend on what UI triggers it
  • The flow:
    • 1. User interface -> 2. Application core -> 3. Infrastructure –> 2. Application core –> 1. User interface

Tools

Tools

  • Tools used by the application example:
    • Tell application to do something: Web server or CLI console (delivery mechanisms)
    • Is told by application to do something: database engine, search engine

Connecting to Application

  • Adapters = code units that connect the tools to the application core
    • Implementation that will allow the business logic to communicate with a specific tool and vice-versa.
    • Primary adapter = adapters tell application to do something
    • Secondary adapter = adapters are told by application to do something

Port

  • Adapters are created to fit a specific entry point to the Application core ~ a Port.
  • Port = an interface defines how the tool can (use/be used by) the application core

  • Ports (Interfaces) belong inside the business logic and fit what application needs, while Adapters belong outside.

Primary/Driving Adapters

Primary adapters

  • Wrap around a Port, use port to tell Application core what to do.
  • Or in other words, it transform a delivery mechanism into a method call in the Application core.
  • Example:
    • Controllers or Console Commands (UI)
    • Implementation of Port is injected into UI constructors

    • A port can be a Service interface / Repository interface that UI requires.
    • A port can also be a Command/Query Bus interface.

      The concreate impl of Port is then injected and used in UI

Secondary/Driven Adapters

Secondary adapters

  • Unlike the Driver Adapters, who wrap around a port, the Driven Adapters implement a Port, an interface, and are then injected into the Application Core, wherever the port is required
  • Example:
    • Port = persistence interface that meets its needs. i.e: interface dbAdapter -> save()
    • 2 adapters MySQLAdapter implements dbAdapter and MongoDBAdapter implements dbAdapter
// PORT
public interface PersistenceInterface {
    void saveData(List<Data> data);
}

// Adapters
public class MySQLAdapter implements PersistenceInterface {
    private MySQLDatabase mySQLDatabase;

    public MySQLAdapter(MySQLDatabase mySQLDatabase) {
        this.mySQLDatabase = mySQLDatabase;
    }

    @Override
    public void saveData(List<Data> data) {
        // Implementation to save data in MySQL
        mySQLDatabase.save(data);
    }
}

// Application Core
public class DataService {
    private PersistenceInterface persistenceInterface;

    public DataService(PersistenceInterface persistenceInterface) {
        this.persistenceInterface = persistenceInterface;
    }

    public void processData(List<Data> data) {
        // Some processing logic
        persistenceInterface.saveData(data);
    }
}

// switch vendors
public class AppConfig {
    public PersistenceInterface persistenceInterface() {
        // return new MySQLAdapter(new MySQLDatabase());
        // return new PostgreSQLAdapter(new PostgreSQLDatabase());
        return new MongoDBAdapter(new MongoDBDatabase());
    }

    public DataService dataService() {
        return new DataService(persistenceInterface());
    }
}


Inversion of Control

  • Note:
    • The adapters depend on a specific tool and a specific port (by implementing an interface).
    • But business logic only depends on the port (interface), which is designed to fit the business logic needs, so it doesn’t depend on a specific adapter or tool.

Application Core organisation

Application layer

Application layer

  • The use cases are defined in Application core, and can be triggered in our Application Core by one or several UI

References

Leave a Comment