Un moyen reposant pour supprimer un tas d'éléments

98

Dans l' article wiki pour REST, il est indiqué que si vous utilisez http://example.com/resources DELETE, cela signifie que vous supprimez toute la collection.

Si vous utilisez http://example.com/resources/7HOU57Y DELETE, cela signifie que vous supprimez cet élément.

Je fais un SITE WEB, notez PAS DE SERVICE WEB.

J'ai une liste qui a 1 case à cocher pour chaque élément de la liste. Une fois que j'ai sélectionné plusieurs éléments à supprimer, je vais permettre aux utilisateurs d'appuyer sur un bouton appelé SUPPRIMER LA SÉLECTION. Si l'utilisateur appuie sur le bouton, une boîte de dialogue js apparaîtra pour demander à l'utilisateur de confirmer la suppression. si l'utilisateur confirme, tous les éléments sont supprimés.

Alors, comment dois-je gérer la suppression de plusieurs éléments de manière RESTFUL?

REMARQUE, actuellement pour DELETE dans une page Web, ce que je fais est d'utiliser la balise FORM avec POST comme action, mais j'inclus une méthode avec la valeur DELETE, car c'est ce qui a été indiqué par d'autres dans SO sur la façon de supprimer RESTful pour la page Web .

Kim Stacks
la source
1
Est-il essentiel que ces suppressions soient effectuées de manière atomique? Voulez-vous vraiment annuler la suppression des 30 premiers éléments si le 31 ne peut pas être supprimé?
Darrel Miller
@darrelmiller bonne question. Je pensais que si les suppressions étaient effectuées de manière atomique, ce serait moins efficace. Par conséquent, je me penche vers DELETE FROM nomtable WHERE ID IN ({list of ids}). Si quelqu'un peut me dire si c'est une bonne idée ou me corriger. ce serait bien apprécié. De plus, je n'ai pas besoin de l'inverse de la suppression pour les 20 premiers éléments si le 21e est supprimé. Encore une fois, j'apprécie que quelqu'un puisse me montrer la différence d'approche où je dois inverser et où je n'ai PAS besoin d'inverser
Kim Stacks
1
Remarque: il peut y avoir des limites pour la clause "IN"; par exemple, dans Oracle, vous pouvez mettre un maximum de 1000 identifiants.
rob
Le guide de conception d'API de Google propose une solution pour créer des opérations personnalisées (par lots) dans une API REST, voir ma réponse ici: stackoverflow.com/a/53264372/2477619
B12Toaster

Réponses:

54

Je pense que la réponse de Rojoca est la meilleure à ce jour. Une légère variation pourrait être, pour supprimer la confirmation javascript sur la même page, et à la place, créer la sélection et la rediriger, en affichant un message de confirmation sur cette page. En d'autres termes:

De:
http://example.com/resources/

fait une

POST avec une sélection des ID à:
http://example.com/resources/selections

qui, en cas de succès, doit répondre par:

HTTP / 1.1 201 créé et un en-tête Location vers:
http://example.com/resources/selections/DF4XY7

Sur cette page, vous verrez alors une boîte de confirmation (javascript) qui, si vous confirmez, fera une demande de:

SUPPRIMER http://example.com/resources/selections/DF4XY7

qui, en cas de succès, devrait répondre avec: HTTP / 1.1 200 Ok (ou tout ce qui est approprié pour une suppression réussie)

Dabbler décent
la source
J'aime cette idée car vous n'avez besoin d'aucune redirection. En incorporant AJAX, vous pouvez faire tout cela sans quitter la page.
rojoca
Après cet exemple DELETE example.com/resources/selections/DF4XY7 , serais-je redirigé vers example.com/resources?
Kim Stacks
7
@fireeyeboy Cette approche en deux étapes semble être une façon si courante d'effectuer une suppression multiple, mais pourquoi? Pourquoi n'envoyez-vous pas simplement une demande DELETE à un uri comme http://example.com/resources/selections/et dans la charge utile (corps) de la demande, vous envoyez les données pour les éléments que vous souhaitez supprimer. Pour autant que je sache, rien ne vous empêche de faire cela, mais je me fais toujours rencontrer "mais ce n'est pas RESTfull".
thecoshman
6
DELETE peut potentiellement faire ignorer le corps par l'infrastructure HTTP: stackoverflow.com/questions/299628/…
Luke Puplett
DELETE peut avoir un corps, mais beaucoup de ses implémentations ont interdit son corps par défaut
dmitryvim
54

