Spokojny sposób na usuwanie wielu elementów

98

W artykule wiki dla REST wskazano, że jeśli używasz http://example.com/resources DELETE, oznacza to, że usuwasz całą kolekcję.

Jeśli używasz http://example.com/resources/7HOU57Y DELETE, oznacza to, że usuwasz ten element.

Robię STRONĘ INTERNETOWĄ, uwaga NIE SERWIS INTERNETOWY.

Mam listę zawierającą 1 pole wyboru dla każdej pozycji na liście. Po wybraniu wielu elementów do usunięcia, pozwolę użytkownikom nacisnąć przycisk o nazwie USUŃ WYBÓR. Jeśli użytkownik naciśnie przycisk, pojawi się okno dialogowe js z prośbą o potwierdzenie usunięcia. jeśli użytkownik potwierdzi, wszystkie pozycje zostaną usunięte.

Jak więc powinienem zająć się usuwaniem wielu elementów w PEWNY sposób?

UWAGA, obecnie dla DELETE na stronie internetowej, co robię, to używam znacznika FORM z POST jako akcją, ale dołączam _method z wartością DELETE, ponieważ jest to to, co zostało wskazane przez innych w SO, jak zrobić RESTful delete dla strony internetowej .

Kim Stacks
źródło
1
Czy ważne jest, aby te usunięcia były wykonywane atomowo? Czy na pewno chcesz cofnąć usunięcie pierwszych 30 pozycji, jeśli nie można usunąć 31.?
Darrel Miller
@darrelmiller dobre pytanie. Pomyślałem, że jeśli usunięcia zostaną wykonane atomowo, będzie to mniej wydajne. Dlatego skłaniam się ku DELETE FROM tablename WHERE ID IN ({lista identyfikatorów}). Jeśli ktoś może mi wskazać, czy to dobry pomysł, czy mnie poprawić. byłoby to bardzo mile widziane. Nie wymagam również cofania usunięcia dla pierwszych 20 pozycji, jeśli usunięto 21. Znowu doceniam to, jeśli ktoś może pokazać mi różnicę w podejściu, w którym muszę odwrócić, a gdzie NIE muszę tego robić
Kim Stacks
1
Uwaga: mogą istnieć ograniczenia dotyczące klauzuli „IN”; na przykład w Oracle można umieścić maksymalnie 1000 identyfikatorów.
okradł
Przewodnik projektowania API Google oferuje rozwiązanie do tworzenia niestandardowych (wsadowych) operacji w REST API, zobacz moją odpowiedź tutaj: stackoverflow.com/a/53264372/2477619
B12Toaster

Odpowiedzi:

54

Myślę, że odpowiedź rojoca jest jak dotąd najlepsza. Niewielką różnicą może być wyeliminowanie potwierdzenia javascript na tej samej stronie, a zamiast tego utworzenie zaznaczenia i przekierowanie do niego, wyświetlając komunikat potwierdzający na tej stronie. Innymi słowy:

Od:
http://example.com/resources/

zrób

POST z wyborem identyfikatorów do:
http://example.com/resources/selections

który, jeśli się powiedzie, powinien odpowiedzieć:

Utworzono protokół HTTP / 1.1 201 i nagłówek lokalizacji:
http://example.com/resources/selections/DF4XY7

Na tej stronie zobaczysz następnie pole potwierdzenia (javascript), które po potwierdzeniu wykona żądanie:

USUŃ http://example.com/resources/selections/DF4XY7

który, jeśli się powiedzie, powinien odpowiedzieć: HTTP / 1.1 200 Ok (lub cokolwiek jest odpowiednie do pomyślnego usunięcia)

Godny amator
źródło
Podoba mi się ten pomysł, ponieważ nie potrzebujesz żadnych przekierowań. Włączając AJAX, możesz to wszystko zrobić bez opuszczania strony.
rojoca
Czy po USUNIĘCIU example.com/resources/selections/DF4XY7 zostałbym przekierowany z powrotem do example.com/resources?
Kim Stacks
7
@fireeyeboy To dwuetapowe podejście wydaje się być tak często sugerowanym sposobem wykonywania wielu operacji usuwania, ale dlaczego? Dlaczego nie wysyłasz po prostu żądania DELETE do URI, http://example.com/resources/selections/aw ładunku (treści) żądania wysyłasz dane, dla których chcesz usunąć elementy. O ile wiem, nic nie stoi na przeszkodzie, abyś to zrobił, ale zawsze spotyka mnie „ale to nie jest RESTfull”.
thecoshman
6
DELETE może potencjalnie spowodować zignorowanie treści przez infrastrukturę HTTP: stackoverflow.com/questions/299628/ ...
Luke Puplett
DELETE może mieć ciało, ale wiele jego implementacji domyślnie zabrania jego treści
dmitryvim
54

