
ARQUITECTURA HEXAGONAL
La arquitectura hexagonal organiza el código en tres capas principales: Dominio, Aplicación e Infraestructura. Estas capas se conectan mediante puertos o interfaces y adaptadores, asegurando que la lógica de negocio esté aislada de los sistemas externos.
- Capa de Dominio o Domain: La capa de dominio es pura, sin referencias a bases de datos, frameworks o tecnologías externas.
- Propósito: Es el núcleo de la aplicación, donde reside la lógica de negocio pura. Contiene las reglas y conceptos fundamentales del sistema, independientes de tecnologías externas (bases de datos, frameworks, etc.).
- Componentes:
- Entidades: Objetos que representan conceptos del negocio, con sus reglas y comportamientos (por ejemplo, un User con validaciones).
- Puertos de salida: Interfaces que definen cómo el dominio interactúa con sistemas externos (por ejemplo, UserRepository para guardar usuarios).
- Características:
- No depende de ninguna otra capa.
- No contiene código relacionado con bases de datos, APIs o frameworks.
- Es el corazón de la aplicación, donde se codifican las reglas de negocio esenciales.
- Capa de Aplicación o Application La capa de aplicación orquesta la lógica, pero no sabe cómo se guarda el usuario (en MySQL, un archivo, etc.).
- Propósito: Contiene la lógica de aplicación, que orquesta las interacciones entre el dominio y los sistemas externos. Define los casos de uso del sistema (por ejemplo, "registrar un usuario").
- Componentes:
- Casos de uso: Clases que implementan flujos de negocio, como RegisterUserUseCase, que coordinan entidades y puertos.
- Puertos de entrada (opcional): Interfaces que definen cómo el exterior puede interactuar con los casos de uso (por ejemplo, RegisterUserService).
- Características:
- Depende del dominio (usa entidades y puertos de salida).
- No depende de la infraestructura (no sabe si los datos se guardan en MySQL o un archivo).
- Es el puente entre el dominio y el mundo exterior.
- Capa de Infraestructura o Infrastructure
- Propósito: Contiene los detalles técnicos y las implementaciones concretas que conectan la aplicación con el mundo exterior (bases de datos, APIs, interfaces de usuario, etc.).
- Componentes:
- Adaptadores de salida: Implementaciones de los puertos de salida, como un repositorio para MySQL (MySqlUserRepository).
- Adaptadores de entrada: Componentes que reciben solicitudes externas, como controladores HTTP o comandos CLI (RegisterUserController).
- Características:
- Depende del dominio y la aplicación (implementa los puertos).
- Contiene código específico de tecnologías (por ejemplo, PDO para MySQL, Guzzle para HTTP).
- Es intercambiable: puedes cambiar un adaptador (por ejemplo, de MySQL a MongoDB) sin afectar el dominio o la aplicación.
Relación entre las capas
- Flujo de dependencias: Las dependencias van de afuera hacia adentro (Infraestructura → Aplicación → Dominio). El dominio es independiente, la aplicación depende del dominio, y la infraestructura depende de ambas.
- Puertos y adaptadores:
- Los puertos son interfaces definidas en el dominio (salida) o la aplicación (entrada) que especifican contratos.
- Los adaptadores en la infraestructura implementan esos contratos para conectar con sistemas externos.
- Aislamiento: El dominio y la aplicación no saben cómo se implementan los adaptadores, lo que permite cambiar tecnologías sin modificar la lógica de negocio.
Cómo interactúan las capas
- Entrada: El adaptador de entrada (RegisterUserController) recibe una solicitud HTTP y la traduce en una llamada al caso de uso (RegisterUserUseCase).
- Aplicación: El caso de uso crea una entidad (User) y usa un puerto de salida (UserRepository) para guardar los datos.
- Dominio: La entidad contiene las reglas de negocio, y el puerto define el contrato para la persistencia.
- Salida: El adaptador de salida (MySqlUserRepository) implementa el puerto, conectando con una base de datos MySQL.
Ventajas de las capas en la arquitectura hexagonal
- Independencia: El dominio no depende de tecnologías externas, lo que facilita cambiar bases de datos o interfaces.
- Testabilidad: Puedes probar la capa de dominio y aplicación con mocks, sin necesidad de infraestructura real.
- Modularidad: Puedes añadir nuevos adaptadores (por ejemplo, un repositorio para MongoDB) sin modificar el núcleo.
- Mantenibilidad: La separación clara de responsabilidades hace que el código sea más fácil de entender y modificar.
La estructura de árbol del proyecto sería:
project/ ├── Application/ │ └── RegisterUserUseCase.php # Capa de Aplicación: Caso de uso ├── Domain/ │ │ ├── Entities/ │ │ └── User.php # Capa de Dominio: Entidad │ └── Ports/ │ └── UserRepository.php # Capa de Dominio: Puerto de salida ├── Infrastructure/ │ ├── Http/ │ │ │ └── RegisterUserController.php # Capa de Infraestructura: Adaptador de entrada │ └── Persistence/ │ └── MySqlUserRepository.php # Capa de Infraestructura: Adaptador de salida
Ejemplo de código
- Capa de Dominio
- Entidad (src/Domain/Entities/User.php):
namespace App\Domain\Entities; class User { private $name; private $email; public function __construct(string $name, string $email) { if (empty($name) || empty($email)) { throw new \InvalidArgumentException('Name and email are required'); } $this->name = $name; $this->email = $email; } public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } }
- Puerto de Salida
namespace App\Domain\Ports; use App\Domain\Entities\User; interface UserRepository { public function save(User $user): void; }
- Entidad (src/Domain/Entities/User.php):
- Capa de Aplicación
- Caso de uso (src/Application/RegisterUserUseCase.php):
namespace App\Application; use App\Domain\Entities\User; use App\Domain\Ports\UserRepository; class RegisterUserUseCase { private $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function execute(string $name, string $email): void { $user = new User($name, $email); // Usa la entidad del dominio $this->userRepository->save($user); // Usa el puerto de salida } }
- Caso de uso (src/Application/RegisterUserUseCase.php):
- Capa de Infraestructura
- Adaptador de entrada (src/Infrastructure/Http/RegisterUserController.php):
namespace App\Infrastructure\Http; use App\Application\RegisterUserUseCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class RegisterUserController { private $registerUserUseCase; public function __construct(RegisterUserUseCase $registerUserUseCase) { $this->registerUserUseCase = $registerUserUseCase; } public function register(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $data = json_decode($request->getBody()->getContents(), true); try { $this->registerUserUseCase->execute($data['name'], $data['email']); $response->getBody()->write(json_encode(['message' => 'User registered successfully'])); return $response->withStatus(201); } catch (\InvalidArgumentException $e) { $response->getBody()->write(json_encode(['error' => $e->getMessage()])); return $response->withStatus(400); } } }
- Adaptador de salida (src/Infrastructure/Persistence/MySqlUserRepository.php):
namespace App\Infrastructure\Persistence; use App\Domain\Entities\User; use App\Domain\Ports\UserRepository; use PDO; class MySqlUserRepository implements UserRepository { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function save(User $user): void { $stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)'); $stmt->execute([ 'name' => $user->getName(), 'email' => $user->getEmail(), ]); } }