Hexagonal Architecture: Ports and Adapters

PUBLISHED: 2026-07-25
AUTHOR: MANUEL PRIETO
Software Architecture

Hexagonal Architecture, originally known as the Ports and Adapters pattern, is a software design pattern introduced by Alistair Cockburn in 2005. Its primary goal is to create loosely coupled application components that can be easily connected to their environment through the use of ports and adapters.

This approach divides a system into several interchangeable components, such as the core application (pure business logic), databases, user interfaces, testing frameworks, and third-party integrations. Its core premise is to keep the business logic at the center and push everything else to the boundaries of the application.

The name "hexagonal" became popular due to the graphical convention of representing the central component of the application as a hexagon. The choice of a hexagon was not to suggest that the system has exactly six ports or entry points, but to provide enough visual space to represent the various interfaces required between the application core and the external world.

Architectural Benefits and Use Cases

This architecture is mainly adopted to achieve the following goals in modern systems:

  • Alternative to traditional layered architecture: It organizes the system into different layers (such as Infrastructure, Application, and Domain) establishing a strict rule where communication always flows from the outside in. This shields the core system from external technological pollution.
  • Decoupling and interchangeability: It allows you to swap infrastructure components (for example, switching from Stripe to PayPal, or migrating from a SQL database to a NoSQL database) without altering or breaking business rules, as the core is oblivious to these details.
  • Testability: Isolating the central logic makes the software inherently testable. You can mock or stub the ports to verify application behavior without relying on databases, APIs, or web frameworks.
  • Multiple entry points: It is ideal when the system needs to support multiple types of interaction (such as HTTP web requests, CLI commands, or asynchronous event queues), routing all interactions through dedicated adapters that keep the application logic clean.
  • Future-proofing against technology shifts: It acts as insurance against the deprecation of libraries and frameworks. If an external technology is discontinued, the core domain logic remains untouched, and you only need to rewrite the corresponding adapters.

The Core Concept: Ports and Adapters

In practice, the implementation of this pattern is best understood by imagining your application's core (the business rules) as a wall socket (the port) and the external technologies as the interchangeable plugs (the adapters) that connect to it.

Cargando diagrama...

1. Ports (Contracts)

Ports are interfaces that define an abstract API. They belong to the application's central layer (domain or use cases) and declare what operations the system needs or exposes, regardless of how they are technically executed.

For example, if your application needs to process payments or interact with users, you define an interface named PaymentPort or UserRepositoryContract inside the core layer. Your business logic depends solely on these interfaces, completely unaware of whether Stripe or a MySQL database is handling the implementation.

2. Adapters (Implementations)

Adapters are the code implementations that reside in the outer layers (infrastructure). They act as a bridge, translating data between the format required by the external technology and the format expected by your application core. Your application logic never interacts directly with databases or web frameworks; it does so via these adapters.

For instance, you create concrete classes in the infrastructure layer that implement your interfaces, such as a StripeAdapter or PayPalAdapter (for payment processing), and an EloquentUserRepository (for database access).

Flow of Execution

To see this dynamic in action, here is how these concepts work together during the lifecycle of a request:

  1. Inbound Path (Driving Adapters): The execution flow begins through driving adapters (such as web controllers, CLI commands, or event consumers). For example, an HTTP controller receives a request, translates the request payload into simple data structures, and invokes the application use case via a driving port (e.g., CreateUserUseCase).
  2. Core Execution: The use case processes the input data and interacts with pure domain entities.
  3. Outbound Path (Driven Adapters): When the business logic needs to persist information or call external systems, it invokes a driven port (e.g., UserRepositoryInterface). Thanks to dependency inversion, the concrete adapter injected at runtime (e.g., EloquentUserRepository which implements the repository) handles the persistence using the framework or database of choice.

This structure guarantees that your core domain is decoupled and fully testable. To change a provider or run unit tests, you simply inject a different adapter implementation into the corresponding port.

Differences with Clean Architecture

While both architectures share the same primary goal—protecting business logic by isolating it from technical details and frameworks—they differ in visual metaphor, structural rigidity, and overall complexity.

Clean Architecture (Clean Architecture Foundations), proposed by Robert C. Martin in 2012, is an evolution that combines Ports and Adapters with Onion Architecture principles into a structured, four-tier implementation.

Visual Metaphor and Structure

  • Hexagonal Architecture: Encourages you to think in terms of "inside" and "outside" using ports and adapters. The core is the socket, and everything else plugs into it. While usually organized into Infrastructure, Application, and Domain, its main focus is simply pushing technical details to the boundaries.
  • Clean Architecture: Organizes the system into strict concentric rings, detailing how to structure the inner components. It typically prescribes four mandatory layers: Entities, Use Cases, Interface Adapters, and Frameworks/Drivers.

Rigidity and the Dependency Rule

  • Clean Architecture: Enforces a strict Dependency RuleRegla de DependenciaPrincipio central de la Arquitectura Limpia que establece que las dependencias del código fuente solo pueden apuntar hacia adentro, es decir, hacia las capas que contienen la lógica de negocio y las reglas empresariales (Entidades y Casos de Uso). Ningún componente o clase de un círculo interno puede conocer, importar o hacer referencia directa a elementos ubicados en círculos externos (como frameworks, bases de datos o la interfaz de usuario).: source code dependencies can only point inward. It requires more setup, strict boundaries, and intensive use of Dependency Inversion, which provides excellent protection but can introduce boilerplate.
  • Hexagonal Architecture: Is less dogmatic internally. It focuses on ensuring external integrations connect through interfaces, making it more approachable for developers and highly pragmatic for medium-sized projects.

Domain-Driven Design (DDD) Integration

Domain-Driven Design (DDD) and Hexagonal Architecture fit together naturally. While Hexagonal Architecture provides the structural boundary to isolate the core from external tools, DDD provides the tactical patterns to model the business domain inside that boundary.

The integration maps DDD concepts directly into the hexagonal layers:

The Domain Layer (Core of the Hexagon)

The deepest layer, containing pure business rules. Under DDD, it is organized using tactical patterns:

  • Entities (Domain Models): The primary business objects with their own identity (e.g., User or Order). DDD advocates for "rich domain models" containing validation rules and state mutations (following the Tell, don't ask principle).
  • Value Objects: Immutable attributes without identity (e.g., Email, UUID, or Money). They are self-validating upon instantiation (e.g., checking if the email format is correct before creation).
  • Ports (Interfaces): Repository and external service interfaces (e.g., UserRepositoryInterface) live inside the domain layer, defining the business contracts without mentioning database specifics.

The Application Layer (The Orchestrator)

Surrounds the domain and contains the Use Cases. It acts as an orchestrator that coordinates the domain and infrastructure:

  1. Receives input data (typically via a Data Transfer Object or DTO).
  2. Validates inputs by instantiating Value Objects and constructs Domain Entities.
  3. Uses the ports (interfaces) injected in its constructor to delegate persistence or infrastructure tasks.

The Infrastructure Layer (Adapters)

Contains the controllers, APIs, and database implementations (such as a database repository using an ORM like Eloquent). Its main task is translation: it receives pure domain entities from the use case and maps them to the database schema or external API format, ensuring infrastructure details never leak into the domain.

Bounded Contexts

At a strategic level, DDD introduces Bounded Contexts. Instead of designing a single large hexagon for the entire project, the system is split into multiple independent Bounded Contexts (e.g., "Billing" and "Shipping"), each with its own isolated hexagon. This modular design simplifies maintenance and enables the system to evolve toward a Microservices architecture if needed, or integrate other data flow patterns like CQRS and Event Sourcing.