diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 55cd4eb..5386421 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -6,7 +6,16 @@ framework: buses: command.bus: + default_middleware: true middleware: + - Infrastructure\Symfony\Messenger\Middleware\DomainEventDispatcher + - dispatch_after_current_bus - doctrine_transaction query.bus: ~ + + event.bus: + default_middleware: allow_no_handlers + middleware: + - dispatch_after_current_bus + - doctrine_transaction \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index d877526..941cb31 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -12,3 +12,5 @@ services: Application\: resource: '../src/Application/' + exclude: + - '../src/Application/DomainEventsHandler/' diff --git a/config/services/domain_events.yaml b/config/services/domain_events.yaml new file mode 100644 index 0000000..a3030d7 --- /dev/null +++ b/config/services/domain_events.yaml @@ -0,0 +1,12 @@ +--- +services: + _defaults: + autowire: true + public: false + + Domain\EventsRegisterer: ~ + + Application\DomainEventsHandler\: + resource: '%kernel.project_dir%/src/Application/DomainEventsHandler/**.php' + tags: + - { name: messenger.message_handler, bus: event.bus } diff --git a/config/services/messenger.yaml b/config/services/messenger.yaml index a5bd415..d234e70 100644 --- a/config/services/messenger.yaml +++ b/config/services/messenger.yaml @@ -5,6 +5,9 @@ services: autoconfigure: true public: false + Infrastructure\Symfony\Messenger\Middleware\: + resource: '%kernel.project_dir%/src/Infrastructure/Symfony/Messenger/Middleware/**.php' + Infrastructure\Symfony\Messenger\CommandBus: $messageBus: '@command.bus' @@ -16,3 +19,9 @@ services: Application\MessageBus\QueryBus: alias: Infrastructure\Symfony\Messenger\QueryBus + + Infrastructure\Symfony\Messenger\EventBus: + $messageBus: '@event.bus' + + Application\MessageBus\EventBus: + alias: Infrastructure\Symfony\Messenger\EventBus \ No newline at end of file diff --git a/src/Application/Controller/DinosaursController.php b/src/Application/Controller/DinosaursController.php index a0dcdde..2d67bca 100644 --- a/src/Application/Controller/DinosaursController.php +++ b/src/Application/Controller/DinosaursController.php @@ -5,7 +5,9 @@ use Application\Form\Type\DinosaurType; use Application\Form\Type\SearchType; use Application\MessageBus\CommandBus; +use Application\MessageBus\EventBus; use Application\MessageBus\QueryBus; +use Domain\Event\DinosaurIsBorn; use Domain\Exception\DinosaurNotFoundException; use Domain\Query\GetSingleDinosaur; use Domain\Query\GetAllDinosaurs; @@ -22,7 +24,8 @@ final class DinosaursController extends AbstractController { public function __construct( private CommandBus $commandBus, - private QueryBus $queryBus + private QueryBus $queryBus, + private EventBus $eventBus ) { } @@ -55,12 +58,10 @@ public function list(Request $request): Response )] public function single(string $id): Response { - $dinosaur = $this - ->dinosaursCollection - ->find($id); - - if (false === $dinosaur) { - throw $this->createNotFoundException('The dinosaur you are looking for does not exists.'); + try { + $dinosaur = $this->queryBus->dispatch(new GetSingleDinosaur\Query($id)); + } catch (DinosaurNotFoundException $e) { + throw $this->createNotFoundException(); } return $this->render('dinosaur.html.twig', [ diff --git a/src/Application/DomainEventsHandler/LogWhenDinosaurDied.php b/src/Application/DomainEventsHandler/LogWhenDinosaurDied.php new file mode 100644 index 0000000..944baea --- /dev/null +++ b/src/Application/DomainEventsHandler/LogWhenDinosaurDied.php @@ -0,0 +1,35 @@ +logger->info(sprintf( + 'Dinosaur %s died', + $event->getDinosaurName() + )); + } + + public static function getHandledMessages(): iterable + { + yield DinosaurDied::class; + } +} diff --git a/src/Application/DomainEventsHandler/LogWhenDinosaurIsBorn.php b/src/Application/DomainEventsHandler/LogWhenDinosaurIsBorn.php new file mode 100644 index 0000000..70bde7a --- /dev/null +++ b/src/Application/DomainEventsHandler/LogWhenDinosaurIsBorn.php @@ -0,0 +1,43 @@ +getAggregateRootId(); + + $dinosaur = $this->dinosaursCollection->find($dinosaurId); + + if (!$dinosaur instanceof Dinosaur) { + throw new DinosaurNotFoundException($dinosaurId); + } + + $this->logger->info(sprintf( + 'Dinosaur %s was born', + $dinosaur->getName() + )); + } + + public static function getHandledMessages(): iterable + { + yield DinosaurIsBorn::class; + } +} diff --git a/src/Application/MessageBus/EventBus.php b/src/Application/MessageBus/EventBus.php new file mode 100644 index 0000000..ddb0b07 --- /dev/null +++ b/src/Application/MessageBus/EventBus.php @@ -0,0 +1,10 @@ +dinosaurName = $dinosaur->getName(); + } + + public function getAggregateRootId(): string + { + return (string) $this->dinosaur->getId(); + } + + public function getDinosaurName(): string + { + return $this->dinosaurName; + } +} diff --git a/src/Domain/Event/DinosaurIsBorn.php b/src/Domain/Event/DinosaurIsBorn.php new file mode 100644 index 0000000..1dd3af3 --- /dev/null +++ b/src/Domain/Event/DinosaurIsBorn.php @@ -0,0 +1,20 @@ +dinosaur->getId(); + } +} \ No newline at end of file diff --git a/src/Domain/Event/EventInterface.php b/src/Domain/Event/EventInterface.php new file mode 100644 index 0000000..318d62e --- /dev/null +++ b/src/Domain/Event/EventInterface.php @@ -0,0 +1,10 @@ + */ + private array $events = []; + + public function register(EventInterface ...$events): void + { + array_push($this->events, ...$events); + } + + /** + * @return array + */ + public function getEvents(): array + { + return $this->events; + } + + public function flush(): void + { + $this->events = []; + } +} diff --git a/src/Domain/HasEventsRegisterer.php b/src/Domain/HasEventsRegisterer.php new file mode 100644 index 0000000..8737095 --- /dev/null +++ b/src/Domain/HasEventsRegisterer.php @@ -0,0 +1,30 @@ +eventsRegisterer = $eventsRegisterer; + } + + public function getEventsRegisterer(): EventsRegisterer + { + return $this->eventsRegisterer; + } + + public function registerEvents(EventInterface ...$events): void + { + $this->eventsRegisterer->register(...$events); + } +} diff --git a/src/Domain/Model/AggregateRoot.php b/src/Domain/Model/AggregateRoot.php new file mode 100644 index 0000000..dc5be02 --- /dev/null +++ b/src/Domain/Model/AggregateRoot.php @@ -0,0 +1,11 @@ +dinosaursCollection->add($dinosaur); + $this->registerEvents(new DinosaurIsBorn($dinosaur)); + return new Output($dinosaur); } } diff --git a/src/Domain/UseCase/RemoveDinosaur/Handler.php b/src/Domain/UseCase/RemoveDinosaur/Handler.php index 70fabe8..f4b493d 100644 --- a/src/Domain/UseCase/RemoveDinosaur/Handler.php +++ b/src/Domain/UseCase/RemoveDinosaur/Handler.php @@ -5,10 +5,14 @@ namespace Domain\UseCase\RemoveDinosaur; use Domain\Collection\DinosaursCollection; +use Domain\Event\DinosaurDied; use Domain\Exception\DinosaurNotFoundException; +use Domain\HasEventsRegisterer; class Handler { + use HasEventsRegisterer; + public function __construct( private DinosaursCollection $dinosaursCollection ) { @@ -27,6 +31,8 @@ public function __invoke(Input $input): Output $this->dinosaursCollection->remove($dinosaur); + $this->registerEvents(new DinosaurDied($dinosaur)); + return new Output(); } } diff --git a/src/Infrastructure/Symfony/Messenger/EventBus.php b/src/Infrastructure/Symfony/Messenger/EventBus.php new file mode 100644 index 0000000..21be1ff --- /dev/null +++ b/src/Infrastructure/Symfony/Messenger/EventBus.php @@ -0,0 +1,33 @@ +handle($event); + } catch (HandlerFailedException $handlerFailedException) { + $nestedExceptions = $handlerFailedException->getNestedExceptions(); + + if (false === $nested = current($nestedExceptions)) { + throw $handlerFailedException; + } + + throw $nested; + } + } +} diff --git a/src/Infrastructure/Symfony/Messenger/Middleware/DomainEventDispatcher.php b/src/Infrastructure/Symfony/Messenger/Middleware/DomainEventDispatcher.php new file mode 100644 index 0000000..9c90087 --- /dev/null +++ b/src/Infrastructure/Symfony/Messenger/Middleware/DomainEventDispatcher.php @@ -0,0 +1,51 @@ +next()->handle($envelope, $stack); + + $stamp = $envelope->last(HandledStamp::class); + + if (!$stamp instanceof HandledStamp) { + return $envelope; + } + + /* + * This entity manager clear operation is mandatory since we need to have + * the freshest object as possible as we always read entities after several + * changes that might not have been updated by doctrine. + */ + $this->em->clear(); + + $events = $this->eventsRegisterer->getEvents(); + + foreach ($events as $event) { + $this->eventBus->dispatch($event); + } + + $this->eventsRegisterer->flush(); + + return $envelope; + } +}