From 95ccd9cd091afa7a5e5ada5bca06aad54c032a6f Mon Sep 17 00:00:00 2001 From: Antoine Lelaisant Date: Fri, 4 Nov 2022 13:59:23 +0100 Subject: [PATCH] step 9: introduce the query bus --- config/packages/messenger.yaml | 2 + config/services/messenger.yaml | 5 ++ config/services/queries.yaml | 11 +++ .../Controller/DinosaursController.php | 34 +++----- .../Controller/SpeciesController.php | 33 +++----- .../DataTransformer/SpeciesReadToModel.php | 45 ++++++++++ src/Application/Form/Type/DinosaurType.php | 8 ++ src/Application/MessageBus/QueryBus.php | 10 +++ src/Domain/Query/GetAllDinosaurs/Handler.php | 27 ++++++ src/Domain/Query/GetAllDinosaurs/Query.php | 13 +++ src/Domain/Query/GetAllSpecies/Handler.php | 27 ++++++ src/Domain/Query/GetAllSpecies/Query.php | 12 +++ .../Query/GetSingleDinosaur/Handler.php | 28 +++++++ src/Domain/Query/GetSingleDinosaur/Query.php | 13 +++ src/Domain/Query/GetSingleSpecies/Handler.php | 28 +++++++ src/Domain/Query/GetSingleSpecies/Query.php | 13 +++ src/Domain/ReadModel/Dinosaur.php | 82 +++++++++++++++++++ src/Domain/ReadModel/Species.php | 59 +++++++++++++ src/Domain/UseCase/EditDinosaur/Input.php | 14 ++++ src/Domain/UseCase/EditSpecies/Input.php | 12 +++ .../Symfony/Messenger/QueryBus.php | 33 ++++++++ 21 files changed, 466 insertions(+), 43 deletions(-) create mode 100644 config/services/queries.yaml create mode 100644 src/Application/Form/DataTransformer/SpeciesReadToModel.php create mode 100644 src/Application/MessageBus/QueryBus.php create mode 100644 src/Domain/Query/GetAllDinosaurs/Handler.php create mode 100644 src/Domain/Query/GetAllDinosaurs/Query.php create mode 100644 src/Domain/Query/GetAllSpecies/Handler.php create mode 100644 src/Domain/Query/GetAllSpecies/Query.php create mode 100644 src/Domain/Query/GetSingleDinosaur/Handler.php create mode 100644 src/Domain/Query/GetSingleDinosaur/Query.php create mode 100644 src/Domain/Query/GetSingleSpecies/Handler.php create mode 100644 src/Domain/Query/GetSingleSpecies/Query.php create mode 100644 src/Domain/ReadModel/Dinosaur.php create mode 100644 src/Domain/ReadModel/Species.php create mode 100644 src/Infrastructure/Symfony/Messenger/QueryBus.php diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 9e88b1d..55cd4eb 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -8,3 +8,5 @@ framework: command.bus: middleware: - doctrine_transaction + + query.bus: ~ diff --git a/config/services/messenger.yaml b/config/services/messenger.yaml index ea71b4d..a5bd415 100644 --- a/config/services/messenger.yaml +++ b/config/services/messenger.yaml @@ -11,3 +11,8 @@ services: Application\MessageBus\CommandBus: alias: Infrastructure\Symfony\Messenger\CommandBus + Infrastructure\Symfony\Messenger\QueryBus: + $messageBus: '@query.bus' + + Application\MessageBus\QueryBus: + alias: Infrastructure\Symfony\Messenger\QueryBus diff --git a/config/services/queries.yaml b/config/services/queries.yaml new file mode 100644 index 0000000..a52136c --- /dev/null +++ b/config/services/queries.yaml @@ -0,0 +1,11 @@ +--- +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Domain\Query\: + resource: '%kernel.project_dir%/src/Domain/Query/**/Handler.php' + tags: + - { name: messenger.message_handler, bus: query.bus } diff --git a/src/Application/Controller/DinosaursController.php b/src/Application/Controller/DinosaursController.php index 984b399..b97f232 100644 --- a/src/Application/Controller/DinosaursController.php +++ b/src/Application/Controller/DinosaursController.php @@ -5,7 +5,10 @@ use Application\Form\Type\DinosaurType; use Application\Form\Type\SearchType; use Application\MessageBus\CommandBus; -use Domain\Collection\DinosaursCollection; +use Application\MessageBus\QueryBus; +use Domain\Exception\DinosaurNotFoundException; +use Domain\Query\GetSingleDinosaur; +use Domain\Query\GetAllDinosaurs; use Domain\UseCase\CreateDinosaur; use Domain\UseCase\EditDinosaur; use Domain\UseCase\RemoveDinosaur; @@ -18,8 +21,8 @@ class DinosaursController extends AbstractController { public function __construct( - private DinosaursCollection $dinosaursCollection, - private CommandBus $commandBus + private CommandBus $commandBus, + private QueryBus $queryBus ) {} #[Route('/dinosaurs', name: 'app_list_dinosaurs')] @@ -36,10 +39,7 @@ public function list(Request $request): Response $q = $search['q']; } - $dinosaurs = $this - ->dinosaursCollection - ->search($q) - ; + $dinosaurs = $this->queryBus->dispatch(new GetAllDinosaurs\Query($q)); return $this->render('dinosaurs-list.html.twig', [ 'dinosaurs' => $dinosaurs, @@ -111,32 +111,22 @@ public function create(Request $request): Response )] public function edit(Request $request, int $id): Response { - $dinosaur = $this - ->dinosaursCollection - ->find($id) - ; - - if ($dinosaur === false) { + try { + $dinosaur = $this->queryBus->dispatch(new GetSingleDinosaur\Query($id)); + } catch (DinosaurNotFoundException $e) { throw $this->createNotFoundException( 'The dinosaur you are looking for does not exists.' ); } - $form = $this->createForm(DinosaurType::class, $dinosaur->toArray()); + $form = $this->createForm(DinosaurType::class, $dinosaur); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $dinosaur = $form->getData(); - $input = new EditDinosaur\Input( - $id, - $dinosaur['name'], - $dinosaur['gender'], - $dinosaur['species']->getId(), - $dinosaur['age'], - $dinosaur['eyesColor'] - ); + $input = EditDinosaur\Input::fromReadModel($dinosaur); try { $this->commandBus->dispatch($input); diff --git a/src/Application/Controller/SpeciesController.php b/src/Application/Controller/SpeciesController.php index 5cdad58..9def8cb 100644 --- a/src/Application/Controller/SpeciesController.php +++ b/src/Application/Controller/SpeciesController.php @@ -6,9 +6,11 @@ use Application\Form\Type\SpeciesType; use Application\MessageBus\CommandBus; -use Domain\Collection\SpeciesCollection; +use Application\MessageBus\QueryBus; use Domain\Exception\SpeciesAlreadyExistsException; use Domain\Exception\SpeciesNotFoundException; +use Domain\Query\GetAllSpecies; +use Domain\Query\GetSingleSpecies; use Domain\UseCase\CreateSpecies; use Domain\UseCase\EditSpecies; use Domain\UseCase\RemoveSpecies; @@ -20,8 +22,8 @@ class SpeciesController extends AbstractController { public function __construct( - private SpeciesCollection $speciesCollection, - private CommandBus $commandBus + private CommandBus $commandBus, + private QueryBus $queryBus ) { } @@ -29,10 +31,7 @@ public function __construct( #[Route('/species', name: 'app_list_species')] public function list(): Response { - $speciesList = $this - ->speciesCollection - ->findAll() - ; + $speciesList = $this->queryBus->dispatch(new GetAllSpecies\Query()); return $this->render('species-list.html.twig', [ 'speciesList' => $speciesList @@ -78,29 +77,21 @@ public function create(Request $request): Response )] public function edit(Request $request, string $id): Response { - $species = $this - ->speciesCollection - ->find($id) - ; - - if ($species === false) { + try { + $species = $this->queryBus->dispatch(new GetSingleSpecies\Query($id)); + } catch (SpeciesNotFoundException $e) { throw $this->createNotFoundException( 'The species you are looking for does not exists.' ); } - $form = $this->createForm(SpeciesType::class, $species->toArray()); + $form = $this->createForm(SpeciesType::class, $species); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $rawSpecies = $form->getData(); + $species = $form->getData(); - $input = new EditSpecies\Input( - speciesId: $id, - name: $rawSpecies['name'], - habitats: $rawSpecies['habitats'], - feeding: $rawSpecies['feeding'] - ); + $input = EditSpecies\Input::fromReadModel($species); try { $this->commandBus->dispatch($input); diff --git a/src/Application/Form/DataTransformer/SpeciesReadToModel.php b/src/Application/Form/DataTransformer/SpeciesReadToModel.php new file mode 100644 index 0000000..bc2e187 --- /dev/null +++ b/src/Application/Form/DataTransformer/SpeciesReadToModel.php @@ -0,0 +1,45 @@ +getId(); + + $species = $this->speciesCollection->find($speciesId); + + if ($species === null) { + throw new SpeciesNotFoundException($speciesId); + } + + return $species; + } + + public function reverseTransform(mixed $value) + { + if (!$value instanceof ModelSpecies) { + return $value; + } + + return new Species($value); + } +} diff --git a/src/Application/Form/Type/DinosaurType.php b/src/Application/Form/Type/DinosaurType.php index beaf5b4..20da2fc 100644 --- a/src/Application/Form/Type/DinosaurType.php +++ b/src/Application/Form/Type/DinosaurType.php @@ -2,6 +2,7 @@ namespace Application\Form\Type; +use Application\Form\DataTransformer\SpeciesReadToModel; use Domain\Model\Species; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -13,6 +14,11 @@ class DinosaurType extends AbstractType { + public function __construct( + private SpeciesReadToModel $speciesReadToModel + ) { + } + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder @@ -31,5 +37,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->add('eyesColor', ColorType::class) ->add('submit', SubmitType::class) ; + + $builder->get('species')->addModelTransformer($this->speciesReadToModel); } } diff --git a/src/Application/MessageBus/QueryBus.php b/src/Application/MessageBus/QueryBus.php new file mode 100644 index 0000000..a46d977 --- /dev/null +++ b/src/Application/MessageBus/QueryBus.php @@ -0,0 +1,10 @@ +dinosaursCollection->search($query->search); + + return array_map( + fn (ModelDinosaur $dinosaur) => new Dinosaur($dinosaur), + $dinosaurs + ); + } +} diff --git a/src/Domain/Query/GetAllDinosaurs/Query.php b/src/Domain/Query/GetAllDinosaurs/Query.php new file mode 100644 index 0000000..e4e78f3 --- /dev/null +++ b/src/Domain/Query/GetAllDinosaurs/Query.php @@ -0,0 +1,13 @@ +speciesCollection->findAll(); + + return array_map( + fn (ModelSpecies $species) => new Species($species), + $species + ); + } +} diff --git a/src/Domain/Query/GetAllSpecies/Query.php b/src/Domain/Query/GetAllSpecies/Query.php new file mode 100644 index 0000000..c5294e2 --- /dev/null +++ b/src/Domain/Query/GetAllSpecies/Query.php @@ -0,0 +1,12 @@ +dinosaursCollection->find($query->id); + + if ($dinosaur === null) { + throw new DinosaurNotFoundException($query->id); + } + + return new Dinosaur($dinosaur); + } +} diff --git a/src/Domain/Query/GetSingleDinosaur/Query.php b/src/Domain/Query/GetSingleDinosaur/Query.php new file mode 100644 index 0000000..af1b738 --- /dev/null +++ b/src/Domain/Query/GetSingleDinosaur/Query.php @@ -0,0 +1,13 @@ +speciesCollection->find($query->id); + + if ($speciesModel === null) { + throw new SpeciesNotFoundException($query->id); + } + + return new Species($speciesModel); + } +} diff --git a/src/Domain/Query/GetSingleSpecies/Query.php b/src/Domain/Query/GetSingleSpecies/Query.php new file mode 100644 index 0000000..6dd0056 --- /dev/null +++ b/src/Domain/Query/GetSingleSpecies/Query.php @@ -0,0 +1,13 @@ +id = $dinosaurModel->getId(); + $this->name = $dinosaurModel->getName(); + $this->gender = $dinosaurModel->getGender(); + $this->species = new Species($dinosaurModel->getSpecies()); + $this->age = $dinosaurModel->getAge(); + $this->eyesColor = $dinosaurModel->getEyesColor(); + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getGender(): string + { + return $this->gender; + } + + public function setGender(string $gender): void + { + $this->gender = $gender; + } + + public function getSpecies(): Species + { + return $this->species; + } + + public function setSpecies(Species $species): void + { + $this->species = $species; + } + + public function getAge(): int + { + return $this->age; + } + + public function setAge(int $age): void + { + $this->age = $age; + } + + public function getEyesColor(): string + { + return $this->eyesColor; + } + + public function setEyesColor(string $eyesColor): void + { + $this->eyesColor = $eyesColor; + } +} diff --git a/src/Domain/ReadModel/Species.php b/src/Domain/ReadModel/Species.php new file mode 100644 index 0000000..8472071 --- /dev/null +++ b/src/Domain/ReadModel/Species.php @@ -0,0 +1,59 @@ +id = $modelSpecies->getId(); + $this->name = $modelSpecies->getName(); + $this->habitats = $modelSpecies->getHabitats(); + $this->feeding = $modelSpecies->getFeeding(); + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getHabitats(): array + { + return $this->habitats; + } + + public function setHabitats(array $habitats): void + { + $this->habitats = $habitats; + } + + public function getFeeding(): string + { + return $this->feeding; + } + + public function setFeeding(string $feeding): void + { + $this->feeding = $feeding; + } +} diff --git a/src/Domain/UseCase/EditDinosaur/Input.php b/src/Domain/UseCase/EditDinosaur/Input.php index cf63d2c..6e22464 100644 --- a/src/Domain/UseCase/EditDinosaur/Input.php +++ b/src/Domain/UseCase/EditDinosaur/Input.php @@ -4,6 +4,8 @@ namespace Domain\UseCase\EditDinosaur; +use Domain\ReadModel\Dinosaur; + final class Input { public function __construct( @@ -15,4 +17,16 @@ public function __construct( public readonly string $eyesColor ) { } + + public static function fromReadModel(Dinosaur $dinosaur): self + { + return new self( + (string) $dinosaur->getId(), + $dinosaur->getName(), + $dinosaur->getGender(), + (string) $dinosaur->getSpecies()->getId(), + $dinosaur->getAge(), + $dinosaur->getEyesColor() + ); + } } \ No newline at end of file diff --git a/src/Domain/UseCase/EditSpecies/Input.php b/src/Domain/UseCase/EditSpecies/Input.php index 95c71f0..e2a5680 100644 --- a/src/Domain/UseCase/EditSpecies/Input.php +++ b/src/Domain/UseCase/EditSpecies/Input.php @@ -4,6 +4,8 @@ namespace Domain\UseCase\EditSpecies; +use Domain\ReadModel\Species; + final class Input { public function __construct( @@ -13,4 +15,14 @@ public function __construct( public readonly string $feeding ) { } + + public static function fromReadModel(Species $species): self + { + return new self( + speciesId: (string) $species->getId(), + name: $species->getName(), + habitats: $species->getHabitats(), + feeding: $species->getFeeding() + ); + } } diff --git a/src/Infrastructure/Symfony/Messenger/QueryBus.php b/src/Infrastructure/Symfony/Messenger/QueryBus.php new file mode 100644 index 0000000..d93770e --- /dev/null +++ b/src/Infrastructure/Symfony/Messenger/QueryBus.php @@ -0,0 +1,33 @@ +handle($input); + } catch (HandlerFailedException $handlerFailedException) { + $nestedExceptions = $handlerFailedException->getNestedExceptions(); + + if (false === $nested = current($nestedExceptions)) { + throw $handlerFailedException; + } + + throw $nested; + } + } +}