Ответ
Доменное событие (Domain Event) — это неизменяемый объект-значение, который фиксирует факт, произошедший в предметной области (домене) и имеющий значение для бизнеса. Он представляет собой что-то, что уже случилось в прошлом, например, OrderWasPlaced, UserEmailWasChanged или PaymentFailed. События используются для информирования других частей системы о произошедшем изменении, способствуя слабой связанности между агрегатами и контекстами.
Характеристики доменного события:
- Именование: Имя события — глагол в прошедшем времени, отражающий завершённое действие.
- Неизменяемость (Immutability): После создания данные события не могут быть изменены.
- Содержание: Содержит минимальный набор данных, необходимый для обработки события (например, ID агрегата, временная метка, релевантные свойства).
- Контекст: Привязано к конкретному агрегату, который его породил.
Пример реализации на PHP:
<?php
declare(strict_types=1);
namespace AppDomainOrderEvent;
use AppDomainOrderValueObjectOrderId;
use AppDomainSharedEventDomainEvent;
use DateTimeImmutable;
final class OrderWasPlaced implements DomainEvent
{
private DateTimeImmutable $occurredOn;
public function __construct(
private OrderId $orderId,
private string $customerEmail,
private float $totalAmount
) {
$this->occurredOn = new DateTimeImmutable();
}
// Геттеры предоставляют доступ к данным события
public function getOrderId(): OrderId
{
return $this->orderId;
}
public function getCustomerEmail(): string
{
return $this->customerEmail;
}
public function getTotalAmount(): float
{
return $this->totalAmount;
}
public function occurredOn(): DateTimeImmutable
{
return $this->occurredOn;
}
}
Как это работает внутри агрегата (Order):
class Order extends AggregateRoot
{
private OrderId $id;
private array $recordedEvents = [];
public static function place(OrderId $id, string $customerEmail, array $items): self
{
$order = new self();
$order->id = $id;
// ... логика создания заказа ...
// 1. После успешного создания агрегата регистрируем событие
$order->recordThat(new OrderWasPlaced($id, $customerEmail, $order->calculateTotal()));
return $order;
}
private function recordThat(DomainEvent $event): void
{
$this->recordedEvents[] = $event;
}
// 2. Метод для извлечения и очистки записанных событий (вызывается после сохранения агрегата)
public function releaseEvents(): array
{
$events = $this->recordedEvents;
$this->recordedEvents = [];
return $events;
}
}
Практическое применение: После сохранения агрегата Order в репозитории, все записанные события (OrderWasPlaced) извлекаются и отправляются в шину событий. На них могут реагировать различные обработчики:
- Отправка email-подтверждения заказа.
- Обновление аналитической панели о новых продажах.
- Резервирование товара на складе.
Это позволяет разделить ответственность и избежать прямых вызовов сервисов внутри доменной логики.