Bagaimana seharusnya REST API menangani permintaan PUT ke sumber daya yang dapat dimodifikasi sebagian?

46

Misalkan REST API, sebagai tanggapan atas GETpermintaan HTTP , mengembalikan beberapa data tambahan dalam sub-objek owner:

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

Jelas, kami tidak ingin siapa pun dapat PUTkembali

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

dan telah berhasil itu. Memang, kami mungkin tidak akan menerapkan cara untuk itu bahkan berpotensi berhasil, dalam hal ini.

Tetapi pertanyaan ini bukan hanya tentang sub-objek: apa, secara umum, yang harus dilakukan dengan data yang tidak boleh dimodifikasi dalam permintaan PUT?

Haruskah dihilangkan dari permintaan PUT?

Haruskah itu dibuang secara diam-diam?

Haruskah dicentang, dan jika berbeda dari nilai atribut yang lama, kembalikan kode kesalahan HTTP dalam respons?

Atau haruskah kita menggunakan patch JSON RFC 6902 alih-alih mengirim seluruh JSON?

Robin Green
sumber
2
Semua ini akan berhasil. Saya kira itu tergantung pada kebutuhan Anda.
Robert Harvey
Saya akan mengatakan bahwa prinsip kejutan paling tidak akan menunjukkan itu harus hilang dari permintaan PUT. Jika tidak memungkinkan maka periksa dan apakah berbeda dan kembali dengan kode kesalahan. Pembuangan diam adalah yang terburuk (pengiriman pengguna berharap itu akan berubah dan Anda memberi tahu mereka "200 OK").
Maciej Piechotka
2
@MaciejPiechotka masalah dengan itu adalah Anda tidak bisa menggunakan model yang sama pada put seperti pada sisipkan atau dapatkan dll, saya lebih suka model yang sama digunakan dan ada aturan lapangan otorisasi sederhana jadi jika mereka memasukkan nilai untuk sebuah bidang yang tidak boleh mereka ubah, mereka mendapatkan kembali 403 Forbidden, dan jika nantinya otorisasi diatur untuk memperbolehkannya, mereka mendapatkan 401 Tidak Sah jika mereka tidak diotorisasi
Jimmy Hoffa
@ JimmyHoffa: Dengan model yang Anda maksud format data (karena mungkin untuk menggunakan kembali model dalam kerangka kerja MVC Istirahat tergantung pada pilihan itu, jika ada yang digunakan - OP tidak disebutkan ada)? Saya akan pergi dengan mudah ditemukan jika saya tidak dibatasi oleh kerangka kerja dan kesalahan awal sedikit lebih dapat ditemukan / mudah diterapkan kemudian memeriksa perubahan (ok - saya tidak boleh menyentuh bidang XYZ). Bagaimanapun, membuang adalah yang terburuk.
Maciej Piechotka

Jawaban:

46

Tidak ada aturan, baik dalam spesifikasi W3C atau aturan tidak resmi REST, yang mengatakan bahwa a PUTharus menggunakan skema / model yang sama dengan yang sesuai GET.

Sangat menyenangkan jika mereka serupa , tetapi itu tidak biasa untuk PUTmelakukan hal-hal yang sedikit berbeda. Misalnya, saya telah melihat banyak API yang menyertakan beberapa jenis ID dalam konten yang dikembalikan oleh GET, untuk kenyamanan. Tetapi dengan a PUT, ID itu ditentukan secara eksklusif oleh URI dan tidak memiliki makna dalam konten. ID apa pun yang ditemukan dalam tubuh akan diabaikan secara diam-diam.

REST dan web pada umumnya sangat terikat dengan Prinsip Robustness : "Jadilah konservatif dalam apa yang Anda kirim, menjadi liberal dalam apa yang Anda terima." Jika Anda setuju secara filosofis dengan ini, maka solusinya jelas: Abaikan data yang tidak valid dalam PUTpermintaan. Itu berlaku untuk data yang tidak dapat diubah, seperti pada contoh Anda, dan omong kosong yang sebenarnya, misalnya bidang yang tidak dikenal.

PATCHberpotensi pilihan lain, tetapi Anda tidak boleh menerapkan PATCHkecuali Anda benar-benar akan mendukung pembaruan parsial. PATCHberarti hanya memperbarui atribut spesifik yang saya sertakan dalam konten ; itu tidak berarti mengganti seluruh entitas tetapi mengecualikan beberapa bidang tertentu . Apa yang sebenarnya Anda bicarakan sebenarnya bukan pembaruan parsial, ini adalah pembaruan penuh, idempoten dan semuanya, hanya saja sebagian dari sumber daya tersebut hanya baca-saja.

Suatu hal yang menyenangkan untuk dilakukan jika Anda memilih opsi ini adalah mengirim kembali 200 (OK) dengan entitas yang sebenarnya diperbarui dalam respons, sehingga klien dapat melihat dengan jelas bahwa bidang baca-saja tidak diperbarui.

