Comment gérer les erreurs de post-validation dans la commande (DDD + CQRS)

18

Par exemple, lorsque vous soumettez un formulaire d'inscription, vous devez vérifier Domain Model( WriteModelen CQRS) qu'il est dans un état valide (exemple, syntaxe de l'adresse e-mail, âge, etc.).

Ensuite, vous créez un Commandet l'envoyez à un Command Bus.

Je comprends que les commandes ne doivent rien renvoyer.

Alors, comment gérez-vous une erreur au Command Bus- delà ? (Par exemple, un utilisateur s'est enregistré 1 seconde avant avec le même username/email).

Comment savez-vous que cette commande a échoué et comment connaissez-vous l'erreur?

JorgeeFG
la source
2
Vous n'avez pas besoin d'un bus d'événement. Pourquoi tout le monde pense que vous avez besoin d'un bus d'événements lors de la mise en œuvre du CQRS, je ne sais pas. CQRS signifie simplement que vous séparez complètement les écritures et les lectures, faites les préoccupations séparément. Vous avez CQRS aussi longtemps que vous le faites. Vos commandes n'ont même pas besoin d'être asynchrones. Si les commandes synchrones avec rapport d'erreur fonctionnent pour vous, faites-le.
Andy
1
Les commandes ne doivent rien retourner en cas de succès . L'idée est née de CQS , ce qui impliquait que la commande pouvait toujours lever une exception en cas d'erreur. Ce que vous décrivez est une commande à sens unique. Voir cette réponse par rapport à cela.
Kasey Speakman

Réponses:

4

Je comprends que les commandes ne doivent rien renvoyer.

C'est une vue, mais elle n'est pas entièrement gravée dans la pierre. Considérez les écritures (PUT, POST, DELETE) dans HTTP - tous ces messages sont des commandes, dans le sens où ce sont des messages avec demande que l'état de la ressource change, et pourtant ils renvoient tous des réponses.

Alors, comment gérez-vous une erreur au-delà du bus de commande? (Par exemple, un utilisateur s'est enregistré 1 seconde avant avec le même nom d'utilisateur / e-mail).

Comment savez-vous que cette commande a échoué et comment connaissez-vous l'erreur?

Ainsi, dans le cas où vous communiquez directement avec le gestionnaire de commandes, un message renvoyé est un moyen parfaitement raisonnable de reconnaître que la commande a été reçue et traitée.

Si vous utilisez un middleware, comme un bus, qui vous empêche de communiquer directement avec la cible, je vous suggère de rechercher des modèles de messagerie asynchrones - comment obtenir le gestionnaire de commandes pour renvoyer un message au votre interlocuteur?

Une idée est de souscrire au résultat de la commande; cela emprunte à certaines des idées des modèles d'intégration d'entreprise de Hohpe. L'idée de base est que, étant donné que le client connaît le message de commande qui a été envoyé, il est bien placé pour s'abonner à tout nouveau message publié à la suite du message de commande. Le gestionnaire de commandes, après avoir enregistré les données dans le livre d'enregistrement, publie les événements annonçant que le changement a réussi, et le client s'abonne à ces événements - reconnaissant les événements corrects en considérant la coïncidence de divers identifiants dans le message (identifiant de causalité, identifiant de corrélation , etc.).

Les approches alternatives sont un peu plus directes. L'une consisterait à inclure dans le message un rappel, qui peut être invoqué par le gestionnaire de commandes une fois le message traité avec succès.

Une alternative très similaire consiste à réserver de l'espace dans le message de commande pour que le gestionnaire de commandes écrive l'accusé de réception - puisque le client a déjà le message de commande en question, le circuit est déjà terminé. Pensez " promesse " ou " futur complétable". Le message indique au gestionnaire de commandes où écrire l'accusé de réception; cela signale au client (verrou de compte à rebours) que l'accusé de réception est disponible.

Et bien sûr, vous avez la possibilité supplémentaire de supprimer le middleware qui semble gêner simplement la bonne chose.

Par exemple, un utilisateur s'est enregistré 1 seconde avant avec le même nom d'utilisateur / e-mail

Si vous gérez l'enregistrement des utilisateurs de manière idempotante, ce ne serait pas nécessairement une erreur - la répétition des messages jusqu'à ce qu'une réponse soit observée est un moyen courant d'assurer au moins une fois la livraison.

VoiceOfUnreason
la source
2

Par exemple, lorsque vous soumettez un formulaire d'inscription, vous devez vérifier dans le modèle de domaine (WriteModel dans CQRS) qu'il est dans un état valide (exemple, syntaxe de l'adresse e-mail, âge, etc.)

Il existe de nombreux types de validation. La validation lorsque vous vérifiez la syntaxe de l'adresse e-mail et le format d'âge est un type de validation qu'une commande peut effectuer. Ce n'est pas vraiment une préoccupation de domaine. Cela peut sembler ainsi parce que certains experts du domaine vous diraient ces spécifications, mais vous devriez faire ce type de validation lors de la création de la commande. En fait, l'idée générale est de faire la validation le plus tôt possible car après avoir créé une commande et l'envoyer à un BUS, il est plus compliqué de prendre des mesures.

Alors, comment gérez-vous une erreur au-delà du bus de commande? (Par exemple, un utilisateur s'est enregistré 1 seconde avant avec le même nom d'utilisateur / e-mail).

Ce type de validation est très discuté dans la communauté CQRS, depuis le début du CQRS. Où le faire? C'est très débattu. J'utilise personnellement l'approche suivante: Avant d'envoyer la commande au BUS, je marque le nom d'utilisateur / e-mail comme pris de manière centralisée (c'est-à-dire une contrainte d'indexation unique sur le nom d'utilisateur / e-mail). Après cela, j'envoie la commande. L'inconvénient est que, si la commande échoue, pendant un court laps de temps ce nom d'utilisateur est pris et non utilisé; cela est acceptable pour mon entreprise, probable pour votre entreprise.

Comment savez-vous que cette commande a échoué et comment connaissez-vous l'erreur?

Si une commande asynchrone est rejetée par l'agrégat en raison d'un invariant de domaine, certaines mesures doivent être prises en émettant une commande compensatoire qui avertit en quelque sorte l'émetteur de la commande (c'est-à-dire envoyer un e-mail expliquant que la commande a échoué).

Le problème avec les e-mails en double est que vous ne pouvez pas envoyer un e-mail car cette adresse e-mail appartient à une autre personne, c'est pourquoi je la vérifie avant d'envoyer la commande au bus.

Constantin Galbenu
la source
1

La validation doit être effectuée chez un décorateur. Ensuite, toute commande nécessitant une validation peut être décorée comme telle.

Les validations peuvent être traitées avec des exceptions si vous retournez void avec votre commande afin qu'elles puissent être récupérées avec des appels de synchronisation ou asynchrones avec le résultat de la tâche retournée.

Une autre possibilité est de considérer la validation comme un type de "requête" qui retournerait un résultat de validation. Exécutez la requête de validation, puis si elle est réussie, exécutez la commande. Ce serait une alternative à l'approche décorative.

Jon Raynor
la source
J'aime l'approche d'exception car c'est la manière la plus propre. Mais les exceptions ne sont-elles pas trop lourdes pour cela?
EresDev
1
HI @EresDev - Oui, ils sont lourds. Mais, je ne sais pas à quel point vos données sont "valides". Disons que sur 1000 enregistrements 2 ne sont pas valides. Les exceptions pourraient être viables car ce sont des conditions vraiment exceptionnelles. Considérez maintenant 1000 enregistrements avec 200 non valides. Dans cette cause, oui, les exceptions doivent être évitées car 20% de vos données ne sont pas valides. Je vous laisse donc décider de choisir ou non cette approche en fonction de la validité de vos données actuelles. :)
Jon Raynor
J'ajouterai à cela en disant que si nous gardons l'exemple du formulaire d'inscription, où je pense que 50% ou plus d'utilisateurs insèrent d'abord des données invalides, les exceptions sont toujours correctes. Dans le backend d'application web, vous obtenez principalement des données valides car les frontends effectuent également la validation. Vous n'obtenez que si quelque chose a raté la validation frontale par une chance rare ou si quelqu'un s'amuse. Ainsi, les exceptions sont très bien dans ce cas.
EresDev