Skill: Create a DDD Aggregate Root

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