Une option consiste à créer une "transaction" de suppression. Donc, vous POSTà quelque chose comme http://example.com/resources/deletesune nouvelle ressource consistant en une liste de ressources à supprimer. Ensuite, dans votre application, vous faites simplement la suppression. Lorsque vous faites la publication, vous devez renvoyer un emplacement de votre transaction créée, par exemple http://example.com/resources/deletes/DF4XY7. Un GETsur celui-ci pourrait renvoyer l'état de la transaction (terminée ou en cours) et / ou une liste de ressources à supprimer.

Rojoca
la source
2
Rien à voir avec votre base de données. Par transaction, j'entends simplement une liste d'opérations à effectuer. Dans ce cas, il s'agit d'une liste de suppressions. Ce que vous faites, c'est créer une nouvelle liste (de suppressions) en tant que ressource dans votre application. Votre application Web peut traiter cette liste comme vous le souhaitez. Cette ressource a un URI, par exemple, example.com/resources/deletes/DF4XY7 . Cela signifie que vous pouvez vérifier l'état de la suppression via un GET vers cet URI. Cela serait pratique si, lorsque vous effectuez une suppression, vous deviez supprimer des images d'Amazon S3 ou d'un autre CDN et que l'opération peut prendre beaucoup de temps.
rojoca
2
+1 c'est une bonne solution. Au lieu d'envoyer un DELETE à chaque ressource, @rojoca propose de créer une instance d'un nouveau type de ressource dont la seule tâche est de supprimer une liste de ressources. Par exemple, vous avez une collection de ressources utilisateur et vous souhaitez supprimer les utilisateurs Bob, Dave et Amy de votre collection, vous créez donc une nouvelle ressource de suppression POSTing Bob, Dave et Amy comme paramètres de création. La ressource de suppression est créée et représente le processus asynchrone de suppression de Bob, Dave et Amy de la collection Users.
Mike Tunnicliffe
1
Je suis désolé. J'ai encore de légères difficultés à comprendre quelques problèmes. le DF4XY7. comment diable générez-vous cette chaîne? Cette ressource de suppression. Dois-je insérer des données dans la base de données? Je m'excuse si je répète quelques questions. Cela m'est juste un peu inconnu.
Kim Stacks
1
Je suppose que DF4XY7 est un identifiant unique généré, il est peut-être plus naturel d'utiliser simplement l'identifiant généré lors de l'enregistrement dans la base de données, par exemple example.com/resources/deletes/7. Ma décision serait de créer le modèle de suppression et de l'enregistrer dans la base de données, vous pouvez demander au processus asynchrone de supprimer les autres enregistrements de mettre à jour le modèle de suppression avec l'état d'achèvement et toutes les erreurs pertinentes.
Mike Tunnicliffe
2
@rojoca ouais, je pense que le problème est que HTTP est vraiment «DELETE est pour supprimer une seule ressource». Quoi que vous fassiez, obtenir plusieurs suppressions est un peu un hack. Vous pouvez toujours renvoyer un «travail» au client en disant que cette tâche est en cours de travail (et pourrait prendre un certain temps), mais utilisez cet URI pour vérifier la progression. J'ai lu la spécification et j'ai compris que DELETE peut avoir un corps, tout comme les autres requêtes.
thecoshman
33

Voici ce qu'Amazon a fait avec son API S3 REST.

Demande de suppression individuelle:

DELETE /ObjectName HTTP/1.1
Host: BucketName.s3.amazonaws.com
Date: date
Content-Length: length
Authorization: authorization string (see Authenticating Requests (AWS Signature Version 4))

Demande de suppression multi-objets :

POST /?delete HTTP/1.1
Host: bucketname.s3.amazonaws.com
Authorization: authorization string
Content-Length: Size
Content-MD5: MD5

<?xml version="1.0" encoding="UTF-8"?>
<Delete>
    <Quiet>true</Quiet>
    <Object>
         <Key>Key</Key>
         <VersionId>VersionId</VersionId>
    </Object>
    <Object>
         <Key>Key</Key>
    </Object>
    ...
</Delete>           

Mais Facebook Graph API , Parse Server REST API et Google Drive REST API vont encore plus loin en vous permettant de «grouper» des opérations individuelles en une seule requête.

Voici un exemple de Parse Server.

Demande de suppression individuelle:

curl -X DELETE \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  https://api.parse.com/1/classes/GameScore/Ed1nuqPvcm

Demande de lot:

curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "requests": [
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1337,
              "playerName": "Sean Plott"
            }
          },
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1338,
              "playerName": "ZeroCool"
            }
          }
        ]
      }' \
  https://api.parse.com/1/batch
Luka Žitnik
la source
13

Je dirais DELETE http://example.com/resources/id1,id2,id3,id4 ou DELETE http://example.com/resources/id1+id2+id3+id4 . Comme "REST est une architecture (...) [pas] un protocole" pour citer cet article de wikipedia, il n'y a, je crois, pas une seule façon de faire cela.

