laravel-ddd/starter
Composer starter kit that turns a fresh Laravel 12/13 app into a Domain-Driven Design structure. Includes base Entity/ValueObject/Repository/Service classes, 12 generators, interactive installer (auth, docs, tests, sample module), API-ready routes, and optional AI context.
Domain-Driven Design (DDD) es un enfoque de desarrollo de software que se centra en crear una comprensión profunda del dominio del negocio y organizar el código en torno a él. En lugar de estructurar tu aplicación por capas técnicas (Controladores, Modelos, Vistas), la estructuras por dominios de negocio (Usuarios, Pedidos, Productos, etc.).
DDD fue popularizado por Eric Evans en su libro de 2003 "Domain-Driven Design: Tackling Complexity in the Heart of Software".
app/
├── Http/
│ ├── Controllers/ # Todos los controladores mezclados
│ │ ├── UserController.php
│ │ ├── OrderController.php
│ │ └── ProductController.php
│ └── Requests/
│ ├── UserRequest.php
│ ├── OrderRequest.php
│ └── ProductRequest.php
├── Models/ # Todos los modelos mezclados
│ ├── User.php
│ ├── Order.php
│ └── Product.php
└── Services/
├── UserService.php
├── OrderService.php
└── ProductService.php
Problema: Cuando tu proyecto crece, pasas más tiempo buscando archivos relacionados. Todo está organizado por tipo técnico, no por contexto de negocio.
app/
├── Domains/
│ ├── Users/ # Todo sobre Usuarios en un solo lugar
│ │ ├── Entities/
│ │ │ └── User.php
│ │ ├── Services/
│ │ │ └── UserService.php
│ │ ├── Repositories/
│ │ │ ├── UserRepositoryInterface.php
│ │ │ └── EloquentUserRepository.php
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ │ └── UserController.php
│ │ │ ├── Requests/
│ │ │ │ └── StoreUserRequest.php
│ │ │ └── Resources/
│ │ │ └── UserResource.php
│ │ └── Tests/
│ ├── Orders/ # Todo sobre Pedidos en un solo lugar
│ │ ├── Entities/
│ │ ├── Services/
│ │ └── ...
│ └── Products/ # Todo sobre Productos en un solo lugar
│ ├── Entities/
│ ├── Services/
│ └── ...
Beneficio: Todo el código relacionado con un dominio vive junto. Cuando necesitas trabajar en "Pedidos", sabes exactamente dónde buscar.
| Aspecto | MVC (Laravel por defecto) | DDD |
|---|---|---|
| Organización | Por tipo técnico (Controladores, Modelos) | Por dominio de negocio (Usuarios, Pedidos) |
| Lógica de negocio | En Controladores o Modelos | En Servicios y Entidades |
| Modelos | Los modelos Eloquent hacen todo | Entidad (dominio) + Eloquent (persistencia) |
| Validación | En Controladores o Form Requests | Form Requests + Servicios de Dominio |
| Estructura de archivos | Plana | Profunda, organizada por dominio |
| Testing | Por tipo (Unit, Feature) | Por dominio |
| Escalabilidad | Difícil escalar equipos | Fácil asignar equipos a dominios |
| Onboarding | Nuevos devs deben aprender toda la estructura | Nuevos devs aprenden un dominio a la vez |
Un objeto que tiene una identidad única y un ciclo de vida. Dos entidades son diferentes incluso si todas sus propiedades son iguales, porque tienen IDs diferentes.
class User extends Entity
{
public function isPremium(): bool
{
return $this->subscription_status === 'premium';
}
public function upgradeToPremium(): void
{
if ($this->isPremium()) {
throw new \Exception('El usuario ya es premium');
}
$this->subscription_status = 'premium';
}
}
// Dos usuarios con los mismos datos son entidades DIFERENTES
$user1 = new User(['email' => 'test@example.com']);
$user2 = new User(['email' => 'test@example.com']);
$user1->id !== $user2->id; // Identidades diferentes
Un objeto que no tiene identidad, solo valor. Dos objetos de valor con el mismo valor se consideran iguales. Son inmutables (no se pueden cambiar después de su creación).
class Email extends ValueObject
{
public function __construct(protected string $value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Email inválido');
}
}
public function getValue(): mixed { return $this->value; }
public function isSame(ValueObject $vo): bool { return $this->value === $vo->getValue(); }
public function __toString(): string { return $this->value; }
}
// Dos emails con el mismo valor son IGUALES
$email1 = new Email('test@example.com');
$email2 = new Email('test@example.com');
$email1->equals($email2); // true
Una capa de abstracción entre tu dominio y la persistencia de datos. Define qué operaciones puedes hacer, no cómo se hacen.
// La interfaz define QUÉ
interface UserRepositoryInterface extends RepositoryInterface
{
public function findByEmail(string $email): ?User;
public function findActiveUsers(): Collection;
}
// La implementación define CÓMO
class EloquentUserRepository implements UserRepositoryInterface
{
public function findByEmail(string $email): ?User
{
$model = \App\Models\User::where('email', $email)->first();
return $model ? new User($model->toArray()) : null;
}
}
¿Por qué usar repositorios?
Una clase que orquesta operaciones de negocio. Usa repositorios para acceder a datos y entidades para aplicar reglas de negocio.
class UserService extends Service
{
public function __construct(
protected UserRepositoryInterface $repository,
protected EventDispatcher $dispatcher
) {}
public function registerUser(array $data): User
{
// Regla de negocio: verificar si el email ya existe
if ($this->repository->findByEmail($data['email'])) {
throw new UserAlreadyExistsException();
}
$user = $this->repository->create($data);
$this->dispatcher->dispatch(new UserRegisteredEvent($user));
return $user;
}
}
Los controladores deben ser delgados. Reciben la petición, la validan, delegan a un servicio y devuelven una respuesta. Sin lógica de negocio.
class UserController extends Controller
{
public function store(
StoreUserRequest $request,
UserService $service
): JsonResponse {
$user = $service->registerUser($request->validated());
return response()->json(['data' => $user], 201);
}
}
Petición HTTP
│
▼
Form Request (Validación)
│
▼
Controlador (Recibe la petición, delega al servicio)
│
▼
Servicio (Orquesta la lógica de negocio)
│
├── Repositorio (Accede a datos)
│ │
│ ▼
│ Modelo Eloquent (Operaciones de BD)
│
└── Entidad (Aplica reglas de negocio)
│
▼
API Resource (Formatea la respuesta)
│
▼
Respuesta HTTP
Probablemente sí. DDD brilla cuando tu proyecto crece. Para un blog simple o un formulario de contacto, la estructura por defecto de Laravel es perfecta. DDD es una inversión que da frutos a medida que aumenta la complejidad.
Sí, pero es un proceso gradual. No necesitas reescribir todo de una vez. Comienza creando un nuevo dominio para una nueva funcionalidad, y migra lentamente el código existente a medida que trabajes en él.
¡Absolutamente! DDD funciona junto con Eloquent, migraciones, colas, eventos y más de Laravel. Este paquete mantiene las migraciones en la carpeta estándar database/migrations/ y usa modelos Eloquent para la persistencia.
DDD facilita los tests. Cada dominio tiene sus propios tests, y puedes mockear repositorios para probar la lógica de negocio de forma aislada.
Empieza pequeño. Muéstrales cómo se estructuraría una nueva funcionalidad con DDD. Destaca los beneficios:
| Entidad | Modelo Eloquent |
|---|---|
| Contiene lógica de negocio | Contiene lógica de acceso a datos |
Vive en Domains/ |
Vive en Models/ |
| Usa Repositorio para persistir | Interactúa directamente con la BD |
| Concepto de dominio | Implementación técnica |
¡Sí! DDD es ideal para APIs. Los controladores delgados delegan a servicios, que devuelven datos que los API Resources formatean. La separación limpia facilita mantener y versionar tu API.
| Concepto | Propósito | Ubicación |
|---|---|---|
| Entidad (Entity) | Lógica de negocio e identidad | Domains/{Module}/Entities/ |
| Objeto de Valor | Valores inmutables con validación | Domains/{Module}/ValueObjects/ |
| Repositorio | Abstracción de acceso a datos | Domains/{Module}/Repositories/ |
| Servicio | Orquestación de negocio | Domains/{Module}/Services/ |
| Controlador | Manejo de petición/respuesta | Domains/{Module}/Http/Controllers/ |
| Form Request | Validación de entrada | Domains/{Module}/Http/Requests/ |
| API Resource | Formateo de respuesta | Domains/{Module}/Http/Resources/ |
composer require laravel-ddd/starterphp artisan ddd:installphp artisan ddd:make-module ProductsHow can I help you explore Laravel packages today?