Comment une API REST convient-elle à un domaine basé sur des commandes / actions?

24

Dans cet article, l'auteur affirme que

Parfois, il est nécessaire d'exposer une opération dans l'API qui est intrinsèquement non RESTful.

et cela

Si une API a trop d'actions, cela indique que soit elle a été conçue avec un point de vue RPC plutôt que d'utiliser les principes RESTful, soit que l'API en question est naturellement mieux adaptée à un modèle de type RPC.

Cela reflète aussi ce que j'ai lu et entendu ailleurs.

Cependant, je trouve cela assez déroutant et j'aimerais mieux comprendre la question.

Exemple I: arrêt d'une machine virtuelle via une interface REST

Il y a, je pense, deux façons fondamentalement différentes de modéliser l'arrêt d'une machine virtuelle. Chaque voie peut avoir quelques variations, mais concentrons-nous sur les différences les plus fondamentales pour l'instant.

1. Corrigez la propriété d'état de la ressource

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(Alternativement, PUTsur la sous-ressource /api/virtualmachines/42/state.)

La machine virtuelle s'arrêtera en arrière-plan et, à un moment ultérieur, selon que l'arrêt se terminera ou non, l'état peut être mis à jour en interne avec «mise hors tension».

2. PUT ou POST sur la propriété actions de la ressource

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

Le résultat est exactement le même que dans le premier exemple. L'état sera mis à jour pour "arrêter" immédiatement et peut-être éventuellement pour "s'éteindre".

Les deux modèles sont-ils RESTful?

Quel design est le meilleur?

Exemple II: CQRS

Que se passe-t-il si nous avons un domaine CQRS avec de nombreuses "actions" (commandes aka) qui pourraient potentiellement conduire à des mises à jour de plusieurs agrégats ou ne peuvent pas être mappées aux opérations CRUD sur des ressources et sous-ressources concrètes?

Devrions-nous essayer de modéliser autant de commandes que le béton crée ou met à jour sur des ressources concrètes, dans la mesure du possible (en suivant la première approche de l'exemple I) et utiliser des «points de terminaison d'action» pour le reste?

Ou devrions-nous mapper toutes les commandes aux points de terminaison d'action (comme dans la deuxième approche de l'exemple I)?

Où devrions-nous tracer la ligne? Quand le design devient-il moins RESTful?

Un modèle CQRS est-il mieux adapté à une API de type RPC?

Selon le texte cité ci-dessus, c'est, si je comprends bien.

Comme vous pouvez le voir dans mes nombreuses questions, je suis un peu confus à ce sujet. Pouvez-vous m'aider à mieux le comprendre?

leifbattermann
la source
"créer une action" ne semble pas RESTful, sauf si l'action exécutée a son propre identifiant de ressource par la suite. Sinon, changer la propriété "state" via PATCH ou PUT est plus logique. Pour la partie CQRS, je n'ai pas encore de bonne réponse.
Fabian Schmengler
3
@Laiv Rien de mal à cela. C'est une question académique, j'aimerais mieux comprendre la question.
leifbattermann

Réponses:

19

Dans le premier cas (arrêt des machines virtuelles), je n'envisagerais aucune des alternatives OP RESTful. Certes, si vous utilisez le modèle de maturité Richardson comme critère, ce sont tous les deux des API de niveau 2 car ils utilisent des ressources et des verbes.

Aucun d'eux, cependant, n'utilise de contrôles hypermédia, et à mon avis, c'est le seul type de REST qui différencie la conception de l'API RESTful de RPC. En d'autres termes, respectez les niveaux 1 et 2 et vous aurez une API de style RPC dans la plupart des cas.

Afin de modéliser deux façons différentes d'arrêter une machine virtuelle, j'exposerais la machine virtuelle elle-même comme une ressource qui (entre autres choses) contient des liens:

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

