Point de terminaison REST pour afficher un aperçu avant le POST

17

Je conçois une nouvelle application Web qui est alimentée par un backend REST et un frontend HTML + JS.

Il y a une méthode POST pour changer une entité (appelons Config), qui a plusieurs effets secondaires dans l'état de nombreux éléments de l'application. Supposons que le POST soit exécuté de cette façon:

POST /api/config BODY {config: ....}

Pour cette raison, je voudrais afficher un aperçu avant que ces modifications ne soient apportées, pour que l'utilisateur final puisse voir ce qui va changer.

La première chose à laquelle j'ai pensé est de créer un point de terminaison GET pour l'aperçu, en envoyant le corps du nouvel état de l'entité. Par ici:

GET /api/preview/items BODY {config: ....}

Pourrait montrer le nouvel état des éléments avec la nouvelle configuration.

GET /api/preview/sales BODY {config: ....}

Pourrait montrer le nouvel état des ventes avec la nouvelle configuration.

Cela semble une bonne idée d'utiliser le verbe GET car je ne modifie pas l'état de l'application. Cependant, l'utilisation d'un corps de demande avec des demandes GET semble être découragée .

Y a-t-il une bonne pratique à ce sujet? Un autre choix pourrait être de stocker la configuration sous forme de brouillon avec une méthode et d'afficher les résultats avec d'autres, mais cela nécessiterait une étape supplémentaire et la gestion des brouillons sur le serveur:

POST /api/preview/config BODY {config: ....}

GET /api/preview/items?idPreviewConfig=1
Xtreme Biker réintègre Monica
la source
Quelle pourrait être exactement cette configuration et comment affecte-t-elle le itemsou sales? Cela affecte-t-il la représentation de l'entité retournée?
Andy
Supposons que les articles et les ventes soient tous deux affectés par les modifications que vous apportez à la configuration.
Xtreme Biker réintègre Monica le
Mais que signifient les changements? Cela change-t-il l'ensemble des entités renvoyées? Cela change-t-il la structure retournée?
Andy
En fait, cela change les valeurs de itemset sales(pas la structure), selon la configuration que vous POSTEZ.
Xtreme Biker réintègre Monica le
Et quelle est exactement la configuration? Peut-il atteindre plusieurs centaines de kilo-octets ou même plus?
Andy

Réponses:

27

C'est trop spécifique au domaine pour avoir une prise en charge native dans HTTP.

Au lieu de cela, vous pouvez effectuer l'une des opérations suivantes:

  1. Avoir un POST /api/config/preview. Côté serveur, l'application saura qu'elle ne doit pas modifier la configuration réelle, mais combiner celle-ci avec celle que vous avez publiée et renvoyer le résultat indiquant ce qui a été modifié.

    Plus tard, si l'utilisateur est satisfait du résultat, il effectuera un POST /api/configcontenant la même charge utile que dans la demande précédente. Cela écrasera efficacement la configuration.

    L'avantage de cette approche est que vous n'apportez aucune modification de rupture à l'API actuelle. Les clients qui n'ont pas besoin de la fonction d'aperçu pourront toujours mettre à jour les entrées comme ils le faisaient auparavant.

    L'inconvénient est que lorsque le corps est volumineux, cela signifierait qu'il serait nécessaire de l'envoyer deux fois au serveur. Si tel est votre cas, vous pouvez utiliser l'approche suivante.

  2. Avoir un POST /api/config/preparequi se souvient de ce qui a été envoyé dans un enregistrement temporaire et renvoie deux choses: l'ID de l'enregistrement temporaire (par exemple 12345) et l'aperçu des modifications.

    Si l'utilisateur est satisfait du résultat, il effectuera une POST /api/config/commit/12345sauvegarde définitive des modifications. Sinon, l'enregistrement temporaire peut être conservé pendant un certain temps, puis supprimé par une tâche cron.

    L'avantage est que, là encore, vous pouvez conserver l'original POST /api/configintact, et les clients qui n'ont pas besoin d'un aperçu ne se casseront pas.

    Les inconvénients sont que (1) la gestion de la suppression d'enregistrements temporaires peut être délicate (ce qui vous fait penser qu'une heure est suffisante? Et si dix minutes plus tard, vous manquez de mémoire? Comment les clients gèrent un HTTP 404 lors de la validation de un dossier qui a expiré?) et que (2) la soumission d'un dossier en deux étapes peut être plus compliquée qu'elle ne devrait l'être.

  3. Déplacez la logique d'aperçu côté client.

