Un modo riposante per eliminare un mucchio di oggetti


97

Nell'articolo wiki per REST è indicato che se usi http://example.com/resources DELETE, significa che stai eliminando l'intera raccolta.

Se utilizzi http://example.com/resources/7HOU57Y DELETE, significa che stai eliminando quell'elemento.

Sto realizzando un SITO WEB, nota NON SERVIZIO WEB.

Ho un elenco che ha 1 casella di controllo per ogni elemento nell'elenco. Dopo aver selezionato più elementi per l'eliminazione, consentirò agli utenti di premere un pulsante chiamato ELIMINA SELEZIONE. Se l'utente preme il pulsante, verrà visualizzata una finestra di dialogo js che chiede all'utente di confermare l'eliminazione. se l'utente conferma, tutti gli elementi vengono eliminati.

Quindi come dovrei provvedere all'eliminazione di più elementi in modo RESTFUL?

NOTA, attualmente per DELETE in una pagina Web, quello che faccio è utilizzare il tag FORM con POST come azione ma includere un _method con il valore DELETE poiché questo è ciò che è stato indicato da altri in SO su come eseguire l'eliminazione RESTful per la pagina Web .


1
È fondamentale che queste eliminazioni vengano eseguite in modo atomico? Vuoi davvero annullare l'eliminazione dei primi 30 elementi se il 31 non può essere eliminato?
Darrel Miller

@darrelmiller bella domanda. Ho pensato che se le eliminazioni vengono eseguite in modo atomico, sarà meno efficiente. Quindi mi sto appoggiando a DELETE FROM tablename WHERE ID IN ({list of ids}). Se qualcuno può farmi notare se questa è una buona idea o correggermi. sarebbe molto apprezzato. Inoltre non richiedo l'inverso dell'eliminazione per i primi 20 elementi se il 21 ° viene eliminato. Ancora una volta apprezzo se qualcuno mi può mostrare la differenza di approccio in cui devo invertire rispetto a dove NON ho bisogno di invertire
Kim Stacks

1
Nota: potrebbero esserci dei limiti per la clausola "IN"; ad esempio, in Oracle puoi inserire un massimo di 1000 ID.
rapina il

Di Google offre guida di progettazione API una soluzione per creare personalizzati (lotto) per le operazioni in un API REST, vedere la mia risposta qui: stackoverflow.com/a/53264372/2477619
B12Toaster

Risposte:


53

Penso che la risposta di Rojoca sia la migliore finora. Una leggera variazione potrebbe essere quella di eliminare la conferma javascript sulla stessa pagina e, invece, creare la selezione e reindirizzare ad essa, mostrando un messaggio di conferma su quella pagina. In altre parole:

Da:
http://example.com/resources/

fare un

POST con una selezione di ID a:
http://example.com/resources/selections

che, in caso di successo, dovrebbe rispondere con:

HTTP / 1.1 201 creato e un'intestazione Location a:
http://example.com/resources/selections/DF4XY7

In questa pagina vedrai quindi una casella di conferma (javascript), che se confermi farà una richiesta di:

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

che, in caso di successo, dovrebbe rispondere con: HTTP / 1.1 200 Ok (o qualsiasi cosa sia appropriata per una cancellazione riuscita)


Mi piace questa idea perché non hai bisogno di reindirizzamenti. Incorporando AJAX potresti fare tutto questo senza lasciare la pagina.
rojoca

Dopo questo DELETE example.com/resources/selections/DF4XY7 , verrei reindirizzato nuovamente a example.com/resources?
Kim Stacks

7
@fireeyeboy Questo approccio in due fasi sembra essere un modo così comunemente suggerito di eseguire un'eliminazione multipla, ma perché? Perché non invii semplicemente una richiesta DELETE a un uri like http://example.com/resources/selections/e nel payload (corpo) della richiesta invii i dati per quali elementi desideri eliminare. Per quanto ne so, non c'è nulla che ti impedisca di farlo, ma mi viene sempre detto "ma non è RESTfull".
thecoshman

6
DELETE può potenzialmente avere il corpo ignorato dai infrastrutture HTTP: stackoverflow.com/questions/299628/...
Luca Puplett

