¿Qué es la Arquitectura Hexagonal?
La arquitectura hexagonal, también conocida como Ports and Adapters, fue propuesta por Alistair Cockburn como una forma de estructurar aplicaciones para que sean independientes de frameworks, bases de datos, y otros detalles de infraestructura.
El objetivo es claro: aislar el dominio de negocio de los detalles técnicos. Así, tu lógica de negocio no dependerá de Laravel, ni de una base de datos, ni de ninguna herramienta externa.
Esto permite:
- Hacer pruebas más fáciles y rápidas
- Cambiar herramientas (base de datos, framework, servicios externos) sin tocar el núcleo del sistema
- Reutilizar el dominio en diferentes contextos (web, CLI, API, etc.)
¿Cómo se ve esto en Laravel?
Laravel, por defecto, promueve una estructura basada en capas (Controllers, Models, Views, etc.). Pero no nos obliga a una arquitectura en particular.
La arquitectura hexagonal propone dividir el sistema en tres áreas principales:
1. Core (Dominio)
- Entidades
- Reglas de negocio
- Casos de uso
- Interfaces de puertos
2. Puertos (Ports)
- Interfaces para entrada (por ejemplo, controladores)
- Interfaces para salida (por ejemplo, repositorios, servicios externos)
3. Adaptadores (Adapters)
- Implementaciones concretas para interactuar con Laravel, la base de datos, o APIs externas.
Estructura de Carpetas
Aquí un ejemplo realista en Laravel:
src/
├── Domain/
│ ├── User/
│ │ ├── Entities/
│ │ ├── ValueObjects/
│ │ ├── Repositories/
│ │ └── Services/
│
├── Application/
│ ├── User/
│ │ ├── UseCases/
│ │ └── DTOs/
│
├── Infrastructure/
│ ├── Persistence/
│ │ └── Eloquent/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Requests/
│ └── Providers/
│
└── Support/
Laravel seguiría manejando rutas, providers, y el ciclo de vida HTTP, pero la lógica de negocio está aislada en el Dominio y la Aplicación.
Ejemplo Rápido: Crear un Usuario
Supongamos que quieres crear un usuario. En una arquitectura tradicional de Laravel, harías todo en un Controller o Service. En hexagonal, se ve así:
Dominio
// Domain/User/Entities/User.php
class User
{
public function __construct(
public string $name,
public string $email,
public string $password
) {}
}
// Domain/User/Repositories/UserRepositoryInterface.php
interface UserRepositoryInterface
{
public function save(User $user): void;
}
Caso de Uso
// Application/User/UseCases/CreateUserUseCase.php
class CreateUserUseCase
{
public function __construct(
private UserRepositoryInterface $repository
) {}
public function execute(CreateUserDTO $dto): void
{
$user = new User($dto->name, $dto->email, bcrypt($dto->password));
$this->repository->save($user);
}
}
Infraestructura
// Infrastructure/Persistence/Eloquent/EloquentUserRepository.php
class EloquentUserRepository implements UserRepositoryInterface
{
public function save(User $user): void
{
UserModel::create([
'name' => $user->name,
'email' => $user->email,
'password' => $user->password
]);
}
}
// Infrastructure/Http/Controllers/UserController.php
class UserController extends Controller
{
public function store(Request $request, CreateUserUseCase $useCase)
{
$dto = new CreateUserDTO(
$request->name,
$request->email,
$request->password
);
$useCase->execute($dto);
return response()->json(['message' => 'User created successfully']);
}
}
¿Y las ventajas?
- Puedes probar
CreateUserUseCase
sin Laravel. - Puedes cambiar
EloquentUserRepository
por una API externa o una base de datos diferente. - El dominio es claro, limpio y reusable.
Conclusión
La arquitectura hexagonal no es solo una moda. Es una forma de diseñar sistemas mantenibles, testeables y desacoplados.
Laravel, aunque es un framework de propósito general, se adapta perfectamente a este enfoque si separas bien responsabilidades.
En próximos artículos exploraremos:
- Cómo organizar los DTOs, entidades y value objects
- Cómo inyectar dependencias en Laravel con interfaces
- Cómo testear los casos de uso sin framework