Si un client souhaite arrêter la Ploehmachine virtuelle, il doit suivre le lien avec le shut-downtype de relation. (Normalement, comme indiqué dans le livre de recettes des services Web RESTful , vous utiliseriez un IRI ou un schéma d'identification plus élaboré pour les types de relations, mais j'ai choisi de garder l'exemple aussi simple que possible.)

Dans ce cas, il y a peu d'autres informations à fournir avec l'action, donc le client doit simplement faire un POST vide par rapport à l'URL dans le href:

POST /vms/1234/fdaIX HTTP/1.1

(Comme cette demande n'a pas de corps, il serait tentant de la modéliser comme une demande GET, mais les demandes GET ne devraient pas avoir d'effets secondaires observables, donc POST est plus correct.)

De même, si un client souhaite éteindre la machine virtuelle, il suivra le power-offlien à la place.

En d'autres termes, les types de relations des liens fournissent des opportunités qui indiquent l'intention. Chaque type de relation a une signification sémantique spécifique. C'est la raison pour laquelle nous parlons parfois du web sémantique .

Afin de garder l'exemple aussi clair que possible, j'ai intentionnellement masqué les URL de chaque lien. Lorsque le serveur d' hébergement reçoit la requête entrante, il sauriez que des fdaIXmoyens fermés , et des CHTY91moyens hors tension .

Normalement, je coderais simplement l'action dans l'URL elle-même, afin que les URL soient /vms/1234/shut-downet /vms/1234/power-off, mais lors de l'enseignement, cela brouille la distinction entre les types de relation (sémantique) et les URL (détails d'implémentation).

Selon les clients que vous possédez, vous pouvez envisager de rendre les URL RESTful non piratables .

CQRS

En ce qui concerne le CQRS, l'une des rares choses sur lesquelles Greg Young et Udi Dahan sont d'accord est que le CQRS n'est pas une architecture de haut niveau . Ainsi, je serais prudent de faire une API RESTful trop semblable à CQRS, car cela signifierait que les clients font partie de votre architecture.

Souvent, la force motrice d'une véritable API RESTful (niveau 3) est que vous voulez pouvoir faire évoluer votre API sans casser les clients et sans avoir le contrôle des clients. Si c'est votre motivation, alors le CQRS ne serait pas mon premier choix.

Mark Seemann
la source
Voulez-vous dire que les premiers exemples ne sont pas tous deux RESTful car ils n'utilisent pas de contrôles hypermédia? Mais je n'ai même pas posté de réponses, seulement les URL et les corps des requêtes.
leifbattermann
4
@leifbattermann Ils ne sont pas RESTful car ils utilisent le corps du message pour communiquer l'intention; c'est clairement RPC. Si vous avez utilisé des liens pour arriver à ces ressources, alors pourquoi auriez-vous besoin de communiquer votre intention à travers le corps?
Mark Seemann
Ça a du sens. Pourquoi proposez-vous un POST? L'action n'est-elle pas idempotente? Dans tous les cas, comment dites-vous à votre client quelle méthode HTTP utiliser?
leifbattermann
2
@ guillaume31 DELETEme semble étrange car après l'arrêt du vm, il existera toujours, uniquement en état "power off" (ou sth. comme ça).
leifbattermann
1
La ressource n'a pas à refléter une machine virtuelle de manière temporaire, elle peut en représenter une instance d'exécution.
guillaume31
6

Arrêter une machine virtuelle via une interface REST

Il s'agit en fait d'un exemple quelque peu célèbre, présenté par Tim Bray en 2009 .

Roy Fielding, discutant du problème, a partagé cette observation :

Personnellement, je préfère les systèmes qui traitent l'état surveillé (comme l'état de l'alimentation) comme non modifiable.

En bref, vous disposez d'une ressource d'informations qui renvoie une représentation actuelle de l'état surveillé; cette représentation peut inclure un lien hypermédia vers un formulaire qui demande une modification de cet état, et le formulaire a un autre lien vers une ressource pour gérer (chaque) demande de modification.

Seth Ladd avait les idées clés du problème

Nous avons transformé Running d'un simple état de personne à un vrai nom qui peut être créé, mis à jour et commenté.