Jedną z opcji jest utworzenie „transakcji” do usunięcia. Więc POSTdo czegoś w rodzaju http://example.com/resources/deletesnowego zasobu składającego się z listy zasobów do usunięcia. Następnie w swojej aplikacji wystarczy usunąć. Kiedy wysyłasz wiadomość, powinieneś zwrócić lokalizację utworzonej transakcji, np http://example.com/resources/deletes/DF4XY7. A GETna tym może zwrócić status transakcji (zakończona lub w toku) i / lub listę zasobów do usunięcia.

rojoca
źródło
2
Nie ma to nic wspólnego z twoją bazą danych. Przez transakcję rozumiem po prostu listę operacji do wykonania. W tym przypadku jest to lista usunięć. To, co robisz, to tworzenie nowej listy (usunięć) jako zasobu w aplikacji. Twoja aplikacja internetowa może przetwarzać tę listę w dowolny sposób. Ten zasób ma identyfikator URI, np . Example.com/resources/deletes/DF4XY7 . Oznacza to, że możesz sprawdzić stan usunięcia za pomocą GET do tego identyfikatora URI. Byłoby to przydatne, gdybyś po usunięciu musiał usunąć obrazy z Amazon S3 lub innej CDN, a ta operacja może zająć dużo czasu.
rojoca
2
+1 to fajne rozwiązanie. Zamiast wysyłać DELETE do każdego zasobu, @rojoca proponuje utworzenie instancji zasobu nowego typu, którego jedynym zadaniem jest usunięcie listy zasobów. Na przykład, masz kolekcję zasobów użytkowników i chcesz usunąć użytkowników Bob, Dave i Amy ze swojej kolekcji, więc tworzysz nowy zasób Deletion POST, podając Bob, Dave i Amy jako parametry tworzenia. Tworzony jest zasób Deletion, który reprezentuje asynchroniczny proces usuwania Roberta, Dave'a i Amy z kolekcji Users.
Mike Tunnicliffe
1
Przepraszam. Nadal mam pewne trudności ze zrozumieniem kilku kwestii. DF4XY7. jak u licha generujesz ten ciąg? Ten zasób do usunięcia. Czy muszę wstawiać dane do bazy danych? Przepraszam, jeśli powtórzę kilka pytań. To jest dla mnie trochę obce.
Kim Stacks
1
Zakładam, że DF4XY7 to wygenerowany unikalny identyfikator, być może bardziej naturalne jest użycie identyfikatora wygenerowanego podczas zapisywania w bazie danych, na przykład example.com/resources/deletes/7. Moim podejściem byłoby utworzenie modelu Deletion i zapisanie go w bazie danych, można kazać asynchronicznemu procesowi usuwania innych rekordów zaktualizować model Deletion o status ukończenia i wszelkie istotne błędy.
Mike Tunnicliffe,
2
@rojoca tak, myślę, że problem polega na tym, że HTTP jest w dużym stopniu „DELETE służy do usuwania pojedynczego zasobu”. Cokolwiek robisz, wielokrotne usuwanie to trochę hack. Nadal możesz zwrócić klientowi „zadanie”, mówiąc, że nad tym zadaniem pracujemy (i może to zająć trochę czasu), ale użyj tego identyfikatora URI, aby sprawdzić postęp. Przeczytałem specyfikację i uznałem, że DELETE może mieć treść, tak jak inne żądania.
thecoshman,
33

Oto, co Amazon zrobił ze swoim interfejsem API S3 REST.

Indywidualne żądanie usunięcia:

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

Żądanie usunięcia wielu obiektów :

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>           

Ale Facebook Graph API , Parse Server REST API i Google Drive REST API idą jeszcze dalej, umożliwiając „grupowanie” poszczególnych operacji w jednym żądaniu.

Oto przykład z Parse Server.

Indywidualne żądanie usunięcia:

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

Żądanie partii:

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
źródło
13

Powiedziałbym DELETE http://example.com/resources/id1,id2,id3,id4 lub DELETE http://example.com/resources/id1+id2+id3+id4 . Ponieważ „REST jest architekturą (…) [nie] protokołem”, cytując ten artykuł w Wikipedii, nie ma, jak sądzę, jednego sposobu na zrobienie tego.

