From 15db0815bcce59a59e176a3b3dab8000498011dd Mon Sep 17 00:00:00 2001 From: Antoine Lelaisant Date: Mon, 7 Nov 2022 13:55:52 +0100 Subject: [PATCH] step 10: introduce domain events --- config/packages/messenger.yaml | 9 ++++ config/services.yaml | 2 + config/services/domain_events.yaml | 12 +++++ config/services/messenger.yaml | 9 ++++ .../Controller/DinosaursController.php | 18 +++---- .../LogWhenDinosaurDied.php | 35 +++++++++++++ .../LogWhenDinosaurIsBorn.php | 43 ++++++++++++++++ src/Application/MessageBus/EventBus.php | 10 ++++ src/Domain/Event/DinosaurDied.php | 27 ++++++++++ src/Domain/Event/DinosaurIsBorn.php | 20 ++++++++ src/Domain/Event/EventInterface.php | 10 ++++ src/Domain/EventsRegisterer.php | 31 +++++++++++ src/Domain/HasEventsRegisterer.php | 30 +++++++++++ src/Domain/Model/AggregateRoot.php | 11 ++++ src/Domain/Model/Dinosaur.php | 2 +- src/Domain/UseCase/CreateDinosaur/Handler.php | 6 +++ src/Domain/UseCase/RemoveDinosaur/Handler.php | 6 +++ .../Symfony/Messenger/EventBus.php | 33 ++++++++++++ .../Middleware/DomainEventDispatcher.php | 51 +++++++++++++++++++ 19 files changed, 354 insertions(+), 11 deletions(-) create mode 100644 config/services/domain_events.yaml create mode 100644 src/Application/DomainEventsHandler/LogWhenDinosaurDied.php create mode 100644 src/Application/DomainEventsHandler/LogWhenDinosaurIsBorn.php create mode 100644 src/Application/MessageBus/EventBus.php create mode 100644 src/Domain/Event/DinosaurDied.php create mode 100644 src/Domain/Event/DinosaurIsBorn.php create mode 100644 src/Domain/Event/EventInterface.php create mode 100644 src/Domain/EventsRegisterer.php create mode 100644 src/Domain/HasEventsRegisterer.php create mode 100644 src/Domain/Model/AggregateRoot.php create mode 100644 src/Infrastructure/Symfony/Messenger/EventBus.php create mode 100644 src/Infrastructure/Symfony/Messenger/Middleware/DomainEventDispatcher.php 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 b97f232..9766be8 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 @@ class DinosaursController extends AbstractController { public function __construct( private CommandBus $commandBus, - private QueryBus $queryBus + private QueryBus $queryBus, + private EventBus $eventBus ) {} #[Route('/dinosaurs', name: 'app_list_dinosaurs')] @@ -54,15 +57,10 @@ public function list(Request $request): Response )] public function single(string $id): Response { - $dinosaur = $this - ->dinosaursCollection - ->find($id) - ; - - if ($dinosaur === false) { - 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; + } +}