Reprenant cela pour redémarrer les machines. Je dirais que vous POSTERIEZ à / vdc / 434 / cluster / 4894 / server / 4343 / reboots Une fois que vous avez publié, vous avez un URI qui représente ce redémarrage, et vous pouvez l'obtenir pour les mises à jour de statut. Grâce à la magie de l'hyperlien, la représentation du redémarrage est liée au serveur qui est redémarré.

Je pense que frapper l'espace URI est bon marché, et les URI sont encore moins chers. Créez une collection d'activités, modélisées sous forme de noms, et affichez, publiez et supprimez!

La programmation RESTful est la bureaucratie de Vogon à l'échelle du Web. Comment faites-vous quoi que ce soit RESTful? Inventez-lui de nouveaux documents et numérisez-les.

Dans un langage un peu plus sophistiqué, vous définissez le protocole d'application de domaine pour «arrêter une machine virtuelle» et identifiez les ressources dont vous avez besoin pour exposer / implémenter ce protocole.

Regarder vos propres exemples

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

C'est bon; vous ne traitez pas vraiment la demande elle-même comme une ressource d'informations distincte, mais vous pouvez toujours la gérer.

Vous avez manqué un peu dans votre représentation du changement.

Avec PATCH, cependant, l'entité incluse contient un ensemble d'instructions décrivant comment une ressource résidant actuellement sur le serveur d'origine doit être modifiée pour produire une nouvelle version.

Par exemple, les instructions de formatage du type de support JSON Patch comme si vous modifiiez directement un document JSON

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

Dans votre alternative, l'idée est proche, mais pas évidemment correcte. PUTest un remplacement complet de l'état de la ressource à l'URL cible , donc vous ne choisiriez probablement pas une orthographe qui ressemble à une collection comme cible d'une représentation d'une seule entité.

POST /api/virtualmachines/42/actions

Est cohérent avec la fiction selon laquelle nous ajoutons une action à une file d'attente

PUT /api/virtualmachines/42/latestAction

Est conforme à la fiction selon laquelle nous effectuons une mise à jour de l'élément de queue dans la file d'attente; c'est un peu bizarre de le faire de cette façon. Le principe de la moindre surprise recommande de donner à chaque PUT son propre identifiant unique, plutôt que de les mettre tous au même endroit et de modifier plusieurs ressources en même temps.

Notez que, dans la mesure où nous discutons de l'orthographe de l'URI - REST s'en fiche; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8best un URI parfaitement cromulent en ce qui concerne REST. La lisibilité, comme pour les noms de variables, est une préoccupation distincte. L'utilisation d'orthographes conformes à la RFC 3986 rendra les gens beaucoup plus heureux.

CQRS

Que se passe-t-il si nous avons un domaine CQRS avec de nombreuses "actions" (commandes aka) qui pourraient potentiellement conduire à des mises à jour de plusieurs agrégats ou ne peuvent pas être mappées aux opérations CRUD sur des ressources et sous-ressources concrètes?

Greg Young sur CQRS

CQRS est un modèle très simple qui offre de nombreuses possibilités d'architecture qui autrement n'existeraient pas. Le CQRS n'est pas une cohérence éventuelle, ce n'est pas un événement, ce n'est pas de la messagerie, il n'a pas de modèles séparés pour la lecture et l'écriture, ni il n'utilise la recherche d'événements.

Lorsque la plupart des gens parlent de CQRS, ils parlent vraiment d'appliquer le modèle CQRS à l'objet qui représente la limite de service de l'application.

Étant donné que vous parlez de CQRS dans le contexte de HTTP / REST, il semble raisonnable de supposer que vous travaillez dans ce dernier contexte, alors allons-y.

Étonnamment, celui-ci est encore plus facile que votre exemple précédent. La raison en est simple: les commandes sont des messages .

Jim Webber décrit HTTP comme le protocole d'application d'un bureau des années 50; le travail se fait en prenant des messages et en les mettant dans des boîtes de réception. La même idée tient - nous obtenons une copie vierge d'un formulaire, le remplissons avec les détails que nous connaissons, le livrons. Ta da

