Supprimer plusieurs enregistrements à l'aide de REST

97

Quelle est la manière REST-Ful de supprimer plusieurs éléments?

Mon cas d'utilisation est que j'ai une collection Backbone dans laquelle je dois pouvoir supprimer plusieurs éléments à la fois. Les options semblent être:

  1. Envoyez une demande DELETE pour chaque enregistrement (ce qui semble être une mauvaise idée s'il y a potentiellement des dizaines d'éléments);
  2. Envoyer un DELETE où les identifiants à supprimer sont regroupés dans l'URL (c'est-à-dire "/ records / 1; 2; 3");
  3. De manière non REST, envoyez un objet JSON personnalisé contenant les ID marqués pour suppression.

Toutes les options sont loin d'être idéales.

Cela semble être une zone grise de la convention REST.

Donald Taylor
la source
3
Possible duplication de Restful way pour supprimer un tas d'éléments
Luka Žitnik

Réponses:

92
  1. Est un choix RESTful viable, mais présente évidemment les limites que vous avez décrites.
  2. Ne fais pas ça. Il serait interprété par les intermédiaires comme signifiant «SUPPRIMER la (seule) ressource à /records/1;2;3» - Donc une réponse 2xx à cela peut les amener à purger leur cache de /records/1;2;3; ne pas purger /records/1, /records/2ou /records/3; proxy une réponse 410 pour /records/1;2;3, ou d'autres choses qui n'ont pas de sens de votre point de vue.
  3. Ce choix est le meilleur et peut être effectué de manière REST. Si vous créez une API et que vous souhaitez autoriser des modifications en masse des ressources, vous pouvez utiliser REST pour le faire, mais la manière exacte n'est pas immédiatement évidente pour beaucoup. Une méthode consiste à créer une ressource 'demande de changement' (par exemple en POSTANT un corps tel que records=[1,2,3]to /delete-requests) et à interroger la ressource créée (spécifiée par l'en- Locationtête de la réponse) pour savoir si votre demande a été acceptée, rejetée, est en cours ou a terminé. Ceci est utile pour les opérations de longue durée. Une autre méthode consiste à envoyer une PATCHrequête à la ressource de liste ,/records, dont le corps contient une liste de ressources et d'actions à effectuer sur ces ressources (quel que soit le format que vous souhaitez prendre en charge). Ceci est utile pour les opérations rapides où le code de réponse de la demande peut indiquer le résultat de l'opération.

Tout peut être réalisé en respectant les contraintes de REST, et généralement la réponse est de faire du "problème" une ressource, et de lui donner une URL.
Ainsi, les opérations par lots, telles que supprimer ici, ou POSTER plusieurs éléments dans une liste, ou effectuer la même modification sur une bande de ressources, peuvent toutes être gérées en créant une liste «d'opérations par lots» et en y POSTANT votre nouvelle opération.

N'oubliez pas que REST n'est pas le seul moyen de résoudre un problème. « REST » est juste un style architectural et que vous n'avez à y adhérer (mais vous perdez certains avantages de l'Internet si vous ne le faites pas). Je vous suggère de regarder cette liste d' architectures d'API HTTP et de choisir celle qui vous convient. Prenez simplement conscience de ce que vous perdez si vous choisissez une autre architecture et prenez une décision éclairée en fonction de votre cas d'utilisation.

Il y a de mauvaises réponses à cette question sur les modèles de gestion des opérations par lots dans les services Web REST? qui ont beaucoup trop de votes positifs, mais devraient être lus aussi.

