Language:

Buscar

Estructurando un Proyecto Laravel con Arquitectura Hexagonal

  • Share this:
Estructurando un Proyecto Laravel con Arquitectura Hexagonal

En el primer artículo de esta serie hablamos sobre qué es la arquitectura hexagonal y por qué puede mejorar la calidad, mantenibilidad y escalabilidad de nuestras aplicaciones. Hoy vamos a dar un paso más allá: cómo estructurar un proyecto Laravel aplicando esta arquitectura sin morir en el intento.

Si bien Laravel es un framework potente y flexible, su estructura por defecto no impone una separación clara entre capas. Y eso está bien. Porque te permite adaptarlo a patrones como DDD, Clean Architecture o, en nuestro caso, Hexagonal.


¿Por qué necesitamos una estructura diferente?

El típico proyecto Laravel tiene carpetas como app/Http, app/Models, app/Services, app/Providers, etc. Todo mezclado, muchas veces sin una frontera clara entre infraestructura y dominio. Esto puede volverse un problema cuando la aplicación crece.

La arquitectura hexagonal nos empuja a separar responsabilidades en capas bien definidas:

  • Dominio: la lógica de negocio pura, sin dependencias.
  • Aplicación: casos de uso que orquestan el dominio.
  • Infraestructura: donde vive Laravel (HTTP, base de datos, colas, etc).

Estructura recomendada de carpetas

Una forma sencilla y limpia de comenzar es crear un directorio src/ dentro del proyecto, y allí organizar todo.

laravel-project/
├── app/
├── bootstrap/
├── config/
├── public/
├── routes/
├── src/
│   ├── Domain/
│   │   └── User/
│   │       ├── Entities/
│   │       ├── ValueObjects/
│   │       ├── Repositories/
│   │       └── Services/
│   ├── Application/
│   │   └── User/
│   │       ├── UseCases/
│   │       └── DTOs/
│   ├── Infrastructure/
│   │   ├── Persistence/
│   │   │   └── Eloquent/
│   │   ├── Http/
│   │   │   ├── Controllers/
│   │   │   └── Requests/
│   │   └── Providers/
│   └── Shared/
├── tests/
├── composer.json

Dominio (Core)

Esta es la parte más importante de tu aplicación. Aquí no hay Laravel. No hay Eloquent. Solo clases PHP puras.

Por ejemplo:

// src/Domain/User/Entities/User.php

class User
{
    public function __construct(
        public readonly string $name,
        public readonly string $email
    ) {}

    public function changeEmail(string $newEmail): void
    {
        // Lógica de negocio, validaciones, etc.
    }
}

Además de entidades, aquí van:

  • Value Objects (Email, UserId, etc.)
  • Interfaces de Repositorio (contratos)
  • Reglas de negocio o servicios del dominio

Application

La capa de aplicación es donde viven los casos de uso. Son clases que orquestan acciones del dominio, respondiendo a una intención del usuario.

// src/Application/User/UseCases/RegisterUserUseCase.php

class RegisterUserUseCase
{
    public function __construct(
        private UserRepositoryInterface $repository
    ) {}

    public function execute(RegisterUserDTO $dto): void
    {
        $user = new User($dto->name, $dto->email);
        $this->repository->save($user);
    }
}

Esta capa también es framework-agnóstica. No sabe nada de Laravel, ni de controladores, ni de base de datos. Solo usa contratos del dominio.


Infrastructure

Aquí es donde Laravel vuelve a aparecer. En esta capa implementamos:

  • Repositorios usando Eloquent
  • Controladores HTTP
  • Jobs, listeners, mailers, etc.
  • Cualquier adaptador externo (como APIs, servicios externos, bases de datos)

Ejemplo de implementación del repositorio:

// src/Infrastructure/Persistence/Eloquent/EloquentUserRepository.php

class EloquentUserRepository implements UserRepositoryInterface
{
    public function save(User $user): void
    {
        UserModel::create([
            'name' => $user->name,
            'email' => $user->email,
        ]);
    }
}

Y el controlador:

// src/Infrastructure/Http/Controllers/UserController.php

class UserController extends Controller
{
    public function register(RegisterUserRequest $request, RegisterUserUseCase $useCase)
    {
        $dto = new RegisterUserDTO(
            name: $request->name,
            email: $request->email
        );

        $useCase->execute($dto);

        return response()->json(['message' => 'Usuario registrado']);
    }
}

Inyección de dependencias en Laravel

Para que Laravel sepa cómo resolver UserRepositoryInterface, debemos registrarlo en un ServiceProvider:

// src/Infrastructure/Providers/AppServiceProvider.php

public function register(): void
{
    $this->app->bind(
        UserRepositoryInterface::class,
        EloquentUserRepository::class
    );
}

¡Y listo! Laravel se encargará de inyectar automáticamente la implementación donde se necesite.


Algunos consejos prácticos

  • No sobreestructures al principio. Empieza simple y solo divide cuando tenga sentido.
  • No tengas miedo de usar Eloquent, pero mantenlo fuera del dominio.
  • Usa DTOs para comunicar datos entre capas.
  • Nombra las cosas con cuidado: UseCase, Service, Entity, Repository, etc.
  • Pon límites a Laravel. Laravel debe ser un detalle de infraestructura, no el corazón del negocio.

Conclusión

Estructurar un proyecto Laravel con arquitectura hexagonal no es complicado, pero sí requiere disciplina. El objetivo es claro: tener un núcleo de negocio limpio, testable, y desacoplado del framework.

Esta forma de organizar el código te prepara para crecer sin dolor. Y cuando llegue el momento de hacer cambios grandes (migrar de base de datos, exponer el dominio por una API o CLI, etc.), te alegrarás de haber separado las capas desde el principio.


En el próximo artículo veremos cómo modelar el dominio correctamente, incluyendo entidades, objetos de valor y reglas de negocio. Nos meteremos de lleno en el corazón de la aplicación.

¿Te gustó este artículo? ¿Tienes dudas o quieres compartir tu experiencia estructurando proyectos Laravel?
Adelante, comparte!

Carlos Santiago

Carlos Santiago

Laravel Developer