sbooker / domain-events
Domain events
Installs: 4 642
Dependents: 2
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Requires
- php: ^7.4 || ^8.0
- ramsey/uuid: ^4.0
Requires (Dev)
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2025-08-22 15:58:16 UTC
README
Domain Events Library (sbooker/domain-events
)
Простая, но мощная библиотека для реализации паттерна "События предметной области" (Domain Events) с упором на чистую архитектуру и принципы DDD.
Базовая библиотека в экосистеме sbooker/domain
Главная особенность — встроенный механизм для автоматического отслеживания, кто совершил действие (Actor), без загрязнения доменной модели.
Экосистема sbooker/domain
предоставляет надежные способы их асинхронной обработки — как с использованием брокера сообщений, так и без него.
Ключевые особенности
- Простота интеграции: Используйте трейт
DomainEventCollector
в ваших сущностях. - Автоматическое обогащение событий: Встроенный декоратор
ActorAwarePublisher
автоматически добавляет к событию информацию о текущем пользователе (Actor
). - Нулевые зависимости: Библиотека не зависит от других библиотек или какого-либо фреймворка.
- Гибкость: Основана на простых интерфейсах (
Publisher
,ActorStorage
,DomainEventSubscriber
), которые вы реализуете в своей инфраструктуре или используете библиотеки из экосистемыsbooker/domain
.
Установка
composer require sbooker/domain-events
Архитектура и компоненты
DomainEvent
: Абстрактный класс, от которого наследуются все ваши события. Уже содержит ID сущности, время возникновения и опционально —Actor
.DomainEventCollector
: Трейт, который вы добавляете в свои сущности (Агрегаты). Он предоставляет метод для сбора (publish
) и последующей отправки (dispatchEvents
) событий.Publisher
: Интерфейс для диспетчера событий. Вы должны предоставить его реализацию или использовать библиотеку sbooker/domain-events-persistence.Actor
: Простой объект, идентифицирующий пользователя, который инициировал действие.ActorStorage
: Интерфейс для получения текущегоActor
из контекста приложения (например, из сессии или токена безопасности).ActorAwarePublisher
: Декоратор для вашегоPublisher
, который автоматически "внедряет"Actor
в событие перед его публикацией.
Быстрый старт
1. Подготовьте вашу сущность и событие
Используйте трейт DomainEventCollector
в вашем агрегате и определите класс события.
// src/Product.php use Sbooker\DomainEvents\DomainEventCollector; use Sbooker\DomainEvents\DomainEntity; class Product implements DomainEntity { use DomainEventCollector; public function __construct(UuidInterface $id, string $name) { // ... // Записываем событие о том, что произошло $this->publish(new ProductCreated($id, $name)); } } // src/ProductCreated.php use Sbooker\DomainEvents\DomainEvent; final class ProductCreated extends DomainEvent { // ... ваш код события }
2. Реализуйте сохранение событий (Transactional Outbox)
Рекомендуется сохранять события в специальную таблицу (event
) в той же базе данных и в той же транзакции, что и сущность.
Для этого используйте решение из sbooker/domain-events-persistence и sbooker/transaction-manager. Или напишите собственное:
// src/Infrastructure/OutboxPublisher.php use Sbooker\DomainEvents\Publisher; use Sbooker\DomainEvents\DomainEvent; // Этот Publisher сохраняет события в репозиторий (например, в БД) final class OutboxPublisher implements Publisher { private OutboxEventRepository $repository; public function __construct(OutboxEventRepository $repository) { $this->repository = $repository; } public function publish(DomainEvent $event): void { $outboxEvent = new OutboxEvent($event); $this->repository->add($outboxEvent); } }
В сервисном слое вы используете OutboxPublisher
. Весь процесс сохранения происходит внутри одной транзакции.
// src/UseCase/CreateProduct/Handler.php use Sbooker\DomainEvents\ActorAwarePublisher; // 1. Создаем Publisher, который сохраняет события в БД $publisher = new ActorAwarePublisher( new OutboxPublisher($outboxEventRepository), // <-- Используем наш новый Publisher new SymfonyActorStorage($security) ); // 2. Выполняем бизнес-логику $product = new Product(Uuid::uuid4(), 'Ноутбук'); $productRepository->add($product); // 3. Передаем события в Publisher, который тоже сохранит их в БД $product->dispatchEvents($publisher); // 4. Коммитим транзакцию // Doctrine EntityManager или ваш Unit of Work сохранит И продукт, И события в одной транзакции $entityManager->flush();
3. Асинхронно обработайте события
Для обработки событий используется фоновый процесс (воркер), который читает события из таблицы.
Экосистема sbooker/domain
поддерживает два основных подхода:
Подход 1: Прямая обработка (без брокера сообщений)
Этот подход идеален, когда вы хотите избежать усложнения инфраструктуры (без RabbitMQ, Kafka и т.д.), но при этом обеспечить надежную параллельную обработку событий.
Библиотека sbooker/domain-events-persistence предоставляет готовые инструменты для запуска нескольких воркеров, которые не будут мешать друг другу, благодаря механизму пессимистичных блокировок.
Пример воркера-обработчика:
// src/Command/ProcessOutboxEventsCommand.php class ProcessOutboxEventsCommand extends Command { public function execute(): int { // findUnprocessed() из sbooker/domain-events-persistence может блокировать // события для безопасной параллельной обработки. $eventsToProcess = $this->outboxRepo->findUnprocessed(); foreach ($eventsToProcess as $outboxEvent) { try { // Напрямую вызываем нужный обработчик (Subscriber) $this->eventSubscriber->handle($outboxEvent->getDomainEvent()); $this->outboxRepo->markAsProcessed($outboxEvent); } catch (\Exception $e) { // Логируем ошибку, событие будет обработано повторно. $this->logger->error('Failed to process event', ['id' => $outboxEvent->getId(), 'error' => $e]); } } $this->entityManager->flush(); return Command::SUCCESS; } }
Подход 2: Ретрансляция в брокер сообщений (классический Outbox)
Классический паттерн для микросервисной архитектуры или когда требуется интеграция с внешними системами через брокер сообщений.
В этом случае задача фонового процесса — всего лишь гарантированно доставить событие из таблицы в RabbitMQ (или другой брокер).
Пример воркера-ретранслятора:
// src/Command/RelayOutboxEventsCommand.php class RelayOutboxEventsCommand extends Command { public function execute(): int { $eventsToRelay = $this->outboxRepo->findUnprocessed(); foreach ($eventsToRelay as $outboxEvent) { try { // Отправляем событие во внешнюю шину $this->realMessageBroker->publish($outboxEvent->getDomainEvent()); $this->outboxRepo->markAsProcessed($outboxEvent); } catch (\Exception $e) { // Логируем ошибку, повторим отправку при следующем запуске. $this->logger->error('Failed to relay event', ['id' => $outboxEvent->getId(), 'error' => $e]); } } $this->entityManager->flush(); return Command::SUCCESS; } }
License
See LICENSE file.