Arquitectura Hexagonal. ¿Por dónde empezar?

Introducción
  • ¿A quién va dirigido?
  • Metodología del curso
  • Herramientas y requisitos
¿Qué es la Arquitectura Hexagonal?
  • Capas de la arquitectura hexagonal
Ejemplo Práctico: Crear el thumbnail de una imagen
  • Estructura de carpetas
  • Capa de Aplicación
  • Capa de Dominio
  • Capa de Infraestructura
Ventajas de la Arquitectura Hexagonal
  • Mayor mantenibilidad
  • Pruebas más simples
  • Mayor flexibilidad
  • Mayor simplicidad
Notas adicionales
  • Para saber más
Compartir la publicación en...
Linkedin Bluesky Twitter
¿Quieres más contenidos similares? ¡Invítame a un café!
Inicio Formación Disponible Arquitectura Hexagonal. ¿Por dónde empezar?

Arquitectura Hexagonal. ¿Por dónde empezar?

La arquitectura hexagonal es un patrón de diseño propuesto por el Dr. Alistair Cockburn en 2005. Su objetivo es crear arquitecturas flexibles de bajo acoplamiento en las que los componentes de las aplicaciones puedan probarse de forma independiente sin depender de la infraestructura ni de las interfaces de usuario.

Meta
Publicado el 25/6/2025
#arquitectura, #hexagonal, #puertos, #adaptadores, #patrones, #diseño

Introducción

Esta publicación se ha diseñado como una guía práctica de introducción a la arquitectura hexagonal donde se podrá...

  • Entender los conceptos básicos de este patrón de diseño.
  • Aplicar este enfoque en cualquier proyecto.

¿A quién va dirigido?

Esta formación está orientada principalmente a desarrolladores/as de software que tengan especial interés en aplicar buenas prácticas en su operativa diaria.

Metodología del curso

La metodología de esta guía se basa en:

  • Teoría y conceptos, con explicaciones claras y concisas de los puntos clave.
  • Ejemplo de implementación.

Herramientas y requisitos

Para aprovechar al máximo este recurso se necesita:

  • Un equipo con acceso a Internet.
  • Privilegios de administrador para instalar software.
  • Conocimientos básicos de desarrollo web.


¿Qué es la Arquitectura Hexagonal?

La arquitectura hexagonal, definida por Dr. Alistair Cockburn, es una implementación de lo que llamamos una Clean Architecture.

El objetivo principal de esta arquitectura es separar nuestra aplicación por responsabilidades haciendo uso de una serie de capas con características específicas.

Esta separación nos permite desacoplar el código de las dependencias externas tales como el framework, librerías externas o cualquier cualquier otro recurso de terceras partes.

A esta propuesta se le conoce también como Puertos y Adaptadores ya que hace un uso intensivo de:

  1. Patrón Adaptador. Aplicado a las diferentes implementaciones de servicios necesarios en nuestra aplicación.
  2. Principio de Segregación de Interfaces. Definiendo así los diferentes puntos de entrada/salida de nuestra aplicación. Es decir, qué puertos tenemos disponibles.
  3. Principio de Inversión de Dependencias. Para desacoplar la lógica y hacerla mucho más modular.

Capas de la arquitectura hexagonal

La arquitectura hexagonal se llama así porque suele representarse mediante un hexágono donde cada lado representa un puerto de entrada/salida de la aplicación.

Así pues, nuestra aplicación se organiza en tres capas principales:

Infraestructura
En esta capa estarán los adaptadores.
Aplicación
En esta capa estarán los casos de uso actuando como "directores de orquesta".
Dominio
Situado en la capa central de la arquitectura, el dominio es el aspecto clave de toda la arquitectura, ya que tiene la lógica de negocio. Es decir, aquella lógica que no depende de framework ni de librerías externas.

Criterios de acceso

Cada uno de los hexágonos concéntricos sólo puede ser accedido por sí mismo y únicamente por sus capas inmediatamente superiores:

Infraestructura
⇝ Puede acceder a Infraestructura, Aplicación y a Dominio
Aplicación
⇝ Puede acceder a Aplicación y a Dominio
Dominio
⇝ Puede acceder a Dominio


Ejemplo Práctico: Crear el thumbnail de una imagen

Veamos cómo implementar la funcionalidad de crear una miniatura de una imágen siguiendo este patrón de diseño.

