Slide 1

Slide 1 text

Éviter un contrôleur de 1000 lignes: Vite, un bus de commande ! SymfonyLive Paris 2018

Slide 2

Slide 2 text

Romaric Drigon Software engineer @ netinfluence, Suisse

Slide 3

Slide 3 text

Ce que l'on souhaite éviter

Slide 4

Slide 4 text

Notre objectif un code simple à comprendre avoir des objets simples à tester unitairement ne pas se répéter utiliser une architecture connue, plutôt que d'en inventer une : plus simple à expliquer, à communiquer autour bénéficier de retours d'expérience

Slide 5

Slide 5 text

Quelle serait une belle architecture ?

Slide 6

Slide 6 text

La solution : un bus de commande Une commande... "...an object is used to encapsulate all information needed to perform an action..." ...et un bus "...a communication system that transfers data between components..."

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Un cas d'utilisation Un explorateur de fichier en JS. Sur chaque fichier, des actions sont possibles: renommer, supprimer... Toutes les requêtes sont envoyées en AJAX à une route d'un backend Symfony.

Slide 9

Slide 9 text

Ce que le backend doit gérer l'utilisateur a-t-il le droit de faire cette action sur ce fichier? authentification permissions valider que la requête est valide l'exécuter logguer (audit) mettre à jour les quotas de l'utilisateur ...

Slide 10

Slide 10 text

C'est l'embouteillage !

Slide 11

Slide 11 text

Notre objectif

Slide 12

Slide 12 text

Mise en place Nous allons utiliser Tactician (https://tactician.thephpleague.com/) Développé par Ross Tuck. Un bundle est disponible. D'autres implémentations existent, comme SimpleBus, de Matthias Noback. Ou encore le nouveau composant Messenger de Symfony.

Slide 13

Slide 13 text

Une commande file = $file; } public function getFile(): File { return $this->file; } }

Slide 14

Slide 14 text

Le handler en charge getFile(); $this->entityManager->remove($file); $this->entityManager->flush(); return $file->getUuid(); // Un handler peut retourner une valeur } }

Slide 15

Slide 15 text

Déclaration du handler Déclaré en tant que services Symfony: Note: Les handlers peuvent maintenant être autowirés, même sans Symfony 3.3. app.remove_handler: class: AppBundle\Handler\RemoveHandler tags: - { name: tactician.handler, command: AppBundle\Model\Command\RemoveComman arguments: - '@doctrine.orm.default_entity_manager'

Slide 16

Slide 16 text

Envoi de la commande dans le bus Dans le contrôleur Symfony: Cool Romaric ... mais c'est quoi l'intérêt ? /** * @ParamConverter("file") */ public function apiAction(File $file) { $command = new RemoveCommand($file); $uuid = $this->get('tactician.commandbus')->handle($command); return new JsonResponse(['uuid' => $uuid]); }

Slide 17

Slide 17 text

Cela évolue très bien !

Slide 18

Slide 18 text

L'application grossit... Un mois plus tard, nous avions 25 commandes et handlers, pour un total de 3500 lignes de code.

Slide 19

Slide 19 text

But wait, there's more ! Les middlewares

Slide 20

Slide 20 text

Quelques exemples de middlewares Doctrine (https://tactician.thephpleague.com/plugins/doctrine/) : exécute le handlers au sein d'une seule transaction Logger (https://tactician.thephpleague.com/plugins/logger/) : loggue tout dans Monolog (-> audit !) Validation (fourni avec le bundle Symfony Tactician) Security, pour l'autorisation ...

Slide 21

Slide 21 text

Quelques autres avantages Test : les handlers et middlewares sont faciles à tester unitairement. Organisation du travail : Chaque fonctionnalité étant dans des classes séparées, le travail est plus simple à répartir entre développeurs. Il y a moins de merge conflicts. Extensions : On peut connecter le bus sur une message queue (RabbitMQ...), ou les exécuter plus tard (tâches planifiées...).

Slide 22

Slide 22 text

Quelques inconvénients Formation : Souvent le pattern n'est pas connu. Il faut l'expliquer à l'équipe. Le noter quelque part (README...). Construction des commandes : Il faut construire les objets dans le contrôleur, alors utiliser un service qui sera une Factory. Debug : Suivre ce qui se passe est plus difficile. Wrapper le CommandBus dans un qui appelle le profiler SF.

Slide 23

Slide 23 text

stopwatch->start($name, 'tactician'); $this->logger->debug(sprintf('Calling %s middleware with %s comman $return = $middleware->execute($command, $lastCallable); $this->stopwatch->stop($name); return $return; };

Slide 24

Slide 24 text

romaric@netinfluence.ch http://netinfluence.ch