Arseni Mourzenko
la source
Qu'en est-il de l'envoi d'un en-tête qui dit "ne persistez pas, montrez-moi seulement le cas-si"? Je vais modifier cela dans la réponse si cela vous convient @ArseniMourzenko
marstato
1
@marstato: personnellement, je n'aime pas particulièrement les en-têtes HTTP pour cet usage. Bien que cela puisse avoir du sens pour d'autres personnes, je vais bien si vous modifiez ma réponse. Notez que vous pouvez également publier votre propre réponse, ce qui permettrait aux autres de voter positivement (et vous permettra d'obtenir des points de réputation).
Arseni Mourzenko
Je suppose que l'option 1 convient mieux à mon cas. Donc, vous POSTEZ la configuration d'aperçu et vous avez les modifications dans le résultat, au lieu d'avoir à définir des points de terminaison d'aperçu pour chacune des entités définies. Semble raisonnable. La seule chose est que vous utilisez un POST pour ne faire aucun changement dans le serveur, techniquement parlant. L'option 3 n'est pas viable dans mon cas.
Xtreme Biker réintègre Monica le
1
@PedroWerneck Pouvez-vous développer cela? Il me semble que l'option 2 définit une autre entité (un projet de configuration) et fournit des moyens sans état d'interagir avec eux.
Andrew dit Réintégrer Monica le
1
@PedroWerneck C'est avec état de la même manière que le stockage d'une configuration sur le serveur est avec état. Ainsi, l'application est déjà dynamique de votre point de vue, tout comme toutes les options pour ajouter cette fonctionnalité.
jpmc26
10

Le point d'utiliser des verbes HTTP spécifiques pour différents appels api dans REST est de tirer parti des mécanismes et des attentes HTTP existants.

L'utilisation d'un GET dans ce cas semble aller à l'encontre des deux.

A. Le client doit inclure un corps avec un GET? inattendu

B. Le serveur renvoie une réponse différente à un get en fonction du corps? rompt les mécanismes de spécification et de mise en cache

Si vous avez du mal avec des questions RESTful, ma règle est de me demander.

"Comment est-ce mieux que de simplement utiliser POST pour tout?"

À moins qu'il n'y ait un avantage immédiat et évident, optez pour la stratégie Just Use POST Stupid (JUPS)

Ewan
la source
Hahaha good catch
Xtreme Biker réintègre Monica
@Ewan ... peu importe qu'il s'agisse ou non d'une approche pragmatique ... si vous utilisez POST pour tout, il convient de noter qu'il n'est pas réellement RESTful.
Allenph
1
bien, sauf si POST est le choix approprié pour toutes vos méthodes. Et ce n'est pas comme s'il y avait une règle objective que vous pouvez appliquer, nous ne ferions qu'argumenter nos interprétations subjectives de ce qui n'est guère plus qu'une ligne directrice.
Ewan
6

Vous pouvez envoyer un en-tête qui indique au serveur "ne persistez pas, montrez-moi seulement quel serait le résultat si vous le faisiez". Par exemple

POST /api/config HTTP/1.1
Host: api.mysite.com
Content-Type: application/json
Persistence-Options: simulate

{
   "config": {
      "key": "value"
   }
}

À quoi le serveur pourrait répondre:

HTTP/1.1 200 OK
Persistence-Options: simulated
Content-Type: application/json

-- preview --

Notez que si vous utilisez un O / RM basé sur une unité de travail et / ou des transactions par demande avec votre base de données, vous pouvez facilement implémenter cette fonctionnalité pour tous vos points de terminaison sans nécessiter de travail sur un point de terminaison particulier: si une demande est livrée avec cette option , annulez la transaction / l'unité d'oeuvre au lieu de la valider.

marstato
la source
@PeterRader good point, supprimé leX-
marstato
Vous êtes le bienvenu. Diriez-vous qu'une entité sous simulation devrait être représentée comme «sous simulation»?
Peter Rader
Non; c'est le point d'une simulation, n'est-ce pas? La valeur d'en-tête pourrait également être, nonemais cela serait - à mon goût - trop en contradiction avec la nature de la POSTméthode.
marstato
2

Je suggère de traiter cela de la même manière que vous traitez les recherches. Je configurerais un point de terminaison POST à ​​partir /api/config/previewduquel CRÉER un nouvel aperçu. Ensuite, je définirais un point de terminaison PUT ou PATCH api/configselon que vous avez l'intention de modifier la configuration actuelle, ou simplement de remplacer la configuration entière (probablement dans le premier cas, vous enverriez l'aperçu que vous venez de créer).