Nicholas Shanks
la source
2
Ce n'est pas votre serveur dont vous devez vous soucier, ce sont les intermédiaires, les CDN, les proxys de mise en cache, etc. Internet est un système en couches. C'est la raison pour laquelle cela fonctionne si bien. Roy a déterminé quels aspects du système étaient nécessaires à son succès et les a nommés REST. Si vous émettez une DELETEdemande, tout ce qui se trouve entre le demandeur et le serveur pensera qu'une seule ressource, à l'URL spécifiée, est en cours de suppression. Les chaînes de requête sont des parties opaques de l'URL de ces appareils, donc peu importe la façon dont vous spécifiez votre API, elles ne sont pas au courant de cette connaissance et ne peuvent donc pas se comporter différemment.
Nicholas Shanks
3
/ records / 1; 2; 3 ne fonctionnera pas si vous avez beaucoup de ressources à supprimer en raison de restrictions de longueur d'URI
dukethrash
3
Notez que si l'on considère DELETE et un organisme définissant les ressources à purger, certains intermédiaires peuvent ne pas transmettre le corps. De plus, certains clients HTTP ne peuvent pas ajouter de corps à un DELETE. Voir stackoverflow.com/questions/299628/…
Luke Puplett
3
@LukePuplett Je dirais simplement qu'il DELETEest interdit de transmettre un corps de requête avec une requête. Ne fais pas ça. Si vous le faites, je mangerai vos enfants. Miam miam miam.
Nicholas Shanks
3
Le problème avec l'argument pour # 3 est qu'il comporte la même pénalité que le contre-argument contre # 2. La création d'une ressource à supprimer n'est pas quelque chose que les mandataires en amont sauront gérer - le même argument de compteur qui est soulevé contre l'approche n ° 2.
LB2
16

Si GET /records?filteringCriteriaretourne un tableau de tous les enregistrements correspondant aux critères, alors DELETE /records?filteringCriteriapourrait supprimer tous ces enregistrements.

Dans ce cas, la réponse à votre question serait DELETE /records?id=1&id=2&id=3.

Martin Ždila
la source
1
Je suis également arrivé à cette conclusion: retournez simplement le verbe à ce que vous voulez faire. Je ne comprends pas comment ce qui se passe pour GET ne va pas pour DELETE.
Luke Puplett
9
GET /records?id=1&id=2&id=3ne signifie pas "obtenir les trois enregistrements avec les ID 1, 2 et 3", cela signifie "obtenir la ressource unique avec le chemin / les enregistrements d'URL? id = 1 & id = 2 & id = 3" qui pourrait être une image d'un navet, un texte brut document contenant le numéro «42» en chinois, ou peut ne pas exister.
Nicholas Shanks
Considérez ce qui suit: deux demandes séquentielles pour /records?id=1et /records?id=2sont envoyées, et leurs réponses mises en cache par un intermédiaire (par exemple votre navigateur ou votre FAI). Si Internet savait ce que votre application voulait dire par là, il va de soi qu'une demande de /records?id=1&id=2pourrait être renvoyée par le cache simplement en fusionnant (d'une manière ou d'une autre) les deux résultats dont il dispose déjà, sans avoir à demander au serveur d'origine. Mais ce n'est pas possible. /records?id=1&id=2peut être invalide (un seul identifiant autorisé par demande) ou peut renvoyer quelque chose de complètement différent (un navet).
Nicholas Shanks
Il s'agit d'un problème de base de mise en cache des ressources. Si mon DBA a muté l'état directement, les caches ne sont plus synchronisés. Vous donnez un exemple 410 retourné par l'intermédiaire, mais 410 est pour les suppressions permanentes, lors de la suppression, un cache peut effacer son emplacement pour cette URL, mais il n'enverra pas un 410 ou un 404, car il ne sait pas si un DBA ne se contente pas de remettre immédiatement la ressource à l'origine.
Luke Puplett
4
@NicholasShanks Je ne suis vraiment pas d'accord. Si les résultats sont mis en cache, c'est la faute du serveur. Et si vous parlez de la conception de l'API, vous êtes, espérons-le, celui qui écrit le code du serveur. Que vous utilisiez id[]=1&id[]=2ou id=1&id=2dans la chaîne de requête pour représenter un tableau de valeurs, cette chaîne de requête représente exactement cela. Et je pense qu'il est extrêmement courant et bon que la chaîne de requête représente un filtre. En outre, si vous autorisez les suppressions et les mises à jour, ne mettez pas en cache les GETrequêtes. Si vous le faites, les clients resteront périmés.
Joseph Nields
8

Je pense que l'API Mozilla Storage Service SyncStorage v1.5 est un bon moyen de supprimer plusieurs enregistrements à l'aide de REST.