Devrions-nous essayer de modéliser autant de commandes que le béton crée ou met à jour sur des ressources concrètes, dans la mesure du possible (en suivant la première approche de l'exemple I) et utiliser des «points de terminaison d'action» pour le reste?

Oui, dans la mesure où les «ressources concrètes» sont des messages, plutôt que des entités dans le modèle de domaine.

Idée clé: votre API REST est toujours une interface ; vous devriez pouvoir changer le modèle sous-jacent sans que les clients n'aient besoin de changer les messages. Lorsque vous publiez un nouveau modèle, vous publiez une nouvelle version de vos points de terminaison Web qui savent comment prendre votre protocole de domaine et l'appliquer au nouveau modèle.

Un modèle CQRS est-il mieux adapté à une API de type RPC?

Pas vraiment - en particulier, les caches Web sont un excellent exemple d'un «modèle de lecture finalement cohérent». Rendre chacune de vos vues adressable indépendamment, chacune avec ses propres règles de mise en cache, vous donne un tas de mise à l'échelle gratuitement. Il y a relativement peu d'intérêt pour une approche exclusivement RPC des lectures.

Pour les écritures, c'est une question plus délicate: envoyer toutes les commandes à un seul gestionnaire à un seul point de terminaison, ou à une seule famille de points de terminaison, est certainement plus facile . REST est vraiment plus sur la façon dont vous trouvez communiquer où le point de terminaison est au client.

Le traitement d'un message comme sa propre ressource unique présente l'avantage que vous pouvez utiliser PUT, alertant les composants intermédiaires du fait que la gestion du message est idempotente, afin qu'ils puissent participer dans certains cas de gestion des erreurs, c'est une bonne chose d'avoir . (Remarque: du point de vue des clients, si les ressources ont des URI différents, alors ce sont des ressources différentes; le fait qu'ils peuvent tous avoir le même code de gestionnaire de requêtes sur le serveur d'origine est un détail d'implémentation caché par l'uniforme interface).

Fielding (2008)

Je dois également noter que ce qui précède n'est pas encore pleinement RESTful, du moins comment j'utilise le terme. Tout ce que j'ai fait est décrit les interfaces de service, qui ne sont pas plus que n'importe quel RPC. Afin de le rendre RESTful, j'aurais besoin d'ajouter un hypertexte pour introduire et définir le service, décrire comment effectuer le mappage à l'aide de formulaires et / ou de modèles de lien, et fournir du code pour combiner les visualisations de manière utile.

VoiceOfUnreason
la source
2

Vous pouvez utiliser 5 niveaux de type de support pour spécifier la commande dans le champ d'en-tête de type de contenu de la demande.

Dans l'exemple VM, ce serait quelque chose dans ce sens

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

alors

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

Ou

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

Voir https://www.infoq.com/articles/rest-api-on-cqrs

guillaume31
la source
Soyez avisé, 5LMT était une solution proposée et n'est pas prise en charge par les normes . J'ai déjà rencontré l' article du CQRS et j'en ai beaucoup appris.
Peter L
1

L'exemple de l'article lié est fondé sur l'idée que le démarrage et l'arrêt de la machine doivent être dirigés par des commandes plutôt que par des changements dans l'état des ressources modélisées. Ce dernier est à peu près ce que REST vit et respire. Une meilleure modélisation de la machine virtuelle nécessite de voir comment fonctionne son homologue du monde réel et comment vous, en tant qu'humain, interagiriez avec elle. C'est de longue haleine, mais je pense que cela donne un bon aperçu du type de réflexion requis pour faire une bonne modélisation.

Il existe deux façons de contrôler l'état d'alimentation de l'ordinateur sur mon bureau:

  • Interrupteur d'alimentation: coupe immédiatement le flux d'électricité vers l'alimentation, ce qui arrête tout l'ordinateur brusquement et de manière désordonnée.
  • Bouton marche / arrêt: Indique au matériel d'aviser le logiciel que quelque chose à l'extérieur veut que tout soit arrêté. Le logiciel effectue un arrêt ordonné, informe le matériel qu'il est fait et le matériel signale à l'alimentation qu'il peut passer en état de veille. Si l'interrupteur d'alimentation est activé, la machine fonctionne et le logiciel est dans un état où il ne peut pas répondre au signal d'arrêt, le système ne s'arrêtera pas sauf si j'arrête l'interrupteur d'alimentation. (Une machine virtuelle se comportera exactement de la même manière; si le signal d'arrêt est ignoré par le logiciel, la machine continuera de fonctionner, je dois la forcer à s'éteindre.) Si je veux pouvoir redémarrer la machine, je dois rallumez l'interrupteur d'alimentation, puis appuyez sur le bouton marche / arrêt. (De nombreux ordinateurs ont la possibilité d'utiliser une pression longue sur le bouton d'alimentation pour passer directement à l'état de veille, mais ce modèle n'a pas vraiment besoin de cela.) Ce bouton peut être traité comme un interrupteur à bascule, car en appuyant dessus, il en résulte un comportement différent selon l'état où il est enfoncé. Si l'interrupteur d'alimentation est éteint, appuyer sur ce bouton ne fait absolument rien.

Pour une machine virtuelle, les deux peuvent être modélisés en tant que valeurs booléennes en lecture / écriture:

  • power- Lorsqu'il est changé en true, rien ne se passe, sauf une note indiquant que le commutateur a été placé dans cet état. Lorsqu'elle est modifiée en false, la machine virtuelle est commandée dans un état de mise hors tension immédiate. Par souci d'exhaustivité, si la valeur reste inchangée après une écriture, rien ne se produit.

  • onoff- Lorsqu'il est changé en true, rien ne se passe si powerc'est le cas false, sinon la VM est commandée pour démarrer. Le changement à l' false, si rien ne se passe powerest false, sinon, la machine virtuelle est commandée de notifier le logiciel pour faire un arrêt ordonné, ce qu'il fera et informer la machine virtuelle qu'il peut aller dans la mise hors tension état. Encore une fois, pour être complet, une écriture sans changement ne fait rien.

Avec tout cela vient la réalisation qu'il y a une situation où l'état de la machine ne reflète pas l'état des commutateurs, et c'est pendant l'arrêt. powersera toujours trueet onoffsera false, mais le processeur exécute toujours son arrêt, et pour cela nous devons ajouter une autre ressource afin que nous puissions dire ce que la machine fait réellement:

  • running- Une valeur en lecture seule qui est truelorsque la machine virtuelle est en cours d'exécution et falsequand elle ne l'est pas, déterminée en demandant à l'hyperviseur son état.

Le résultat est que si vous voulez qu'une VM démarre, vous devez vous assurer que les ressources poweret onoffont été définies true. (Vous pouvez permettre que l' powerétape soit ignorée en la réinitialisant automatiquement, de sorte que si elle est définie sur false, elle devienne une truefois que la machine virtuelle a été définitivement arrêtée. Que ce soit une chose RESTly-pure à faire est un aliment pour une autre discussion.) Si vous voulez faire un arrêt ordonné, vous définissez onoffà falseet revenir plus tard pour voir si la machine effectivement arrêté, la mise powerà falsesi elle ne l'a pas.