Estructura de carpetas

Si bien, esta es la estructura de carpetas genérica en un proyecto que sigue este patrón...

.
├── app
├── bootstrap
├── config
├── database
├── public
├── resources
├── routes
├── src
│   ├── Application
│   │   ├── UseCases
│   │   ├── Services
│   │   └── ...
│   ├── Domain
│   │   ├── Interfaces
│   │   ├── Repositories
│   │   ├── ValueObjects
│   │   └── ...
│   └── Infrastructure
│       ├── Http
│       │   ├── Controllers
│       │   ├── Requests
│       │   └── ...
│       ├── Persistence
│       ├── Providers
│       ├── Services
│       └── ...
├── tests
└── vendor

Recuerda que al añadir un nuevo namespace debemos actualizar el composer.json para que autocargue dicho path.

...
"autoload": {
    "Database\\Factories\\": "database/factories/",
    "Database\\Seeders\\": "database/seeders/",
    "App\\": "app/",
    "Hexagonal\\": "src/"
}
...

Para este ejemplo las diferentes funcionalidades estarán dentro de carpetas independientes:

.
├── app
├── bootstrap
├── config
├── database
├── public
├── resources
├── routes
├── src
│   ├─── Thumbnail
│   │    ├── Application
│   │    │   └── UseCases
│   │    │       └── ThumbnailUseCase.php
│   │    ├── Domain
│   │    │   └── Interfaces
│   │    │       └── ThumbnailServiceInterface.php
│   │    └── Infrastructure
│   │        ├── Http
│   │        │   ├── Controllers
│   │        │   │   └── ThumbnailController.php
│   │        │   └── Requests
│   │        │       └── ThumbnailFormRequest.php
│   │        ├── Providers
│   │        │   ├── Services
│   │        │   │   └── ThumbnailServiceProvider.php
│   │        │   └── UseCases
│   │        │       └── ThumbnailUseCaseProvider.php
│   │        └── Services
│   │            └── ThumbnailService.php
│   ├─── Greyscale
│   │    ├── Application
│   │    ├── Domain
│   │    └── Infrastructure
│   ├─── ...
│   └─── Shared
│        ├── Application
│        ├── Domain
│        └── Infrastructure
├── tests
└── vendor

En la línea #32 podemos ver que hay una carpeta llamada Shared. Esta carpeta contendrá aquella lógica compartida entre las diferentes funcionalidades, evitando así la duplicidad de código.

Capa de Aplicación

Caso de Uso

Los casos de uso describen la secuencia de acciones que se deben ejecutar para la realización de una tarea determinada.

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Application\UseCases;
use Hexagonal\Thumbnail\Domain\Interface\ThumbnailServiceInterface;
class ThumbnailUseCase
{
    public function __construct(
        private ThumbnailServiceInterface $service,
    ) {
    }
    public function __invoke(
        string $source,
        int $width,
    ): bool {
        return $this->service->handle($source, $width);
    }
}

Capa de Dominio

Interfaz

Esta clase actuará como un contrato permitiendo usar servicios que tengan determinados métodos definidos según la firma de la interfaz.

De este modo nuestra aplicación sólo aceptará aquellas clases que tengan definido una serie de métodos con la misma firma que las presentes en la interfaz. Garantizando así que, independientemente del servicio que implemente la lógica, la aplicación funcionará correctamente mientras se cumpla dicho contrato.

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Domain\Interfaces;
interface ThumbnailServiceInterface
{
    public function handle(
        string $source,
        int $width,
    ): bool;
}

Capa de Infraestructura

Controlador

El controlador será el encargado de llamar al caso de uso una vez se hayan validado los parámetros de entrada...

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Infrastructure\Http\Controllers;
use App\Http\Controllers\Controller;
use Hexagonal\Thumbnail\Application\UseCases\ThumbnailUseCase;
use Hexagonal\Thumbnail\Infrastructure\Http\Request\ThumbnailFormRequest;
...
class ThumbnailController extends Controller
{
    public function __construct(
        private ThumbnailUseCase $thumbnailUseCase,
    ) {
    }
    public function __invoke(ThumbnailFormRequest $request): JsonResponse
    {
        $status = $this->thumbnailUseCase->__invoke(
            source: $request->input('source'),
            width: $request->input('width'),
        );
        return response()->json(['status' => $status]);
    }
}

