Sourcing d'événements et REST

17

Je suis tombé sur la conception Event Sourcing et j'aimerais l'utiliser dans une application où un client REST est nécessaire (RESTful pour être précis). Cependant, je ne parviens pas à les connecter ensemble car REST est assez similaire à CRUD et la recherche d'événements est basée sur les tâches. Je me demandais comment pouvez-vous concevoir la création de commandes basées sur des demandes au serveur REST. Considérez cet exemple:

Avec REST, vous pouvez mettre un nouvel état dans la ressource appelée Fichier. Dans une demande, vous pouvez envoyer un nouveau nom de fichier, vous pouvez changer le dossier parent et / ou changer le propriétaire du fichier et ainsi de suite.

Comment construire le serveur pour pouvoir utiliser le sourcing d'événements. Je pensais à ces possibilités:

  1. Déterminer le serveur quels champs ont été modifiés et de créer des commandes appropriées ( RenameFileCommand, MoveFileCommand, ChangeOwnerCommand, ...) et l' expédition de ces individuellement. Cependant, dans cette configuration, chacune des commandes peut échouer, laissant les autres hors de la transaction et donc hors du changement "atomique" de la ressource.

  2. Dispatch une seule commande ( UpdateFileCommand) et dans le gestionnaire de commande, plus précisément dans l'ensemble, déterminer quels champs ont été modifiés et envoyer des événements individuels au lieu ( FileRenamedEvent, FileMovedEvent, OwnerChangedEvent, ...)

  3. Celui-ci je n'aime pas du tout: dans la demande au serveur, je spécifierais dans les en-têtes la commande à utiliser, car l'interface utilisateur est toujours basée sur les tâches (mais la communication se fait via REST). Cependant, il échouera dans toute autre utilisation de la communication REST (par exemple dans des applications externes) car ils ne sont pas tenus de modifier uniquement le seul champ dans une demande. J'apporte également un assez grand couplage dans l'interface utilisateur, REST et ES.

Laquelle préférez-vous ou existe-t-il une meilleure façon de gérer cela?

Note latérale: application écrite en Java et Axon Framework pour le sourcing d'événements.

