Skill: Create a DDD Aggregate Root
Steps
1. Define the Aggregate Class
declare(strict_types=1);
namespace App\Module\{Module}\Domain\Model;
final class {Aggregate}
{
/** @var list<DomainEventInterface> */
private array $domainEvents = [];
private function __construct(
private readonly {Aggregate}Id $id,
private readonly TenantId $tenantId,
// ... other properties
private readonly \DateTimeImmutable $createdAt,
private \DateTimeImmutable $updatedAt,
) {}
public static function create(
{Aggregate}Id $id,
TenantId $tenantId,
// ... creation params
): self {
$aggregate = new self(
id: $id,
tenantId: $tenantId,
// ...
createdAt: new \DateTimeImmutable(),
updatedAt: new \DateTimeImmutable(),
);
$aggregate->recordEvent(new {Aggregate}Created(
// ... event payload
));
return $aggregate;
}
/** @return list<DomainEventInterface> */
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
private function recordEvent(DomainEventInterface $event): void
{
$this->domainEvents[] = $event;
}
}
2. Create the ID Value Object
final readonly class {Aggregate}Id
{
public function __construct(
public string $value,
) {}
}
3. Define Invariants
- Validate in the constructor and factory methods
- Throw domain-specific exceptions for violations
- Document invariants as comments or in tests
4. Write Unit Tests
- Test creation with valid data
- Test each state transition
- Test each invariant violation
- Test event emission