Pasti ada beberapa orang yang berpikir sebaliknya - bahwa itu seharusnya merupakan kesalahan untuk mencoba memperbarui bagian read-only dari sumber daya. Ada beberapa pembenaran untuk ini, terutama atas dasar bahwa Anda pasti akan mengembalikan kesalahan jika seluruh sumber daya hanya-baca dan pengguna mencoba memperbaruinya. Ini jelas bertentangan dengan prinsip ketahanan, tetapi Anda mungkin menganggapnya lebih "mendokumentasikan diri sendiri" untuk pengguna API Anda.

Ada dua konvensi untuk ini, keduanya sesuai dengan ide asli Anda, tetapi saya akan mengembangkannya. Yang pertama adalah untuk melarang bidang baca-saja muncul di konten, dan mengembalikan HTTP 400 (Permintaan Buruk) jika mereka melakukannya. API semacam ini juga harus mengembalikan HTTP 400 jika ada bidang lain yang tidak dikenal / tidak dapat digunakan. Yang kedua adalah mengharuskan bidang baca-saja identik dengan konten saat ini, dan mengembalikan 409 (Konflik) jika nilainya tidak cocok.

Saya sangat tidak suka dengan pemeriksaan kesetaraan dengan 409 karena selalu membutuhkan klien untuk melakukan GETuntuk mengambil data saat ini sebelum dapat melakukan PUT. Itu tidak baik dan mungkin akan menyebabkan kinerja yang buruk, untuk seseorang, di suatu tempat. Saya juga sangat tidak suka 403 (Terlarang) untuk ini karena ini menyiratkan bahwa seluruh sumber daya dilindungi, bukan hanya bagian dari itu. Jadi pendapat saya adalah, jika Anda benar-benar harus memvalidasi alih-alih mengikuti prinsip ketahanan, validasi semua permintaan Anda dan kembalikan 400 untuk apa pun yang memiliki bidang tambahan atau tidak dapat ditulis.

Pastikan 400/409 / apa pun Anda termasuk informasi tentang apa masalah spesifik itu dan bagaimana cara memperbaikinya.

Kedua pendekatan ini valid, tetapi saya lebih suka yang pertama sesuai dengan prinsip ketahanan. Jika Anda pernah mengalami bekerja dengan API REST besar, Anda akan menghargai nilai kompatibilitas ke belakang. Jika Anda pernah memutuskan untuk menghapus bidang yang ada atau membuatnya hanya-baca, itu adalah perubahan yang kompatibel jika server mengabaikan bidang-bidang itu, dan klien lama masih akan bekerja. Namun, jika Anda melakukan validasi ketat pada konten, itu tidak kompatibel lagi, dan klien lama akan berhenti bekerja. Yang pertama umumnya berarti lebih sedikit pekerjaan untuk pengelola API dan kliennya.

Aaronaught
sumber
1
Jawaban bagus, dan terangkat. Namun, saya tidak yakin saya setuju dengan ini: "Jika Anda pernah memutuskan untuk menghapus bidang yang ada atau membuatnya hanya-baca, itu adalah perubahan yang kompatibel jika server mengabaikan bidang-bidang itu, dan klien lama masih akan bekerja. " Jika klien mengandalkan bidang yang dihapus / baru saja dibaca ini, bukankah ini masih akan memengaruhi perilaku keseluruhan aplikasi? Dalam hal menghapus bidang, saya berpendapat mungkin lebih baik secara eksplisit menghasilkan kesalahan daripada mengabaikan data; jika tidak, klien tidak tahu bahwa pembaruan yang sebelumnya berfungsi sekarang gagal.
rinogo
Jawaban ini salah. karena 2 alasan dari RFC2616: 1. (bagian 9.1.2) PUT harus independen. Masukkan berkali-kali dan itu akan menghasilkan hasil yang sama seperti menempatkan hanya sekali. 2.
Akses
1
Bagaimana jika Anda melakukan pemeriksaan kesetaraan hanya jika nilai yang tidak dapat diubah dikirim dalam permintaan. Saya pikir ini memberi Anda yang terbaik dari dua dunia; Anda tidak memaksa klien untuk melakukan GET, dan Anda masih memberi tahu mereka ada sesuatu yang salah jika mereka mengirim nilai yang tidak valid untuk yang tidak dapat diubah.
Ahmad Abdelghany
Terima kasih, perbandingan mendalam yang Anda lakukan pada paragraf terakhir yang berasal dari pengalaman adalah persis apa yang saya cari.
dhill
9

Potensi idem

Setelah RFC, seorang PUT harus mengirimkan objek penuh ke sumber daya. Alasan utama ini, adalah bahwa PUT harus idempoten. Ini berarti permintaan, yang diulang harus mengevaluasi hasil yang sama di server.

Jika Anda mengizinkan pembaruan parsial, itu tidak bisa lagi menjadi potensi. Jika Anda memiliki dua klien. Klien A dan B, maka skenario berikut dapat berkembang:

Klien A mendapatkan gambar dari gambar sumber. Ini berisi deskripsi gambar, yang masih valid. Klien B menempatkan gambar baru dan memperbarui deskripsi yang sesuai. Gambar telah berubah. Klien A melihat, dia tidak perlu mengubah deskripsi, karena itu sesuai keinginannya dan hanya menempatkan gambar.

Ini akan menyebabkan inkonsistensi, gambar memiliki metadata yang salah terpasang!

Yang lebih menjengkelkan adalah bahwa perantara mana pun dapat mengulangi permintaan itu. Dalam kasus itu memutuskan entah bagaimana PUT gagal.

Arti PUT tidak dapat diubah (meskipun Anda dapat menyalahgunakannya).

Pilihan lain

Untungnya ada opsi lain, ini adalah PATCH. PATCH adalah metode yang memungkinkan Anda memperbarui sebagian struktur. Anda cukup mengirim struktur parsial. Untuk aplikasi sederhana, ini baik-baik saja. Metode ini tidak dijamin ampuh. Klien harus mengirim permintaan dalam bentuk berikut:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

Dan server dapat membalas kembali dengan 204 (Tidak ada konten) untuk menandai keberhasilan. Kesalahan Anda tidak dapat memperbarui bagian dari struktur. Metode PATCH adalah atom.

Kelemahan dari metode ini adalah, tidak semua browser mendukung ini, tetapi ini adalah opsi paling alami dalam layanan REST.

Contoh tambalan permintaan: http://tools.ietf.org/html/rfc5789#section-2.1

Json menambal

Opsi json tampaknya cukup komprehensif dan opsi yang menarik. Tetapi bisa sulit untuk diterapkan untuk pihak ketiga. Anda harus memutuskan apakah basis pengguna Anda dapat menangani ini.

Ini juga agak berbelit-belit, karena Anda perlu membangun penerjemah kecil yang mengubah perintah menjadi struktur parsial, yang akan Anda gunakan untuk memperbarui model Anda. Penerjemah ini juga harus memeriksa, apakah perintah yang diberikan masuk akal. Beberapa perintah membatalkan satu sama lain. (tulis fielda, hapus fielda). Saya pikir Anda ingin melaporkan ini kembali ke klien untuk membatasi waktu debug di pihaknya.

Tetapi jika Anda punya waktu, ini adalah solusi yang sangat elegan. Anda masih harus memvalidasi bidang tentu saja. Anda dapat menggabungkan ini dengan metode PATCH untuk tetap menjadi model REST. Tapi saya pikir POST dapat diterima di sini.

Menjadi buruk

Jika Anda memutuskan untuk menggunakan opsi PUT, yang agak berisiko. Maka Anda setidaknya tidak harus membuang kesalahan. Pengguna memiliki harapan tertentu (data akan diperbarui) dan jika Anda memecahkan ini, Anda akan memberikan beberapa pengembang bukan waktu yang baik.

Anda dapat memilih untuk mundur: 409 Konflik atau 403 Dilarang. Tergantung bagaimana Anda melihat proses pembaruan. Jika Anda melihatnya sebagai seperangkat aturan (sistem-sentris), maka konflik akan lebih baik. Sesuatu seperti, bidang ini tidak dapat diperbarui. (Bertentangan dengan aturan). Jika Anda melihatnya sebagai masalah otorisasi (user-centric), maka Anda harus kembali terlarang. Dengan: Anda tidak diizinkan mengubah bidang ini.

Anda masih harus memaksa pengguna untuk mengirim semua bidang yang dapat dimodifikasi.

Pilihan yang masuk akal untuk menegakkan ini adalah untuk mengaturnya ke sub-sumber daya, yang hanya menawarkan data yang dapat dimodifikasi.

Opini pribadi

Secara pribadi saya akan pergi (jika Anda tidak harus bekerja dengan browser) untuk model PATCH sederhana dan kemudian memperpanjangnya dengan prosesor patch JSON. Ini dapat dilakukan dengan membedakan pada mimetypes: Tipe mime dari patch json:

aplikasi / json-patch

Dan json: application / json-patch

membuatnya mudah untuk mengimplementasikannya dalam dua fase.

Edgar Klerks
sumber
3
Contoh idempotensi Anda tidak masuk akal. Entah Anda mengubah deskripsi atau tidak. Bagaimanapun, Anda akan mendapatkan hasil yang sama, setiap saat.
Robert Harvey
1
Anda benar, saya pikir sudah waktunya tidur. Saya tidak bisa mengeditnya. Ini lebih merupakan contoh tentang rasional mengirim semua data dalam permintaan PUT. Terima kasih untuk penunjuknya.
Edgar Klerks
Saya tahu ini 3 tahun yang lalu ... tetapi apakah Anda tahu di mana di RFC saya dapat menemukan informasi lebih lanjut tentang "PUT harus mengirimkan objek penuh ke sumber daya." Saya telah melihat ini disebutkan di tempat lain tetapi ingin melihat bagaimana hal itu didefinisikan dalam spesifikasi.
CSharper
Saya rasa saya menemukannya? tools.ietf.org/html/rfc5789#halaman-3
CSharper