Supprime une collection entière.

DELETE https://<endpoint-url>/storage/<collection>

Supprime plusieurs BSO d'une collection avec une seule demande.

DELETE https://<endpoint-url>/storage/<collection>?ids=<ids>

ids : supprime les BSO de la collection dont les identifiants se trouvent dans la liste séparée par des virgules. Un maximum de 100 identifiants peut être fourni.

Supprime le BSO à l'emplacement indiqué.

DELETE https://<endpoint-url>/storage/<collection>/<id>

http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions

bootsoon
la source
Cela semble être une bonne solution. Je suppose que si Mozilla pense que c'est correct, alors ça doit l'être? La seule question est alors la gestion des erreurs. Supposons qu'ils passent? Ids = 1,2,3 et id 3 n'existe pas, supprimez-vous 1 et 2 puis répondez par un 200 parce que le demandeur veut que 3 disparaisse et qu'il n'y est pas, donc ce n'est pas grave? ou si ils sont autorisés à supprimer 1 mais pas 2 ... ne supprimez-vous rien et répondez-vous par une erreur ou supprimez-vous ce que vous pouvez et laissez les autres ...
tempcke
Je renvoie généralement une réponse réussie car l'état final est le même quel que soit. Cela simplifie également la logique du client, car il n'a plus à gérer cet état d'erreur. En ce qui concerne le cas d'autorisation, j'échouerais simplement toute la demande ... mais cela dépend vraiment de votre cas d'utilisation.
Nathan Phetteplace le
3

Cela semble être une zone grise de la convention REST.

Oui, jusqu'à présent, je n'ai rencontré qu'un seul guide de conception d'API REST qui mentionne les opérations par lots (comme une suppression par lots): le guide de conception de l'API Google .

Ce guide mentionne la création de méthodes "personnalisées" qui peuvent être associées via une ressource en utilisant un signe deux-points, par exemple https://service.name/v1/some/resource/name:customVerb, il mentionne également explicitement les opérations par lots comme cas d'utilisation:

Une méthode personnalisée peut être associée à une ressource, une collection ou un service. Il peut prendre une demande arbitraire et renvoyer une réponse arbitraire, et prend également en charge la demande et la réponse en continu. [...] Les méthodes personnalisées devraient utiliser le verbe HTTP POST car il a la sémantique la plus flexible [...] Pour les méthodes critiques pour les performances, il peut être utile de fournir des méthodes de traitement par lots personnalisées pour réduire la surcharge par requête .

Ainsi, vous pouvez faire ce qui suit selon le guide API de Google:

POST /api/path/to/your/collection:batchDelete

... pour supprimer un groupe d'éléments de votre ressource de collection.

B12Toaster
la source
Est-ce une solution viable que la liste des éléments est communiquée via un tableau au format JSON?
Daniele
Oui bien sûr. vous pouvez POSTER une charge utile dans laquelle les identifiants sont envoyés via un tableau json.
B12Toaster
Il est intéressant de noter que le guide de l'API Google a déclaré If the HTTP verb used for the custom method does not accept an HTTP request body (GET, DELETE), the HTTP configuration of such method must not use the body clause at all,au chapitre Méthode personnalisée. Mais l' GET accounts.locations.batchGetapi est la méthode GET avec body. C'est bizarre. developer.google.com/my-business/reference/rest/v4/…
鄭元傑
@ 鄭元傑 d'accord, ça a l'air un peu bizarre à première vue mais si vous regardez de près c'est en fait une POSTméthode http utilisée et seule la méthode personnalisée est nommée batchGet. Je suppose que Google le fait pour (a) s'en tenir à sa règle selon laquelle toutes les méthodes personnalisées doivent être POST(voir ma réponse) et (b) pour permettre aux gens de mettre plus facilement un "filtre" dans le corps afin que vous n'ayez pas à le faire échapper ou encoder le filtre comme avec les chaînes de requête. l'inconvénient, bien sûr, est que ce n'est plus vraiment cachable ...
B12Toaster
https://service.name/v1/some/resource/name:customVerbn'est pas RESTful par définition.
démon le
2