Allenph
la source
0

Avec les autres bonnes réponses, une autre option pourrait être de publier la configuration comme mentionné, et d'avoir un processus de restauration disponible aussi. Je pense que, comme la méthodologie Agile, il vaut mieux avoir moins peur des changements en ayant des procédures plus granulaires, reproductibles et testées, et cela vous donnerait une sauvegarde lorsque vous en avez besoin, réduisant le risque à peu ou pas du tout, selon l'application .

Là encore, si vous pouvez avoir des erreurs de configuration affectant l'ensemble du système, vous souhaitez le gérer plus activement, et si c'est le cas, pourquoi ne pas simplement vous efforcer de prévisualiser les modifications à ce stade, du point de vue du serveur ou du client. Bien que je puisse voir comment cette fonctionnalité de prévisualisation pourrait être plus coûteuse à développer, les cas d'utilisation ont leur propre ensemble d'étapes disparates à suivre et à tester.

Pysis
la source
0

RFC6648 déconseille les nouvelles X-constructions, je dois donc voter contre l'idée d'envoyer un nouveau champ d'en-tête. REST est un style d'architecture, ce dont nous parlons est RESTful - mais permet d'ignorer cela pour le moment.

Parce que REST est représentatif (et qu'une simulation n'a aucune représentation dans la réalité) et avec état (et qu'une simulation n'est pas un état jusqu'à ce qu'elle soit engagée), nous devons avoir une nouvelle portée, comme une portée de simulation. Mais nous devons l'appeler émulation au lieu de simulation car la simulation comprend le processus de simulation mais avec état signifie que nous avons un état stationnaire, une solution idéale d'une simulation: une émulation. Nous devons donc l'appeler émulation dans l'URL. Cela pourrait également être une bonne solution:

GET  /api/emulation - 200 OK {first:1, last:123}
POST /api/emulation/124 - 200 OK
GET  /api/emulation/124/config - 200 OK {config:{tax:8}}
PUT  /api/emulation/124/config {config:{tax:16}} - 200 OK {config:{tax:16}}
GET  /api/emulation/124/items - 200 OK [first:1, last: 3000]
GET  /api/emulation/124/items/1 - 200 OK {price:1.79, name:'Cup'}
--- show emulation ---
--- commit emulation ---
PUT /api/config {config:{tax:16}}
DELETE /api/emulation/124 - 200 OK

Il existe une autre approche .... vous remarquerez peut-être que beaucoup de demandes du client HTML / JavaScript peuvent produire le trop grand nombre de demandes , ce qui atteint la limite d'environ 17 demandes en même temps (consultez cette page ). Vous pouvez échanger l'utilisation de REST et au lieu de fournir des états d'objet boiteux, vous pouvez fournir des états de page riches spécifiques à l'utilisateur. Exemple:

GET /user/123/config - 200 OK {user:'Tim', date:3298347239847, currentItem:123, 
                  roles:['Admin','Customer'], config:{tax:16}, unsavedChanges:true, ...}

Sincères amitiés

Peter Rader
la source