web-dev-qa-db-fra.com

Comment une API REST convient-elle pour un domaine basé sur une commande / action?

Dans ce 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 comporte trop d'actions, cela indique que soit elle a été conçue avec un point de vue RPC plutôt que d'utiliser des 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 interface VM 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, PUT sur la sous-ressource /api/virtualmachines/42/state.)

Le VM s'arrêtera en arrière-plan et à un moment ultérieur, selon que l’arrêt réussit ou non, l’état peut être mis à jour en interne avec "power off".

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 "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, il 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?

25
leifbattermann

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é de Richardson comme critère, ils sont tous les deux leve 2 API car ils utilisent des ressources et des verbes .

Cependant, aucun d'eux n'utilise de contrôles hypermédia, et à mon avis, c'est le seul type de REST qui se différencie Conception d'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 le VM lui-même comme une ressource qui (entre autres) 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 machine virtuelle Ploeh, il doit suivre le lien avec le shut-down type de relation. (Normalement, comme indiqué dans le RESTful Web Services Cookbook , 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 contre l'URL dans le href:

POST /vms/1234/fdaIX HTTP/1.1

(Puisque cette requête n'a pas de corps, il serait tentant de la modéliser comme une requête GET, mais les requêtes 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-off lien à 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 de le 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 demande entrante, il sait que fdaIX signifie arrêtez et CHTY91 signifie éteindre.

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

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

CQRS

En ce qui concerne CQRS, l'une des rares choses sur lesquelles Greg Young et Udi Dahan sont d'accord est que 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, le moteur 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.

20
Mark Seemann

Arrêt d'une interface VM via une interface REST

Il s'agit en fait d'un exemple quelque peu célèbre, mis en avant 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 pour demander une modification de cet état, et le formulaire a un autre lien vers une ressource à gérer (chaque ) changer de requête.

Seth Ladd avait les informations clés sur le 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 devriez POST to/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 d'état. Grâce à la magie des hyperliens, 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é, ce que vous faites est de définir le protocole d'application de domaine pour "arrêter une machine virtuelle" et d'identifier les ressources que vous devez 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é jointe 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 format de 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. PUT est un remplacement complet de l'état de la ressource à l'URL cible , donc vous ne choisiriez probablement pas une orthographe qui ressemble comme une collection comme cible d'une représentation d'une entité unique .

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 cohérent avec 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 ne se soucie pas; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8b est un URI parfaitement cromulent en ce qui concerne REST est concerné. La lisibilité, comme pour les noms de variable, est une préoccupation distincte. L'utilisation d'orthographes cohérentes avec 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

Le 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 aient besoin pour modifier 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 a 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 à 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 Origin 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.

6
VoiceOfUnreason

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

2
guillaume31

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 du VM nécessite de voir comment fonctionne son homologue du monde réel et comment vous, en tant qu'humain, interagiriez avec lui. 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 le bloc d'alimentation, arrêtant soudainement l'ensemble de l'ordinateur.
  • Bouton On/Off: 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. (Un VM se comportera exactement de la même manière; si le signal d'arrêt est ignoré par le logiciel, la machine continuera à fonctionner, je dois la forcer à s'éteindre.) Si je veux pouvoir redémarrer la machine, je dois remettre l'interrupteur d'alimentation sous tension, puis appuyer 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, cela entraîne un comportement différent en fonction de l'état quand il est pressé. 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'il est changé en false, le VM est commandé 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 modifié en true, rien ne se passe si power est false, sinon le VM est commandé pour démarrer. Lorsqu'il est modifié en false, rien ne se passe si power est false, sinon, le VM est chargé d'informer le logiciel de procéder à un arrêt ordonné, ce qui il le fera et informera ensuite le VM qu'il peut passer à l'état hors tension. Encore une fois, pour être complet, une écriture sans changement ne fait rien.

Avec tout cela, on se rend compte 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. power sera toujours true et onoff sera false, mais le processeur exécute toujours son arrêt, et pour cela nous devons ajouter une autre ressource afin nous pouvons dire ce que fait réellement la machine:

  • running - Une valeur en lecture seule qui est true lorsque le VM est en cours d'exécution et false quand il ne l'est pas, déterminée en demandant à l'hyperviseur son Etat.

Le résultat est que si vous voulez qu'un VM démarre, vous devez vous assurer que les ressources power et onoff ont été définies true . (Vous pouvez permettre que l'étape power soit ignorée en la réinitialisant automatiquement, de sorte que si elle est définie sur false, elle devienne true après le VM a été vérifiée de façon vérifiable. Que ce soit une chose RESTly pure à faire, c'est de la matière pour une autre discussion.) Si vous voulez qu'elle s'arrête correctement, vous définissez onoff sur false et revenez plus tard pour voir si la machine s'est réellement arrêtée, définissez power sur false si ce n'est pas le cas.

Comme dans le monde réel, vous avez toujours le problème d'être invité à démarrer le VM après que onoff a été remplacé par false mais est toujours running car c'est au milieu de l'arrêt. La façon dont vous gérez cela est une décision politique.

1
Blrfl

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 remet cette représentation sur le serveur. Le serveur accepte cette nouvelle représentation d'état et, comme effet secondaire, arrête réellement 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

Hey 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é

Comme un effet secondaire du serveur acceptant ce nouvel état, il peut vérifier quelles actions il doit réellement effectuer (comme l'arrêt physique VM 42), mais cela est transparent pour le client. Le client est indifférent aux actions que le serveur doit entreprendre pour se mettre en cohérence 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 l'état physique VM dans l'état d'arrêt. Le client n'émet pas de commandes au serveur pour atteindre cela, c'est juste dire Ceci 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 de shutdown à shut-down vous avez cassé 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.

0
Cormac Mulhall