DEV Community

Discussion on: Domain Driven Design avec PHP & Symfony

Collapse
 
spike31 profile image
Gilles Gauthier

Merci pour ton retour d'expérience !

Tu n'as pas parlé des events dispatchés dans un command handler, c'est volontaire ?

Par exemple moi j'aime bien dans un command handler "UploadImageHandler" pouvoir dispatcher un event "ImageUploaded" et avoir un subscriber qui écoute cet event et qui va créer un nouvel handler selon les besoins.

Exemple créer une notification pour informer qu'une image a été uploadée.

public function onImageUploaded(ImageUploaded $event) {
  $this->messenger->dispatch(new Notification(Type::Image));
}
Enter fullscreen mode Exit fullscreen mode

ça permets de ne pas avoir un EventBus qui puisse créer des "actions" mais toujours des commands handlers

C'est quelque chose que tu fais ?

:)

Collapse
 
ludofleury profile image
Ludovic Fleury • Edited

Merci Gilles pour ton commentaire (et ton boulot sur Lexik)

Je ne suis pas sur de bien comprendre le détail de ta question:

un command handler "UploadImageHandler" pouvoir dispatcher un event "ImageUploaded" et avoir un subscriber qui écoute cet event et qui va créer un nouvel handler selon les besoins.

Si je lis cette phrase j'en comprends la chronologie suivante:

  1. Dispatch command: UploadImage
  2. Handle command: UploadImageHandler
  3. Event: ImageUploaded
  4. Handle command: CropThumbnailHandler

Car je lis subscriber qui écoute cet event et qui va créer un nouvel handler ou encore pas avoir un EventBus qui puisse créer des "actions" mais toujours des commands handlers)

J'ai du mal à articuler cette solution, dans tes listeners/subscribers tu instancies la commande + directement le handler?

Il m'arrive d'implémenter des Events bien entendu, je n'en parle pas pour éviter les confusions entre Event domain et Event application.

Event domain

Pour être un peu plus technique: lorsqu'on fait de l'ES (event sourcing), on parle d'event domain. Ce sont des Events utilisés pour gérer le cycle de vie des concepts métiers. Prenons l'exemple de la mise à jour d'un email utilisateur. C'est un évènement du domaine qui a des conséquences en cascade souvent sur d'autres concepts du métier. Les changements d'état sur les entités, les AR's sont gérés via un stream d'events .

Note: il n'est pas obligatoire de faire de l'event sourcing pour dispatcher des events dans son domain. Mais c'est tout de même beaucoup plus rare.

Event applicatif

On se souvient que sur une implem DDD "classique" ou "hexagonale" , on identifie 3 couches: application, domain et infrastructure.

Les commandes / handlers appartiennent à la couche applicative. La couche qui manipule les concepts métiers (la couche domaine). Cette couche peut bien entendu émettre des évènements. Ce sont exactement ceux que tu mentionnes dans ton commentaire: un command handler qui dispatch un event. L'articulation est la suivante:

  1. Dispatch command: UploadImage
  2. Handle command: UploadImageHandler
  3. Dispatch Event: ImageUploaded

  4. Handle Event: ImageUploadedListener

  5. Dispatch command: CropPreview

  6. Handle command: CropPreviewHandler

  7. Dispatch event: PreviewCropped

A la lecture de ton exemple de code, je pense que c'est ce que tu as voulu dire (?)
Et j'essaie d'interpréter:

ne pas avoir un EventBus qui puisse créer des "actions" mais toujours des commands handlers

En "J'évite d'avoir des command handlers qui instancie/dispatch directement de nouvelles commandes, je découple la séquentialité des commandes grâces à des évènements." (?)

Collapse
 
spike31 profile image
Gilles Gauthier

C'est bien ça Ludovic ! (J'avoue que n'ai pas fais l'effort d'être clair en me relisant..!) Je parlais d'Event Applicatif.

Si je reprends ton point 4. ImageUploadedListener

Ce que je fais c'est que j'injecte le CommandBus dans le listener, pour pouvoir dispatcher la command CropPreview, et ainsi recommencer une nouvelle boucle

command -> handler -> event -> listener -> command -> handler ...

Et le tout est enveloppé, quand c'est possible, dans une transaction Mysql. Je dis quand c'est possible, car si j'ai une action tournée vers l'extérieure comme "envoyer un email", il n'y a pas de rollback/annulation possible.

C'est une mise en place que j'avais lu dans un article CQRS Journey il y a plusieurs années (rechercher sur la page "Approach 3: Using a process manager")

En suivant ce principe j'arrive à découpler mes handlers et à pouvoir "enchaîner" les commandes qui en découlent, et mon code est organisé de manière plus "lisible".

J'ai souvent vu des exemples de ce genre :

  1. Dispatch command: CreateUser
  2. Handle command: CreateUserHandler
  3. Dispatch Event: UserCreated
  4. Handle Event: UserListener
  5. Et dans le listener le code complet pour envoyer un email

Je trouve que c'est une erreur parce qu'on donne de la responsabilité à un listener, on lui donne de la logique métier, alors que (pour moi) il est juste là que pour "router" vers la prochaine command à exécuter. Comme un ProcessManager..

En tout cas j'ai hâte de lire tes prochains articles !

Thread Thread
 
ludofleury profile image
Ludovic Fleury

Super, merci pour ton retour, je vais essayer de compléter la réponse dans ce cas avec tes éléments.

Concernant les "events applicatif", du coup je suis tout à fait aligné avec ton implémentation.

c'est une erreur parce qu'on donne de la responsabilité à un listener, on lui donne de la logique métier

Ce point est très important et je soutiens l'approche. Les listeners ne doivent pas contenir de logique métier. Il y a plusieurs options pour éviter cela: soit le dispatch d'une autre commande (comme tu le fais) soit l'appel à un service applicatif autre qu'une commande (mais je trouve qu'on perd immédiatement en consistence "DX": plusieurs façon de manipuler la couche applicative).

En revanche:

Et le tout est enveloppé, quand c'est possible, dans une transaction Mysql

Là, je suis plus réservé. J'éviterai au maximum. Parce que selon les approches "tactiques" du DDD, comprendre les reco d'implémentation:

  • Les AR garantissent l'atomicité d'une opération
  • J'étends cela au niveau des commandes pour soulager le design des AR (concepts plutôt difficiles) et également éviter les SAGA

Mais, je délimite la transactionnalité d'une opération métier à 1 unique commande. Le plus difficile à maintenir dans une application, c'est surement l'intégrité des changements d'état, la diffusion de la transaction au travers de plusieurs commandes amène du risque, de la complexité de maintenance.

Comme tu peux le lire dans l'article, je fais également beaucoup de compromis ou d'économie dans mes implémentations (command handler parfois bof-bof, commande async). Et il n'y a pas de place à la pureté dans la réalité (des contextes de nos projets). Donc ce n'est pas "mauvais" et nous maitrisons nos risques dans ces décisions tech un peu "touchy", je me méfierai juste de banaliser des transactions "plus large".

Cette article est en venu spontanément en réaction à l'excellente conf de @lilobase . Je ne pensais pas rédiger plus, mais si tu as des sujets, cela peut m'inspirer?

Dernier point, dans la traduction anglaise de cet article j'ai une partie "Bonus Track" à la fin, qui parle de la synchronisation de BC's et évoque une mécanique events similaire.