roux
la source
Certainement pas le 3ème. Je ferais le 1er, mais je dois y penser.
inf3rno
Vous vous demandez si une seule requête HTTP peut entraîner plusieurs commandes? Est-ce que je comprends bien? A mon humble avis. il devrait pouvoir le faire, mais je n'ai pas lu sur DDD depuis un certain temps, donc je dois vérifier un exemple de code sur la façon de l'implémenter.
inf3rno
J'ai trouvé un exemple, mais c'est CRUD. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/… Je vais demander à l'auteur quelle est son opinion, il en sait plus sur DDD que moi.
inf3rno
1
La mienne est que vous devez utiliser plusieurs commandes dans une "unité d'oeuvre" si vous voulez une cohérence immédiate. Si vous parlez de cohérence éventuelle, la question n'a pas de sens pour moi. Une autre solution possible pour envoyer un CompositeCommand qui peut contenir les commandes que vous souhaitez exécuter atomique. Il peut s'agir d'une simple collecte, la seule chose importante est que le bus puisse le gérer correctement.
inf3rno
1
Selon lui, vous devriez essayer d'établir une relation 1: 1 entre les commandes et les requêtes HTTP. Si vous ne pouvez pas le faire (c'est une mauvaise odeur), alors vous devriez utiliser du vrac (je l'ai appelé composite) pour le rendre atomique.
inf3rno

Réponses:

11

Je pense que vous pouvez avoir un processus utilisateur à la discordance d'implémentation ici.

Premièrement: un utilisateur voudra-t-il honnêtement effectuer plusieurs modifications simultanément sur un fichier? Un changement de nom (qui peut ou non inclure un changement de chemin?), Un changement de propriétaire et peut-être un changement de contenu de fichier (par souci d'argument) semblent être des actions distinctes.

Prenons le cas où la réponse est "oui" - vos utilisateurs veulent vraiment faire ces changements simultanément.

Dans ce cas, je vous recommande fortement contre toute mise en œuvre qui envoie plusieurs événements - RenameFileCommand, MoveFileCommand, ChangeOwnerCommand- pour représenter cette seule intention de l' utilisateur.

Pourquoi? Parce que les événements peuvent échouer. C'est peut-être extrêmement rare, mais votre utilisateur a soumis une opération qui semblait atomique - si un seul des événements en aval échoue, l'état de votre application n'est plus valide.

Vous invitez également les dangers de la course sur une ressource qui est clairement partagée entre chacun des gestionnaires d'événements. Vous devrez écrire "ChangeOwnerCommand" de telle manière que le nom de fichier et le chemin d'accès au fichier n'aient pas d'importance, car ils pourraient être obsolètes au moment de la réception de la commande.

Lors de la mise en œuvre d'un système reposant non piloté par des événements avec déplacement et renommage de fichiers, je préfère garantir la cohérence en utilisant quelque chose comme un système eTag - assurez-vous que la version de la ressource en cours de modification est la version que l'utilisateur a récupérée en dernier, et échoue si elle a été modifié depuis. Mais si vous envoyez plusieurs commandes pour cette opération à utilisateur unique, vous devrez incrémenter la version de votre ressource après chaque commande - vous n'avez donc aucun moyen de savoir que la ressource que l'utilisateur modifie est vraiment la même version que la ressource qu'il a lue en dernier. .

Ce que je veux dire par là, c'est que si quelqu'un d'autre effectue une autre opération sur le fichier presque en même temps. Les 6 commandes peuvent s'empiler dans n'importe quel ordre. Si nous n'avions que 2 commandes atomiques, la commande précédente pourrait réussir et la commande suivante pourrait échouer "la ressource a été modifiée depuis sa dernière récupération". Mais il n'y a aucune protection contre cela lorsque les commandes ne sont pas atomiques, donc la cohérence du système est violée.

Fait intéressant, il existe un mouvement vers quelque chose comme une architecture basée sur les événements dans REST, appelée "Rest without PUT", recommandée dans le radar de la technologie Thoughtworks, janvier 2015 . Il y a un blog considérablement plus long sur Rest without PUT ici .

Essentiellement, l'idée est que POST, PUT, DELETE et GET conviennent parfaitement aux petites applications, mais lorsque vous devez commencer à supposer que put, post et delete peuvent être interprétés à l'autre extrémité, vous introduisez le couplage. (par exemple "quand JE SUPPRIME la ressource associée à mon compte bancaire, le compte doit être fermé") Et la solution proposée est de traiter REST de manière plus sourcée par l'événement. ie permet de POSTER l'intention de l'utilisateur en tant que ressource d'événement unique.

L'autre cas est plus simple. Si vos utilisateurs ne veulent pas effectuer toutes ces opérations simultanément, ne les laissez pas. POSTER un événement pour chaque intention d'utilisateur. Vous pouvez maintenant utiliser le contrôle de version etag sur vos ressources.

Quant aux autres applications qui utilisent une API très différente de vos ressources. Ça sent le trouble. Pouvez-vous construire une façade de l'ancienne API au-dessus de votre API RESTful et les pointer vers la façade? c'est-à-dire exposer un service qui effectue plusieurs mises à jour d'un fichier en séquence via le serveur REST?

Si vous ne créez pas l'interface RESTful au-dessus de l'ancienne solution, ni ne construisez une façade de l'ancienne interface au-dessus de la solution REST, et essayez de maintenir les deux API pointant vers une ressource de données partagée, vous rencontrerez des maux de tête majeurs.

perfectionniste
la source
Je peux voir l'inadéquation, cependant, par REST en principe, il est possible de mettre à jour l'état de plusieurs champs en mettant le nouvel état à la ressource (par une certaine représentation). C'est ainsi que REST fonctionne par définition - transfert d'état représentatif, l'inconvénient est que l'intention de l'utilisateur est perdue dans le processus. De plus, REST est presque un standard pour les API externes. Je veux simplement concevoir l'application pour pouvoir utiliser les deux. La suppression de PUT est comme une solution de contournement à cause d'ES. D'après ce que je peux voir, une commande de mise à jour émettant plusieurs événements est réalisable tant que la gestion des commandes et des événements se fait en une seule transaction.
rousse
Etag est quelque chose que j'aimerais faire de toute façon. Votre lien vers un "article de blog plus long" est également le même que le premier lien.
rousse
J'y reviendrai après un certain examen avec plus de réflexions sur PUT. Certes, la suppression de PUT n'est pas seulement "une solution de contournement pour ES". J'ai corrigé le lien du blog.
perfectionniste
4

Tout à l'heure, je suis tombé sur l'article suivant, qui encourage à spécifier les noms de commande dans la demande au serveur dans l'en-tête Content-Type (tout en suivant 5 niveaux de type de support).

Dans l'article, ils mentionnent que le style RPC est mauvais pour REST et suggèrent d'étendre Content-Type pour spécifier le nom de la commande:

Une approche courante consiste à utiliser des ressources de style RPC, par exemple / api / InventoryItem / {id} / rename. Bien que cela supprime apparemment le besoin de verbes arbitraires, c'est contre la présentation orientée ressources de REST. Nous devons nous rappeler qu'une ressource est un substantif et que le verbe HTTP est le verbe / action et les messages auto-descriptifs (l'un des principes de REST) ​​sont le véhicule pour transmettre d'autres axes d'information et d'intention. En fait, la commande dans la charge utile du message HTTP devrait être suffisante pour exprimer toute action arbitraire. Cependant, s'appuyer sur le corps du message a ses propres problèmes, car le corps est généralement livré sous forme de flux et la mise en mémoire tampon du corps dans son intégralité avant d'identifier une action n'est pas toujours possible ni judicieuse.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

L'article est ici: http://www.infoq.com/articles/rest-api-on-cqrs

Vous pouvez en savoir plus sur 5 niveaux de type de média ici: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


Bien qu'ils exposent les événements de domaine à l'API REST, ce que je considérerais comme une mauvaise pratique, j'aime la solution car elle ne crée pas un nouveau "protocole" uniquement pour CQRS, que ce soit en envoyant des noms de commande dans le corps ou en extra et reste fidèle aux principes RESTful.

roux
la source