Comme dans le monde réel, vous avez toujours le problème d'être invité à démarrer la machine virtuelle une fois qu'elle a été onoffchangée en falsemais c'est toujours runningparce qu'elle est au milieu de l'arrêt. La façon dont vous gérez cela est une décision politique.

Blrfl
la source
0

Les deux modèles sont-ils RESTful?

Donc, si vous voulez réfléchir tranquillement, oubliez les commandes. Le client ne dit pas au serveur d'arrêter la machine virtuelle. Le client "ferme dow" (métaphoriquement) sa copie de la représentation de la ressource en mettant à jour son état puis en remettant cette représentation sur le serveur. Le serveur accepte cette nouvelle représentation d'état et, comme effet secondaire, arrête la machine virtuelle. L'effet secondaire est laissé au serveur.

C'est moins de

Hé serveur, client ici, ça te dérangerait d'arrêter la VM

et plus de

Hé serveur, client ici, j'ai mis à jour l'état de la ressource VM 42 dans l'état d'arrêt, mettez à jour votre copie de cette ressource et faites ce que vous pensez être approprié

En tant qu'effet secondaire du serveur acceptant ce nouvel état, il peut vérifier les actions qu'il doit réellement effectuer (telles que l'arrêt physique de VM 42), mais cela est transparent pour le client. Le client n'est pas concerné par les actions que le serveur doit entreprendre pour devenir cohérent avec ce nouvel état

Oubliez donc les commandes. Les seules commandes sont les verbes en HTTP pour le transfert d'état. Le client ne sait pas, ni se soucie, comment le serveur va mettre la machine virtuelle physique dans l'état d'arrêt. Le client n'émet pas de commandes au serveur pour y parvenir, il dit simplement c'est le nouvel état, comprenez-le .

La puissance de ceci est qu'il dissocie le client du serveur en termes de contrôle de flux. Si plus tard le serveur change la façon dont il fonctionne avec les VM, le client s'en fiche. Il n'y a aucun point de terminaison de commande à mettre à jour. Dans RPC si vous modifiez le point de terminaison API deshutdown à shut-downvous avez interrompu tous vos clients car ils ne connaissent plus la commande à appeler sur le serveur.

REST est similaire à la programmation déclarative, où au lieu de lister un ensemble d'instructions pour changer quelque chose, vous indiquez simplement comment vous voulez qu'il soit et laissez l'environnement de programmation le comprendre.

Cormac Mulhall
la source
Merci pour votre réponse. La deuxième partie sur le découplage client / serveur correspond très bien à ma propre compréhension. Avez-vous une ressource / un lien qui sauvegarde la première partie de votre réponse? Exactement quelle contrainte REST est rompue si j'utilise des ressources, des méthodes HTTP, de l'hypermédia, des messages auto-descriptifs, etc.?
leifbattermann
Il n'y a aucun problème avec l'utilisation des ressources, des méthodes HTTP, etc. HTTP est un protocole RESTful après tout. Là où il y a un problème, utilisez ce que vous appelez des "points de terminaison d'action". Dans REST, il existe des ressources, qui représentent des concepts ou des choses (comme la machine virtuelle 42 ou mon compte bancaire) et les verbes HTTP utilisés pour transférer l'état de ces ressources entre les clients et les serveurs. C'est ça. Ce que vous ne devriez pas faire, c'est essayer de créer de nouvelles commandes en combinant des points de terminaison non liés aux ressources avec des verbes HTTP. Ainsi, 'virtualmachines / 42 / actions' n'est pas une ressource et ne devrait pas exister dans un système RESTful.
Cormac Mulhall
Ou pour le dire autrement, le client ne devrait pas essayer d'exécuter des commandes sur le serveur (au-delà des verbes HTTP limités concernés uniquement par le transfert d'état des ressources). Le client doit mettre à jour sa copie de la ressource et simplement demander au serveur d'accepter ce nouvel état. L'acceptation de ce nouvel état peut avoir des effets secondaires (la VM 42 est physiquement arrêtée) mais cela échappe au souci du client. Si le client n'essaie pas d'exécuter des commandes sur le serveur, il n'y a pas de couplage via ces commandes entre le client et le serveur.
Cormac Mulhall
Vous pouvez exécuter la commande sur la ressource ... Comment feriez-vous disons "déposer" et "retirer" sur un compte bancaire? Ce serait utiliser CRUD pour quelque chose qui n'est pas CRUD.
Konrad
Il vaut mieux l'utiliser POST /api/virtualmachines/42/shutdownau lieu d'avoir un "effet secondaire". L'API doit être compréhensible pour l'utilisateur, comment savoir si, par exemple DELETE /api/virtualmachines/42, la machine virtuelle sera arrêtée? Un effet secondaire pour moi est un bug, nous devons concevoir nos API pour qu'elles soient compréhensibles et auto-descriptives
Konrad