Cara yang tenang untuk menghapus banyak item

98

Dalam artikel wiki untuk REST ditunjukkan bahwa jika Anda menggunakan http://example.com/resources DELETE, itu berarti Anda menghapus seluruh koleksi.

Jika Anda menggunakan http://example.com/resources/7HOU57Y DELETE, artinya Anda menghapus elemen tersebut.

Saya membuat SITUS WEB, catat BUKAN LAYANAN WEB.

Saya memiliki daftar yang memiliki 1 kotak centang untuk setiap item di daftar. Setelah saya memilih beberapa item untuk dihapus, saya akan mengizinkan pengguna untuk menekan tombol bernama DELETE SELECTION. Jika pengguna menekan tombol, kotak dialog js akan muncul meminta pengguna untuk mengkonfirmasi penghapusan. jika pengguna mengkonfirmasi, semua item akan dihapus.

Jadi, bagaimana saya harus melayani penghapusan beberapa item dengan cara yang RESTFUL?

CATATAN, saat ini untuk DELETE di halaman web, yang saya lakukan adalah menggunakan tag FORM dengan POST sebagai tindakan tetapi menyertakan _method dengan nilai DELETE karena ini adalah apa yang ditunjukkan oleh orang lain di SO tentang cara melakukan penghapusan RESTful untuk halaman web .

Kim Stacks
sumber
1
Apakah penting bahwa penghapusan ini dilakukan secara atomik? Anda yakin ingin membalikkan penghapusan 30 item pertama jika yang ke-31 tidak dapat dihapus?
Darrel Miller
@darreliller pertanyaan bagus. Saya pikir jika penghapusan dilakukan secara atomik, itu akan menjadi kurang efisien. Oleh karena itu saya condong ke DELETE FROM tablename WHERE ID IN ({list of ids}). Jika seseorang dapat menunjukkan kepada saya apakah ini ide yang bagus atau mengoreksi saya. itu akan sangat dihargai. Juga saya tidak memerlukan kebalikan dari penghapusan untuk 20 item pertama jika yang ke-21 dihapus. Sekali lagi saya menghargai jika seseorang dapat menunjukkan kepada saya perbedaan dalam pendekatan di mana saya perlu membalikkan versus di mana saya TIDAK perlu membalikkan
Kim Stacks
1
Catatan: mungkin ada batasan untuk klausa "IN"; misalnya, di Oracle, Anda dapat memasukkan maksimal 1000 id.
merampok
Panduan desain API Google menawarkan solusi untuk membuat operasi (batch) kustom di REST API, lihat jawaban saya di sini: stackoverflow.com/a/53264372/2477619
B12Toaster

Jawaban:

54

Saya pikir jawaban rojoca sejauh ini adalah yang terbaik. Sedikit variasi mungkin, untuk menghilangkan konfirmasi javascript pada halaman yang sama, dan sebaliknya, membuat pilihan dan mengarahkan ulang ke sana, menunjukkan pesan konfirmasi pada halaman itu. Dengan kata lain:

Dari:
http://example.com/resources/

lakukan a

POSTING dengan ID pilihan untuk:
http://example.com/resources/selections

yang, jika berhasil, harus merespons dengan:

HTTP / 1.1 201 dibuat, dan tajuk Lokasi ke:
http://example.com/resources/selections/DF4XY7

Pada halaman ini Anda akan melihat kotak konfirmasi (javascript), yang jika Anda konfirmasi akan melakukan permintaan:

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

yang, jika berhasil, harus merespons dengan: HTTP / 1.1 200 Ok (atau apa pun yang sesuai untuk penghapusan yang berhasil)

Dabbler yang Layak
sumber
Saya suka ide ini karena Anda tidak memerlukan pengalihan apa pun. Memasukkan AJAX Anda dapat melakukan ini semua tanpa meninggalkan halaman.
rojoca
Setelah HAPUS example.com/resources/selections/DF4XY7 ini , apakah saya akan dialihkan kembali ke example.com/resources?
Kim Stacks
7
@fireeyeboy Pendekatan dua langkah ini tampaknya merupakan cara yang umum disarankan untuk melakukan multi-delete, tetapi mengapa? Mengapa Anda tidak mudah mengirim permintaan DELETE ke uri seperti http://example.com/resources/selections/dan dalam payload (isi) permintaan Anda mengirim data untuk item mana yang ingin Anda hapus. Sejauh yang saya tahu, tidak ada yang mencegah Anda melakukan ini, tetapi saya selalu bertemu dengan "tetapi itu tidak RESTfull".
thecoshman
6
DELETE berpotensi membuat badan diabaikan oleh infrastruktur HTTP: stackoverflow.com/questions/299628/…
Luke Puplett
DELETE dapat memiliki body, tetapi banyak implementasinya yang melarang body-nya secara default
dmitryvim
54