J'ai autorisé le remplacement en gros d'une collection, par exemple PUT ~/people/123/shoesoù le corps est la représentation entière de la collection.

Cela fonctionne pour les petites collections d'éléments enfants où le client souhaite examiner les éléments et en éliminer certains, en ajouter d'autres, puis mettre à jour le serveur. Ils pourraient METTRE une collection vide pour tout supprimer.

Cela signifierait GET ~/people/123/shoes/9resterait toujours dans le cache même si un PUT l'a supprimé, mais ce n'est qu'un problème de mise en cache et ce serait un problème si une autre personne supprimait la chaussure.

Mes API de données / systèmes utilisent toujours des ETags par opposition aux délais d'expiration, de sorte que le serveur est touché à chaque demande, et j'ai besoin d'en-têtes de version / concurrence corrects pour muter les données. Pour les API qui sont en lecture seule et alignées vue / rapport, j'utilise les délais d'expiration pour réduire les appels à l'origine, par exemple, un classement peut durer 10 minutes.

Pour des collections beaucoup plus volumineuses, comme ~/people, j'ai tendance à ne pas avoir besoin de plusieurs suppressions, le cas d'utilisation a tendance à ne pas se produire naturellement et donc un seul DELETE fonctionne bien.

À l'avenir, et d'après l'expérience de la création d'API REST et des mêmes problèmes et exigences, comme l'audit, je serais enclin à utiliser uniquement les verbes GET et POST et à concevoir autour d'événements, par exemple POST un événement de changement d'adresse, bien que je soupçonne que 'll viendra avec son propre ensemble de problèmes :)

J'autoriserais également les développeurs front-end à créer leurs propres API qui consomment des API back-end plus strictes, car il existe souvent des raisons pratiques et valides côté client pour lesquelles ils n'aiment pas les conceptions d'API REST strictes "Fielding zealot", et pour la productivité et raisons de la superposition du cache.

Luke Puplett
la source
J'ai adoré cette réponse jusqu'à ce que je lis la dernière phrase :) Je n'ai jamais vu de cas d'utilisation où l'application de REST strict a eu un effet néfaste net. Bien sûr, cela peut permettre d'écrire plus de code aux deux extrémités, mais vous vous retrouvez avec un système plus sûr, plus propre et moins couplé.
Nicholas Shanks
Haha. C'est en fait devenu un modèle! Le backend pour le front-end s'appelle le radar de la technologie ThoughtWorks. Cela permet également d'écrire plus de logique d'application, ce qui serait fastidieux en JavaScript, et peut évidemment être mis à jour sans client, par exemple, pour une application iOS.
Luke Puplett
En lisant les quatre premiers résultats de Google, il semble que cette technique BFF ne puisse fonctionner que lorsque les clients sont sous votre contrôle . Les développeurs clients développent l'API de leur choix, mappant les appels aux API de microservice qui sont le véritable back-end. Dans ce diagramme: samnewman.io/patterns/architectural/bff/#bff, je placerais la ligne «Périmètre» sous les cases BFF - chaque case fait simplement partie du client. Il pourrait même vivre en dehors du centre de données hébergeant les microservices. Je ne vois pas non plus comment REST ne s'applique pas aux deux interfaces (client / BFF et BFF / microservice).
Nicholas Shanks
1
Ouais c'est un bon point. C'est généralement lorsque vous avez une équipe de microservices et une équipe qui crée une application angulaire, par exemple, et que cette équipe de développement est plus de types frontaux qui n'aiment pas avoir à travailler contre un tas de petits services puristes. Bien que je ne vois aucune raison pour laquelle vous ne pouvez pas utiliser le même modèle pour extraire les microservices et l'agrégation dans une façade plus utilisable pour vos clients, de sorte que les microservices puissent être modifiés sans affecter la façade.
Luke Puplett
Un point de terminaison d'API doit modéliser les besoins du domaine et de l'entreprise. Code pour résoudre ces problèmes et éviter la sur-ingénierie pour adhérer à des spécifications strictes et inflexibles de nombreuses fois. REST n'est de toute façon rien d'autre que des lignes directrices.
Victorio Berra