Zdaję sobie sprawę, że powyższe nie jest możliwe bez JS z HTML, ale mam wrażenie, że REST był:

  • Stworzone bez myślenia o drobnych szczegółach, takich jak transakcje. Kto musiałby operować na więcej niż jednym elemencie? Jest to w jakiś sposób uzasadnione w protokole HTTP, ponieważ nie było to przeznaczone do obsługi przez niego niczego innego niż statyczne strony internetowe.
  • Nie trzeba dobrze dopasowywać się do obecnych modeli - nawet czystego HTML.
Maciej Piechotka
źródło
thx - co by było, gdybyś chciał usunąć całą kolekcję - czy identyfikatory powinny zostać pominięte?
BKSpurgeon
„Mam wrażenie, że REST został ... stworzony bez myślenia o drobnych szczegółach, takich jak transakcje” - nie sądzę, że to do końca prawda. Jeśli dobrze rozumiem, w REST transakcje są reprezentowane przez zasoby, a nie przez metodę. Jest dobra dyskusja, której kulminacją jest ten komentarz do tego wpisu na blogu .
Paul D. Waite
10

Co ciekawe, myślę, że ta sama metoda ma zastosowanie do PATCHOWANIA wielu jednostek i wymaga zastanowienia się, co mamy na myśli z naszym adresem URL, parametrami i metodą REST.

  1. zwraca wszystkie elementy „foo”:

    [GET] api/foo

  2. zwraca elementy „foo” z filtrowaniem pod kątem określonych identyfikatorów:

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

W jakim sensie adres URL i filtr określają „z jakimi elementami mamy do czynienia?”, A metoda REST (w tym przypadku „GET”) mówi „co zrobić z tymi elementami?”.

  1. Dlatego PATCH wiele rekordów, aby oznaczyć je jako przeczytane

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

..z danymi foo [odczyt] = 1

  1. Wreszcie, aby usunąć wiele rekordów, ten punkt końcowy jest najbardziej logiczny:

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

Proszę zrozumieć, że nie wierzę w żadne „zasady” w tym zakresie - dla mnie to po prostu „ma sens”

fezfox
źródło
Właściwie w odniesieniu do PATCH: ponieważ ma to oznaczać częściową aktualizację, jeśli myślisz o liście jednostek jako o samej encji (nawet jeśli jest to tablica typu), wysyłając częściową tablicę (tylko identyfikatory, które chcesz zaktualizować) częściowych jednostek, to może pominąć ciąg zapytania, a tym samym nie mieć adresu URL reprezentującego więcej niż jedną jednostkę.
Szabolcs Páll
2

Jak mówi odpowiedź Decent Dabbler i odpowiedź rojocas , najbardziej kanonicznym jest użycie zasobów wirtualnych do usunięcia wybranych zasobów, ale myślę, że jest to niepoprawne z perspektywy REST, ponieważ wykonanieDELETE http://example.com/resources/selections/DF4XY7 powinno usunąć sam zasób selekcji, a nie wybrane zasoby.

Biorąc pod uwagę odpowiedź Macieja Piechotki lub odpowiedź fezfox , mam tylko zastrzeżenie: istnieje bardziej kanoniczny sposób przekazywania tablicy identyfikatorów i używa operatora tablicy:

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

W ten sposób atakujesz punkt końcowy Usuń kolekcję, ale filtrujesz usunięcie za pomocą zapytania w odpowiedni sposób.

mangelsnc
źródło
-1

Ponieważ nie ma `` właściwego '' sposobu, aby to zrobić, w przeszłości zrobiłem:

wyślij DELETE na adres http://example.com/something z danymi zakodowanymi w formacie XML lub JSON w treści.

kiedy otrzymasz żądanie, sprawdź, czy nie ma opcji DELETE, jeśli prawda, a następnie przeczytaj treść tych, które mają zostać usunięte.

user103219
źródło
To podejście ma dla mnie sens, po prostu wysyłasz dane w jednym żądaniu, ale zawsze spotykam się z „ale to nie jest RESTfull”. Czy masz jakieś źródła sugerujące, że jest to wykonalna i „RESTfull” metoda, aby to zrobić?
thecoshman
10
Problem z tym podejściem polega na tym, że operacje DELETE nie oczekują treści, więc niektóre pośredniczące routery w Internecie mogą ją usunąć bez Twojej kontroli i wiedzy. Więc używanie body do DELETE nie jest bezpieczne!
Alex White
Odniesienie do komentarza Alexa: 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.z tools.ietf.org/html/rfc7231#section-4.3.5
cottton
-1

Miałem tę samą sytuację, aby usunąć wiele elementów. Oto, co ostatecznie zrobiłem. Użyłem operacji DELETE, a identyfikatory elementów, które miały zostać usunięte, były częścią nagłówka HTTP.

Sherin Syriac
źródło