Salah satu opsinya adalah membuat "transaksi" hapus. Jadi Anda POSTseperti http://example.com/resources/deletessumber daya baru yang terdiri dari daftar sumber daya yang akan dihapus. Kemudian di aplikasi Anda, Anda tinggal melakukan delete. Ketika Anda melakukan posting, Anda harus mengembalikan lokasi transaksi yang Anda buat, misalnya http://example.com/resources/deletes/DF4XY7. A GETini dapat mengembalikan status transaksi (selesai atau dalam proses) dan / atau daftar sumber daya yang akan dihapus.

rojoca
sumber
2
Tidak ada hubungannya dengan database Anda. Yang saya maksud dengan transaksi adalah daftar operasi yang harus dilakukan. Dalam hal ini ini adalah daftar penghapusan. Apa yang Anda lakukan adalah membuat daftar baru (dari penghapusan) sebagai sumber daya dalam aplikasi Anda. Aplikasi web Anda dapat memproses daftar itu sesuka Anda. Sumber daya tersebut memiliki URI misalnya, example.com/resources/deletes/DF4XY7 . Ini berarti Anda dapat memeriksa status penghapusan melalui GET ke URI tersebut. Ini akan berguna jika ketika Anda melakukan penghapusan Anda harus menghapus gambar dari Amazon S3 atau beberapa CDN lain dan operasi itu mungkin membutuhkan waktu lama untuk diselesaikan.
rojoca
2
+1 ini adalah solusi yang bagus. Alih-alih mengirim DELETE ke setiap sumber daya, @rojoca mengusulkan untuk membuat instance jenis sumber daya baru yang tugas utamanya adalah menghapus daftar sumber daya. Misalnya, Anda memiliki kumpulan sumber daya pengguna dan Anda ingin menghapus Pengguna Bob, Dave, dan Amy dari koleksi Anda, jadi Anda membuat sumber daya Penghapusan baru POSTing Bob, Dave, dan Amy sebagai parameter pembuatan. Sumber daya Penghapusan dibuat, dan mewakili proses asinkron untuk menghapus Bob, Dave, dan Amy dari koleksi Pengguna.
Mike Tunnicliffe
1
Saya menyesal. Saya masih mengalami sedikit kesulitan dalam memahami beberapa masalah. DF4XY7. bagaimana Anda menghasilkan string ini? Sumber daya Penghapusan ini. Apakah saya perlu memasukkan data apa pun ke dalam database? Saya minta maaf jika saya mengulangi beberapa pertanyaan. Itu hanya sedikit asing bagi saya.
Kim Stacks
1
Saya berasumsi DF4XY7 adalah id unik yang dihasilkan, mungkin lebih wajar jika hanya menggunakan id yang dihasilkan saat disimpan ke DB, misalnya example.com/resources/deletes/7. Saya mengambil akan membuat model Penghapusan dan menyimpannya dalam database, Anda dapat memiliki proses asynchronous menghapus catatan lain memperbarui model Penghapusan dengan status penyelesaian, dan kesalahan yang relevan.
Mike Tunnicliffe
2
@rojoca ya, saya pikir masalahnya adalah HTTP sangat banyak 'HAPUS untuk menghapus satu sumber daya'. Apa pun yang Anda lakukan, mendapatkan banyak penghapusan adalah sedikit retasan. Anda masih bisa mengembalikan 'pekerjaan' ke klien yang mengatakan bahwa tugas ini sedang dikerjakan (dan bisa memakan waktu lama) tetapi gunakan URI ini untuk memeriksa kemajuannya. Saya membaca spesifikasi dan menganggap bahwa DELETE dapat memiliki badan, seperti permintaan lainnya.
thecoshman
33

Inilah yang dilakukan Amazon dengan S3 REST API mereka.

Permintaan penghapusan individu:

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

Permintaan Hapus Multi-Objek :

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>           

Tetapi Facebook Graph API , Parse Server REST API, dan Google Drive REST API melangkah lebih jauh dengan memungkinkan Anda untuk "menumpuk" operasi individual dalam satu permintaan.

Berikut contoh dari Parse Server.

Permintaan penghapusan individu:

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

Permintaan 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
Luka Žitnik
sumber
13

Saya akan mengatakan HAPUS http://example.com/resources/id1,id2,id3,id4 atau HAPUS http://example.com/resources/id1+id2+id3+id4 . Karena "REST adalah arsitektur (...) [bukan] protokol" untuk mengutip artikel wikipedia ini, saya yakin, tidak ada satu cara pun untuk melakukan ini.