DELETE può avere un corpo, ma molte delle sue implementazioni ne hanno proibito il corpo per impostazione predefinita
dmitryvim

54

Un'opzione è creare una "transazione" di cancellazione. Quindi POSTa qualcosa di simile a http://example.com/resources/deletesuna nuova risorsa composta da un elenco di risorse da eliminare. Quindi nella tua applicazione fai solo l'eliminazione. Quando esegui il post, devi restituire una posizione della transazione creata, ad es http://example.com/resources/deletes/DF4XY7. Una GETsu questa potrebbe restituire lo stato della transazione (completata o in corso) e / o un elenco di risorse da eliminare.


2
Niente a che vedere con il tuo database. Per transazione intendo solo un elenco di operazioni da eseguire. In questo caso si tratta di un elenco di eliminazioni. Quello che fai è creare un nuovo elenco (di eliminazioni) come risorsa nella tua applicazione. La tua applicazione web può elaborare quell'elenco come preferisci. Quella risorsa ha un URI, ad esempio example.com/resources/deletes/DF4XY7 . Ciò significa che puoi controllare lo stato dell'eliminazione tramite un GET a quell'URI. Ciò sarebbe utile se quando hai eseguito un'eliminazione devi eliminare le immagini da Amazon S3 o da qualche altro CDN e l'operazione potrebbe richiedere molto tempo per essere completata.
rojoca

2
+1 questa è una bella soluzione. Invece di inviare un DELETE a ciascuna risorsa, @rojoca propone di creare un'istanza di un nuovo tipo di risorsa la cui unica attività è l'eliminazione di un elenco di risorse. Ad esempio, si dispone di una raccolta di risorse utente e si desidera eliminare gli utenti Bob, Dave e Amy dalla raccolta, quindi creare una nuova risorsa di eliminazione POSTing Bob, Dave e Amy come parametri di creazione. La risorsa di eliminazione viene creata e rappresenta il processo asincrono di eliminazione di Bob, Dave e Amy dalla raccolta Users.
Mike Tunnicliffe

1
Mi dispiace. Ho ancora qualche leggera difficoltà a comprendere alcuni problemi. il DF4XY7. come diavolo si genera questa stringa? Questa risorsa di eliminazione. Devo inserire dei dati nel database? Mi scuso se ripeto alcune domande. Mi è solo un po 'sconosciuto.
Kim Stacks

1
Presumo che DF4XY7 sia un ID univoco generato, forse è più naturale usare semplicemente l'ID generato quando viene salvato nel DB, ad esempio example.com/resources/deletes/7. La mia opinione sarebbe quella di creare il modello di eliminazione e salvarlo nel database, puoi fare in modo che il processo asincrono che elimina gli altri record aggiorni il modello di eliminazione con lo stato di completamento e gli eventuali errori rilevanti.
Mike Tunnicliffe

2
@rojoca sì, penso che il problema sia che HTTP è molto "DELETE è per la rimozione di una singola risorsa". Qualunque cosa tu faccia, ottenere più eliminazioni è un po 'un trucco. Puoi ancora restituire un "lavoro" al client dicendo che questa attività è in fase di elaborazione (e potrebbe richiedere del tempo), ma utilizza questo URI per controllare l'avanzamento. Ho letto le specifiche e ho capito che DELETE può avere un corpo, proprio come le altre richieste.
thecoshman

33

Ecco cosa ha fatto Amazon con la sua API REST S3.

Richiesta di eliminazione individuale:

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))

Richiesta di eliminazione di più oggetti :

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>           

Ma l' API Graph di Facebook , l' API REST di Parse Server e l' API REST di Google Drive vanno ancora oltre, consentendo di "raggruppare" singole operazioni in un'unica richiesta.

Ecco un esempio da Parse Server.

Richiesta di eliminazione individuale:

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

Richiesta batch:

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

13

Direi DELETE http://example.com/resources/id1,id2,id3,id4 o DELETE http://example.com/resources/id1+id2+id3+id4 . Poiché "REST è un'architettura (...) [non] protocollo", per citare questo articolo di wikipedia, non esiste, credo, un solo modo per farlo.

