← Назад к вопросам

Что делать с событиями предметной области, порождённые агрегатором?

1.7 Middle🔥 151 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Стратегии обработки доменных событий, порожденных агрегатом

В современной PHP разработке, особенно при использовании Domain-Driven Design (DDD) и Event-Driven Architecture (EDA), обработка событий предметной области (Domain Events), порожденных агрегатом, является критически важной. Эти события представляют факты (facts) — то, что уже произошло в системе, и их корректная обработка определяет надежность и расширяемость приложения.

1. Основные подходы к обработке событий агрегата

Синхронная обработка внутри транзакции

В этом подходе события обрабатываются сразу после их генерации агрегатом, в рамках той же транзакции (unit of work).

class OrderAggregateRoot
{
    private array $events = [];

    public function confirmOrder(): void
    {
        // Изменение состояния агрегата
        $this->status = 'confirmed';
        
        // Генерация события
        $this->events[] = new OrderConfirmedEvent(
            $this->id,
            $this->customerId,
            $this->totalAmount
        );
        
        // Синхронная обработка внутри агрегата или сервиса
        $this->applyEventsImmediately();
    }
    
    private function applyEventsImmediately(): void
    {
        foreach ($this->events as $event) {
            // Например, отправка email, обновление статистики
            $this->eventDispatcher->dispatch($event);
        }
        $this->events = [];
    }
}

Преимущества:

  • Гарантированная обработка события (если транзакция успешна)
  • Упрощенная логика — нет необходимости в механизмах отложенной обработки

Недостатки:

  • Увеличение времени транзакции
  • Риск распространения ошибок (если обработка события неуспешна, вся транзакция откатывается)
  • Трудности масштабирования

Отложенная обработка через механизм событий

События собираются во время работы агрегата, но их обработка происходит после успешного сохранения агрегата (commit транзакции).

class DoctrineAggregateRepository implements AggregateRepositoryInterface
{
    public function save(AggregateRoot $aggregate): void
    {
        // 1. Сохраняем состояние агрегата в БД
        $this->entityManager->persist($aggregate);
        $this->entityManager->flush();
        
        // 2. После успешного сохранения — обрабатываем события
        foreach ($aggregate->releaseEvents() as $event) {
            // Используем Event Bus для отправки события
            $this->eventBus->dispatch($event);
        }
    }
}

Преимущества:

  • Транзакция агрегата не зависит от обработки событий
  • Возможность масштабирования обработчиков событий
  • Легче реализовать компенсационные действия при ошибках

Недостатки:

  • Необходимость гарантированной доставки событий
  • Возможность рассинхронизации (агрегат сохранен, но событие не обработано)

2. Ключевые архитектурные компоненты

Для эффективной обработки требуется несколько компонентов:

Event Bus или Event Dispatcher

Сервис, отвечающий за передачу событий соответствующим обработчикам (Event Handlers).

interface EventBusInterface
{
    public function dispatch(DomainEvent $event): void;
}

class SymfonyEventBus implements EventBusInterface
{
    public function dispatch(DomainEvent $event): void
    {
        // Логика поиска и вызова обработчиков
        foreach ($this->getHandlersForEvent($event) as $handler) {
            $handler->handle($event);
        }
    }
}

Специализированные Event Handlers

Каждый обработчик отвечает за одну конкретную реакцию на событие.

class OrderConfirmedEventHandler
{
    public function handle(OrderConfirmedEvent $event): void
    {
        // 1. Отправка email клиенту
        $this->emailService->sendConfirmation($event->orderId());
        
        // 2. Обновление аналитической статистики
        $this->analyticsRepository->incrementConfirmedOrders(
            $event->customerId()
        );
        
        // 3. Создание задач для логистики
        $this->logisticsService->scheduleDelivery($event->orderId());
    }
}

3. Практические рекомендации для PHP разработчиков

Всегда отделяйте генерацию события от его обработки. Агрегат должен отвечать только за бизнес-правила и генерацию событий, но не за их побочные эффекты.

Используйте механизм "releaseEvents". Агрегат должен предоставить метод для получения всех необработанных событий после завершения операции.

abstract class AggregateRoot
{
    private array $domainEvents = [];
    
    protected function recordEvent(DomainEvent $event): void
    {
        $this->domainEvents[] = $event;
    }
    
    public function releaseEvents(): array
    {
        $events = $this->domainEvents;
        $this->domainEvents = [];
        return $events;
    }
}

Рассмотрите использование Outbox Pattern для гарантированной доставки. Вместо непосредственной отправки в Event Bus, события сначала сохраняются в специальную таблицу "outbox" в той же транзакции, что и агрегат, затем отдельный процесс читает и обрабатывает их.

Обеспечьте идемпотентность обработчиков. События могут быть обработаны повторно (при retry механизмах), поэтому обработчики должны быть устойчивы к повторным выполнениям.

Логируйте все события. Это критично для аудита, анализа проблем и восстановления системы.

4. Интеграция с PHP фреймворками

В Symfony можно использовать Symfony Messenger с транспортами (doctrine, rabbitmq) для асинхронной обработки. В Laravel — Laravel Events и Queues.

// Symfony Messenger интеграция
class OrderConfirmedEvent implements MessageInterface
{
    // ...
}

#[AsMessageHandler]
class OrderConfirmedEventHandler
{
    public function __invoke(OrderConfirmedEvent $event): void
    {
        // Обработка события, возможно асинхронно через транспорт
    }
}

Выбор стратегии зависит от требований проекта: для высоконагруженных систем предпочтительна асинхронная обработка с гарантированной доставкой, для простых приложений может быть достаточно синхронного выполнения в рамках транзакции. Ключевой принцип — события должны обрабатываться декларативно (через отдельные обработчики), а не как часть логики агрегата.

Что делать с событиями предметной области, порождённые агрегатором? | PrepBro