Saya sadar bahwa di atas tidak mungkin tanpa JS dengan HTML tetapi saya merasa REST adalah:

  • Dibuat tanpa memikirkan detail kecil seperti transaksi. Siapa yang perlu mengoperasikan lebih dari satu item? Ini entah bagaimana dibenarkan dalam protokol HTTP karena tidak dimaksudkan untuk melayani melalui itu apa pun selain halaman web statis.
  • Tidak perlu menyesuaikan dengan baik ke model saat ini - bahkan HTML murni.
Maciej Piechotka
sumber
thx - bagaimana jika Anda ingin menghapus seluruh koleksi - apakah ID harus dihilangkan?
BKSpurgeon
"Saya merasa REST ... dibuat tanpa memikirkan detail kecil seperti transaksi" - Saya rasa itu tidak sepenuhnya benar. Jika saya mengerti dengan benar, di REST, transaksi diwakili oleh sumber daya, bukan dengan metode. Ada beberapa diskusi bagus yang berujung pada komentar di postingan blog ini .
Paul D. Waite
10

Menariknya, saya pikir metode yang sama berlaku untuk PATCHing beberapa entitas, dan memerlukan pemikiran tentang apa yang kami maksud dengan URL, parameter, dan metode REST kami.

  1. kembalikan semua elemen 'foo':

    [GET] api/foo

  2. kembalikan elemen 'foo' dengan pemfilteran untuk id tertentu:

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

Dimana artinya adalah URL dan filter menentukan "elemen apa yang kita hadapi?", Dan metode REST (dalam hal ini "GET") mengatakan "apa yang harus dilakukan dengan elemen tersebut?"

  1. Oleh karena itu PATCH beberapa record untuk menandainya sebagai telah dibaca

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

..dengan data foo [read] = 1

  1. Terakhir untuk menghapus beberapa rekaman, titik akhir ini paling logis:

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

Tolong mengerti, saya tidak percaya ada "aturan" tentang ini - bagi saya itu "masuk akal"

fezfox.dll
sumber
Sebenarnya tentang PATCH: karena ini dimaksudkan untuk memperbarui sebagian jika Anda berpikir tentang daftar entitas sebagai entitas itu sendiri (meskipun bertipe array), mengirimkan array parsial (hanya id yang ingin Anda perbarui) dari entitas parsial, maka Anda dapat meninggalkan string kueri, sehingga tidak memiliki URL yang mewakili lebih dari satu entitas.
Szabolcs Páll
2

Seperti yang dikatakan jawaban Decent Dabbler dan jawaban rojocas , yang paling kanonik adalah menggunakan sumber daya virtual untuk menghapus pilihan sumber daya, tetapi menurut saya itu salah dari perspektif REST, karena menjalankan a DELETE http://example.com/resources/selections/DF4XY7harus menghapus sumber daya pilihan itu sendiri, bukan sumber daya yang dipilih.

Mengambil anwser Maciej Piechotka atau jawaban fezfox , saya hanya memiliki keberatan: Ada cara yang lebih kanonik untuk melewatkan array id, dan menggunakan operator array:

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

Dengan cara ini Anda menyerang ke titik akhir Koleksi Hapus tetapi memfilter penghapusan dengan string kueri dengan cara yang benar.

mangelsnc
sumber
-1

Karena tidak ada cara yang 'tepat' untuk melakukan ini, apa yang telah saya lakukan di masa lalu adalah:

kirim HAPUS ke http://example.com/something dengan data yang dikodekan xml atau json di bagian isi.

ketika Anda menerima permintaan, periksa HAPUS, jika benar, kemudian baca isi untuk yang akan dihapus.

pengguna103219
sumber
Ini adalah pendekatan yang masuk akal bagi saya, Anda cukup mengirim data dalam satu permintaan, namun saya selalu bertemu dengan "tetapi tidak RESTfull". Apakah Anda punya sumber yang menyatakan bahwa ini adalah metode yang layak dan 'RESTfull' untuk melakukan ini?
thecoshman
10
Masalah dengan pendekatan ini adalah bahwa operasi DELETE tidak mengharapkan tubuh, dan beberapa perute perantara di internet dapat menghapusnya untuk Anda tanpa kendali atau sepengetahuan Anda. Jadi menggunakan tubuh untuk HAPUS tidak aman!
Alex White
Referensi untuk komentar 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.dari tools.ietf.org/html/rfc7231#section-4.3.5
cottton
-1

Saya memiliki situasi yang sama untuk menghapus beberapa item. Inilah yang akhirnya saya lakukan. Saya menggunakan operasi DELETE dan id item yang akan dihapus adalah bagian dari header HTTP.

Sherin Syriac
sumber