Sono consapevole che quanto sopra non è possibile senza JS con HTML ma ho la sensazione che REST fosse:

  • Creato senza pensare a dettagli minori come le transazioni. Chi avrebbe bisogno di operare su più di un singolo articolo? Questo è in qualche modo giustificato nel protocollo HTTP in quanto non era destinato a servire attraverso di esso nient'altro che le pagine web statiche.
  • Non è necessario adattarsi bene ai modelli attuali, anche del puro HTML.

thx - cosa succede se si desidera eliminare l'intera raccolta - gli ID dovrebbero quindi essere omessi?
BKSpurgeon

"Ho la sensazione che REST sia stato ... creato senza pensare a dettagli minori come le transazioni" - Non credo che sia del tutto vero. Se ho capito bene, in REST, le transazioni sono rappresentate da risorse, non da un metodo. C'è una buona discussione che culmina in questo commento su questo post del blog .
Paul D. Waite

10

È interessante notare che penso che lo stesso metodo si applichi al PATCH di più entità e richiede di pensare a cosa intendiamo con il nostro URL, parametri e metodo REST.

  1. restituisce tutti gli elementi 'foo':

    [GET] api/foo

  2. restituisce elementi 'foo' con filtri per ID specifici:

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

In che senso l'URL e il filtro determinano "con quali elementi abbiamo a che fare?", E il metodo REST (in questo caso "GET") dice "cosa fare con quegli elementi?"

  1. Quindi PATCH più record per contrassegnarli come letti

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

..con i dati foo [read] = 1

  1. Infine, per eliminare più record, questo endpoint è il più logico:

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

Per favore, capisci che non credo che ci siano "regole" su questo - per me "ha senso"


In realtà per quanto riguarda PATCH: poiché significa aggiornamento parziale se pensi all'elenco delle entità come un'entità stessa (anche se di tipo array), inviando un array parziale (solo gli ID che vuoi aggiornare) di entità parziali, allora tu può tralasciare la stringa di query, quindi non avendo un URL che rappresenta più di un'entità.
Szabolcs Páll

2

Come dice la risposta di Decent Dabbler e la risposta di rojocas , il più canonico è usare le risorse virtuali per eliminare una selezione di risorse, ma penso che non sia corretto dal punto di vista REST, perché l'esecuzione di DELETE http://example.com/resources/selections/DF4XY7dovrebbe rimuovere la risorsa di selezione stessa, non le risorse selezionate.

Prendendo la risposta di Maciej Piechotka o la risposta di fezfox , ho solo un'obiezione: esiste un modo più canonico per passare un array di ID e sta usando l'operatore di array:

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

In questo modo stai attaccando l'endpoint Delete Collection ma filtrando l'eliminazione con una stringa di query nel modo giusto.


-1

Poiché non esiste un modo "corretto" per farlo, ciò che ho fatto in passato è:

invia DELETE a http://example.com/qualcosa con dati codificati xml o json nel corpo.

quando ricevi la richiesta, controlla DELETE, se vero, quindi leggi il corpo per quelli da eliminare.


Questo è l'approccio che ha senso per me, è sufficiente inviare i dati in una richiesta, ma mi viene sempre risposto "ma non è RESTfull". Hai qualche fonte che suggerisce che questo sia un metodo valido e "RESTfull" per farlo?
thecoshman

10
Il problema con questo approccio è che le operazioni DELETE non prevedono un corpo, quindi alcuni router intermedi su Internet potrebbero rimuoverlo per te senza il tuo controllo o la tua conoscenza. Quindi usare body per DELETE non è sicuro!
Alex White

Riferimento per Alex commento: stackoverflow.com/questions/299628/...
Luca 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.da tools.ietf.org/html/rfc7231#section-4.3.5
cottton

-1

Ho avuto la stessa situazione per eliminare più elementi. Questo è quello che ho finito per fare. Ho usato l'operazione DELETE e gli ID degli elementi che dovevano essere eliminati facevano parte dell'intestazione HTTP.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.