Je suis conscient que ci-dessus n'est pas possible sans JS avec HTML mais j'ai le sentiment que REST était:

  • Créé sans penser aux détails mineurs comme les transactions. Qui aurait besoin d'opérer sur plus d'un seul article? Ceci est en quelque sorte justifié dans le protocole HTTP car il n'était pas destiné à servir à travers lui autre chose que des pages Web statiques.
  • Pas nécessaire de bien s'adapter aux modèles actuels - même en HTML pur.
Maciej Piechotka
la source
thx - et si vous vouliez supprimer toute la collection - les identifiants devraient-ils alors être omis?
BKSpurgeon
«J'ai le sentiment que REST a été ... créé sans penser à des détails mineurs comme les transactions» - je ne pense pas que ce soit tout à fait vrai. Si je comprends bien, dans REST, les transactions sont représentées par des ressources et non par une méthode. Il y a une bonne discussion qui culmine dans ce commentaire sur ce billet de blog .
Paul D.Waite
10

Fait intéressant, je pense que la même méthode s'applique au PATCHing de plusieurs entités et nécessite de réfléchir à ce que nous entendons par notre URL, nos paramètres et notre méthode REST.

  1. renvoie tous les éléments 'foo':

    [GET] api/foo

  2. renvoie des éléments 'foo' avec filtrage pour des identifiants spécifiques:

    [GET] api/foo?ids=3,5,9

En quoi l'URL et le filtre déterminent-ils "quels éléments nous traitons-nous?", Et la méthode REST (dans ce cas "GET") dit "que faire de ces éléments?"

  1. Par conséquent, PATCH plusieurs enregistrements pour les marquer comme lus

    [PATCH] api/foo?ids=3,5,9

..avec les données toto [lecture] = 1

  1. Enfin, pour supprimer plusieurs enregistrements, ce point de terminaison est le plus logique:

    [DELETE] api/foo?ids=3,5,9

S'il vous plaît, comprenez que je ne pense pas qu'il y ait de "règles" à ce sujet - pour moi, cela "a du sens"

fezfox
la source
En fait en ce qui concerne PATCH: puisque cela signifie une mise à jour partielle si vous pensez à la liste des entités comme une entité elle-même (même si elle est de type tableau), en envoyant un tableau partiel (uniquement les identifiants que vous souhaitez mettre à jour) d'entités partielles, alors vous peut laisser de côté la chaîne de requête, n'ayant donc pas d'URL représentant plus d'une entité.
Szabolcs Páll
2

Comme le dit la réponse de Decent Dabbler et la réponse rojocas , le plus canonique consiste à utiliser des ressources virtuelles pour supprimer une sélection de ressources, mais je pense que c'est incorrect du point de vue REST, car l'exécution d'un DELETE http://example.com/resources/selections/DF4XY7devrait supprimer la ressource de sélection elle-même, pas les ressources sélectionnées.

En prenant la réponse Maciej Piechotka ou la réponse fezfox , je n'ai qu'une objection: il existe une manière plus canonique de transmettre un tableau d'identifiants, et utilise l'opérateur de tableau:

DELETE /api/resources?ids[]=1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d&ids[]=7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b

De cette façon, vous attaquez le point de terminaison Supprimer la collection mais en filtrant la suppression avec une chaîne de requête de la bonne manière.

mangelsnc
la source
-1

Comme il n'y a pas de moyen `` approprié '' de faire cela, ce que j'ai fait dans le passé est:

envoyer DELETE à http://example.com/something avec des données codées xml ou json dans le corps.

lorsque vous recevez la demande, vérifiez DELETE, si vrai, puis lisez le corps pour ceux à supprimer.

utilisateur103219
la source
C'est l'approche qui a du sens pour moi, vous envoyez simplement les données en une seule demande, mais je suis toujours rencontré "mais ce n'est pas RESTfull". Avez-vous des sources suggérant que c'est une méthode viable et «RESTfull» pour ce faire?
thecoshman
10
Le problème avec cette approche est que les opérations DELETE ne s'attendent pas à un corps, et donc certains des routeurs intermédiaires sur Internet peuvent le supprimer pour vous sans votre contrôle ou votre connaissance. Donc, utiliser body pour DELETE n'est pas sûr!
Alex White
Référence pour le commentaire d'Alex: stackoverflow.com/questions/299628/…
Luke Puplett
1
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.depuis tools.ietf.org/html/rfc7231#section-4.3.5
cottton
-1

J'ai eu la même situation pour supprimer plusieurs éléments. C'est ce que j'ai fini par faire. J'ai utilisé l'opération DELETE et les identifiants des éléments qui devaient être supprimés faisaient partie de l'en-tête HTTP.

Sherin Syriaque
la source