Form Request

Esta clase encapsula la validación de datos de entrada del controlador:

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Infrastructure\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ThumbnailFormRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }
    public function rules()
    {
        return [
            'source' => 'required|string',
            'width' => 'required|integer|gt:0',
        ];
    }
}

Proveedor del Servicio

Esta clase permite definir la inversión de dependencias del servicio así como definir el modo en que queremos referenciar a las instancias del servicio.

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Infrastructure\Providers\Services;
use Illuminate\Support\ServiceProvider;
use Hexagonal\Thumbnail\Domain\Interfaces\ThumbnailServiceInterface;
use Hexagonal\Thumbnail\Infrastructure\Services\ThumbnailService;
class ThumbnailServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(ThumbnailServiceInterface::class, function ($app) {
            return new ThumbnailService();
        });
    }
}

En este caso indicamos que, cada vez que necesitemos una instancia de ThumbnailServiceInterface::class la aplicación debe crear una instancia de ThumbnailService::class.

Proveedor del Caso de Uso

Esta clase permite definir la inversión de dependencias del caso de uso así como definir el modo en que queremos referenciar a las instancias del caso de uso:

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Infrastructure\Providers\UseCases;
use Illuminate\Support\ServiceProvider;
use Hexagonal\Thumbnail\Application\UseCases\ThumbnailUseCase;
class ThumbnailUseCaseProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(ThumbnailUseCase::class, function ($app) {
            return new ThumbnailUseCase(
                $app->make(ThumbnailServiceInterface::class),
            );
        });
    }
}

Servicio

Esta clase implementa la funcionalidad de crear una miniatura de una imágen. En este caso, usando Imagick como proveedor:

<?php
declare(strict_types = 1);
namespace Hexagonal\Thumbnail\Infrastructure\Services;
use Hexagonal\Thumbnail\Domain\Interface\ThumbnailServiceInterface;
use Imagick;
...
class ThumbnailService implements ThumbnailServiceInterface
{
    public function handle(
        string $source,
        int $width,
    ): bool {
        try {
            $imagick = new Imagick($source);
            $imagick->scaleImage($width, 0)
            $imagick->writeImage($source);
            $imagick->destroy();
            return true;
        } catch (Throwable $exception) {
            // @TODO Log the exception somehow...
            return false;
        }
    }
}

Ventajas de la Arquitectura Hexagonal

Mayor mantenibilidad
Al estar el núcleo de la aplicación desacoplado de cualquier dependencia tecnológica, es más fácil realizar cambios en los adaptadores sin afectar la lógica de negocio.
Pruebas más simples
Al tener un núcleo desacoplado se pueden realizar pruebas unitarias más fáciles y efectivas en la lógica de negocio sin depender de infraestructuras externas.
Mayor flexibilidad
La arquitectura hexagonal permite cambiar fácilmente entre diferentes tecnologías, frameworks o infraestructura sin afectar a la lógica de negocio.

Supongamos que Imagick no está disponible o bien se quiere implementar dicha funcionalidad usando otro proveedor. Con este patrón de diseño bastaría con crear un nuevo servicio que implemente la misma interfaz y actualizar la inversión de dependencias del proveedor de servicio, dejando inalterado el resto de la aplicación.

Otro caso bastante frecuente es el de actualizar el framework o bien, llevar nuestra lógica a otro framework diferente. Para ello bastaría con:

1. Copiar la carpeta src dentro de la nueva versión del framework.
2. Actualizar el fichero composer.json para que autocargue el nuevo namespace de la carpeta src.
3. Incorporar en el bootstrap del framework los proveedores de servicio y casos de uso oportunos.

Mayor simplicidad
Promueve la separación de responsabilidades en una organización estándard, lo que hace que el código sea más comprensible y accesible con independencia del framework utilizado.


Para saber más...

  • Hexagonal architecture the original 2005 article, por Dr. Alistair Cockburn
  • Presentación en Youtube de Hexagonal Architecture, por Dr. Alistair Cockburn
  • DDD, Hexagonal, Onion, Clean, CQRS... How I put it all together
  • Arquitectura Hexagonal 101


Este documento tiene licencia Atribución/Reconocimiento-SinDerivados 4.0 Internacional.

Ir arriba Ir arriba