pccomponentes/ddd
Mini framework PHP para construir aplicaciones con DDD + CQRS + Event Sourcing, orientado a la escritura. Propone arquitectura hexagonal (Application/Domain/Infrastructure/EntryPoint/Util) y guía de capas, dependencias y persistencia basada en eventos.
## Getting Started
### Minimal Steps to Begin
1. **Install the Package**
Add the package via Composer in your Laravel project:
```bash
composer require pccomponentes/ddd
Understand the Hexagonal Structure Organize your project into the five layers:
├── Application
├── Domain
├── Infrastructure
├── EntryPoint
└── Util
First Use Case: Create a Domain Entity
Define a domain entity (e.g., User) in Domain/Entities/User.php:
namespace Domain\Entities;
use Domain\Model\AggregateRoot;
use Domain\Model\Event\DomainEventInterface;
class User extends AggregateRoot
{
public function register(string $name, string $email): void
{
$this->recordThat(new UserRegistered($name, $email));
}
}
Define a Domain Event
Create an event in Domain/Events/UserRegistered.php:
namespace Domain\Events;
use Domain\Model\Event\DomainEventInterface;
class UserRegistered implements DomainEventInterface
{
public function __construct(
public string $name,
public string $email
) {}
}
Implement a Command Handler
In Application/Commands/RegisterUserCommandHandler.php:
namespace Application\Commands;
use Domain\Entities\User;
use Domain\Repositories\UserRepositoryInterface;
class RegisterUserCommandHandler
{
public function __construct(
private UserRepositoryInterface $userRepository
) {}
public function handle(RegisterUserCommand $command): void
{
$user = User::create($command->name, $command->email);
$user->register($command->name, $command->email);
$this->userRepository->save($user);
}
}
Set Up Infrastructure (Repository)
Implement a repository in Infrastructure/Persistence/UserRepository.php:
namespace Infrastructure\Persistence;
use Domain\Entities\User;
use Domain\Repositories\UserRepositoryInterface;
class UserRepository implements UserRepositoryInterface
{
public function save(User $user): void
{
// Persist user and events using Event Sourcing (e.g., Doctrine, MongoDB ODM).
}
}
Configure Dependency Injection
Use Laravel’s service container to bind interfaces to implementations in EntryPoint/Http/Controllers/UserController.php:
namespace EntryPoint\Http\Controllers;
use Application\Commands\RegisterUserCommand;
use Application\Commands\RegisterUserCommandHandler;
use Illuminate\Http\Request;
class UserController
{
public function __construct(
private RegisterUserCommandHandler $handler
) {}
public function register(Request $request)
{
$this->handler->handle(new RegisterUserCommand(
$request->name,
$request->email
));
}
}
Leverage Value Objects
Use built-in value objects (e.g., Uuid, DateTimeValueObject) in your domain:
use Domain\Model\ValueObject\Uuid;
$userId = Uuid::create(); // Generates UUIDv7 by default
$userIdV4 = Uuid::v4(); // Explicitly generate UUIDv4
UserRegistered) when state changes occur.// Domain/Entities/User.php
class User extends AggregateRoot
{
public function changeEmail(string $newEmail): void
{
$this->recordThat(new EmailChanged($this->id, $newEmail));
}
}
Application and Domain layers).// Infrastructure/ReadModel/UserReadModel.php
class UserReadModel
{
public function project(UserRegistered $event): void
{
UserRead::create([
'id' => $event->userId,
'name' => $event->name,
'email' => $event->email,
]);
}
}
// EntryPoint/Http/Controllers/OrderController.php
class OrderController
{
public function create(OrderRequest $request, CreateOrderCommandHandler $handler)
{
$handler->handle(new CreateOrderCommand(
$request->userId,
$request->productId,
$request->quantity
));
}
}
// config/container.php
$container->bind(
Domain\Repositories\UserRepositoryInterface::class,
Infrastructure\Persistence\UserRepository::class
);
// Domain/Entities/User.php
protected function recordThat(DomainEventInterface $event): void
{
$this->events[] = $event;
event(new DomainEventOccurred($event));
}
DomainEventOccurred in Infrastructure to persist events:
// Infrastructure/EventListeners/PersistDomainEvent.php
class PersistDomainEvent
{
public function handle(DomainEventOccurred $event)
{
// Save event to event store (e.g., MongoDB, PostgreSQL).
}
}
// Infrastructure/Jobs/ProjectUserReadModel.php
class ProjectUserReadModel implements ShouldQueue
{
public function handle(UserRegistered $event)
{
UserRead::create([...]);
}
}
ProjectUserReadModel::dispatch($event);
// EntryPoint/Http/Requests/RegisterUserRequest.php
public function rules(): array
{
return [
'email' => 'required|email',
];
}
public function prepareForValidation()
{
$this->merge([
'user_id' => Uuid::create(), // Auto-generate UUIDv7
]);
}
// tests/Unit/Domain/UserTest.php
public function test_user_registration()
{
$user = User::create('John Doe', 'john@example.com');
$user->register('John Doe', 'john@example.com');
$this->assertCount(1, $user->pullEvents());
$this->assertInstanceOf(UserRegistered::class, $user->pullEvents()[0]);
}
Uuid::create() for time-sorted IDs:
$orderId = Uuid::create(); // UUIDv7 (time-ordered)
Uuid::v4() for security-sensitive cases:
$token = Uuid::v4(); // Random UUIDv4
spatie/laravel-event-sourcing) or optimize event storage (e.g., indexing).How can I help you explore Laravel packages today?