acc-create-domain-event
Domain Event Generator
Generate DDD-compliant Domain Events with metadata and tests.
Domain Event Characteristics
- Immutable:
final readonly class - Past tense: Describes something that happened
- Self-contained: All data needed to understand what happened
- Metadata: Event ID, timestamp, causation/correlation IDs
- No behavior: Pure data, no logic
Template
<?php
declare(strict_types=1);
namespace Domain\{BoundedContext}\Event;
use Domain\Shared\Event\DomainEvent;
final readonly class {Name}Event implements DomainEvent
{
public function __construct(
{properties}
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->{aggregateIdProperty};
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
Event Metadata
<?php
declare(strict_types=1);
namespace Domain\Shared\Event;
final readonly class EventMetadata
{
public function __construct(
public string $eventId,
public DateTimeImmutable $occurredAt,
public ?string $causationId = null,
public ?string $correlationId = null,
public int $version = 1,
public ?string $userId = null
) {}
public static function create(): self
{
return new self(
eventId: self::generateId(),
occurredAt: new DateTimeImmutable()
);
}
public static function withCausation(string $causationId, ?string $correlationId = null): self
{
return new self(
eventId: self::generateId(),
occurredAt: new DateTimeImmutable(),
causationId: $causationId,
correlationId: $correlationId ?? $causationId
);
}
private static function generateId(): string
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}
Domain Event Interface
<?php
declare(strict_types=1);
namespace Domain\Shared\Event;
interface DomainEvent
{
public function aggregateId(): string;
public function occurredAt(): DateTimeImmutable;
}
Common Domain Events
Order Events
<?php
declare(strict_types=1);
namespace Domain\Order\Event;
use Domain\Shared\Event\DomainEvent;
use Domain\Shared\Event\EventMetadata;
final readonly class OrderCreatedEvent implements DomainEvent
{
public function __construct(
public string $orderId,
public string $customerId,
public DateTimeImmutable $createdAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->orderId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class OrderLineAddedEvent implements DomainEvent
{
public function __construct(
public string $orderId,
public string $productId,
public string $productName,
public int $quantity,
public int $unitPriceCents,
public string $currency,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->orderId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class OrderConfirmedEvent implements DomainEvent
{
public function __construct(
public string $orderId,
public int $totalCents,
public string $currency,
public DateTimeImmutable $confirmedAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->orderId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class OrderCancelledEvent implements DomainEvent
{
public function __construct(
public string $orderId,
public string $reason,
public DateTimeImmutable $cancelledAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->orderId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class OrderShippedEvent implements DomainEvent
{
public function __construct(
public string $orderId,
public string $trackingNumber,
public string $carrier,
public DateTimeImmutable $shippedAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->orderId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
User Events
<?php
declare(strict_types=1);
namespace Domain\User\Event;
final readonly class UserRegisteredEvent implements DomainEvent
{
public function __construct(
public string $userId,
public string $email,
public string $name,
public DateTimeImmutable $registeredAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->userId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class UserEmailChangedEvent implements DomainEvent
{
public function __construct(
public string $userId,
public string $previousEmail,
public string $newEmail,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->userId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
final readonly class UserDeactivatedEvent implements DomainEvent
{
public function __construct(
public string $userId,
public string $reason,
public DateTimeImmutable $deactivatedAt,
public EventMetadata $metadata
) {}
public function aggregateId(): string
{
return $this->userId;
}
public function occurredAt(): DateTimeImmutable
{
return $this->metadata->occurredAt;
}
}
Test Template
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Order\Event;
use Domain\Order\Event\OrderCreatedEvent;
use Domain\Shared\Event\EventMetadata;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass(OrderCreatedEvent::class)]
final class OrderCreatedEventTest extends TestCase
{
public function testCreatesEventWithAllData(): void
{
$metadata = EventMetadata::create();
$createdAt = new DateTimeImmutable();
$event = new OrderCreatedEvent(
orderId: 'order-123',
customerId: 'customer-456',
createdAt: $createdAt,
metadata: $metadata
);
self::assertSame('order-123', $event->orderId);
self::assertSame('customer-456', $event->customerId);
self::assertSame($createdAt, $event->createdAt);
self::assertSame($metadata, $event->metadata);
}
public function testReturnsAggregateId(): void
{
$event = $this->createEvent();
self::assertSame('order-123', $event->aggregateId());
}
public function testReturnsOccurredAt(): void
{
$metadata = EventMetadata::create();
$event = new OrderCreatedEvent(
orderId: 'order-123',
customerId: 'customer-456',
createdAt: new DateTimeImmutable(),
metadata: $metadata
);
self::assertEquals($metadata->occurredAt, $event->occurredAt());
}
private function createEvent(): OrderCreatedEvent
{
return new OrderCreatedEvent(
orderId: 'order-123',
customerId: 'customer-456',
createdAt: new DateTimeImmutable(),
metadata: EventMetadata::create()
);
}
}
Event Naming Conventions
Past Tense Naming
| Action | Event Name |
|---|---|
| Create order | OrderCreatedEvent |
| Confirm order | OrderConfirmedEvent |
| Cancel order | OrderCancelledEvent |
| Ship order | OrderShippedEvent |
| Add line | OrderLineAddedEvent |
| Remove line | OrderLineRemovedEvent |
| Change email | UserEmailChangedEvent |
| Register user | UserRegisteredEvent |
| Deactivate user | UserDeactivatedEvent |
Bad Names (Avoid)
| Bad Name | Why | Good Name |
|---|---|---|
CreateOrderEvent |
Present tense | OrderCreatedEvent |
OrderEvent |
Vague | OrderConfirmedEvent |
OrderStatusChangedEvent |
Too generic | OrderConfirmedEvent |
UpdateOrderEvent |
Doesn't say what changed | OrderAddressChangedEvent |
Event Data Guidelines
Include All Relevant Data
// GOOD: All data needed to understand what happened
final readonly class OrderConfirmedEvent
{
public function __construct(
public string $orderId,
public int $totalCents, // Include computed values
public string $currency,
public int $lineCount, // Summary data
public DateTimeImmutable $confirmedAt,
public EventMetadata $metadata
) {}
}
// BAD: Missing important data
final readonly class OrderConfirmedEvent
{
public function __construct(
public string $orderId, // Only ID, no details
public EventMetadata $metadata
) {}
}
Use Primitive Types
// GOOD: Primitive types for serialization
final readonly class OrderCreatedEvent
{
public function __construct(
public string $orderId, // Not OrderId
public string $customerId, // Not CustomerId
public int $totalCents, // Not Money
public string $currency,
public EventMetadata $metadata
) {}
}
// BAD: Value Objects in events (serialization issues)
final readonly class OrderCreatedEvent
{
public function __construct(
public OrderId $orderId, // Serialization complex
public Money $total, // Serialization complex
public EventMetadata $metadata
) {}
}
Generation Instructions
When asked to create a Domain Event:
- Name in past tense (what happened)
- Include aggregate ID for identification
- Add all relevant data needed to understand the event
- Use primitive types for easy serialization
- Include metadata (event ID, timestamp)
- Generate tests for structure and interface
Usage
To generate a Domain Event, provide:
- Name (e.g., "OrderConfirmed", "UserRegistered")
- Bounded Context (e.g., "Order", "User")
- Aggregate ID field
- Data fields needed
- Any computed/summary data to include
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12