Penggunaan metode PUT vs PATCH dalam skenario kehidupan nyata API

682

Pertama-tama, beberapa definisi:

PUT didefinisikan dalam Bagian 9.6 RFC 2616 :

Metode PUT meminta entitas terlampir disimpan di bawah Request-URI yang disediakan. Jika Request-URI mengacu pada sumber daya yang sudah ada, entitas terlampir HARUS dianggap sebagai versi modifikasi dari yang berada di server asal . Jika Request-URI tidak menunjuk ke sumber daya yang ada, dan bahwa URI mampu didefinisikan sebagai sumber daya baru oleh agen pengguna yang meminta, server asal dapat membuat sumber daya dengan URI itu.

PATCH didefinisikan dalam RFC 5789 :

Metode PATCH meminta agar serangkaian perubahan yang dijelaskan dalam entitas permintaan diterapkan ke sumber daya yang diidentifikasi oleh Request-URI.

Juga menurut RFC 2616 Bagian 9.1.2 PUT adalah Idempoten sedangkan PATCH tidak.

Sekarang mari kita lihat contoh nyata. Ketika saya melakukan POST /usersdengan data {username: 'skwee357', email: '[email protected]'}dan server mampu membuat sumber daya, ia akan merespons dengan 201 dan lokasi sumber daya (mari kita asumsikan /users/1) dan panggilan berikutnya ke GET /users/1akan kembali {id: 1, username: 'skwee357', email: '[email protected]'}.

Sekarang katakanlah saya ingin mengubah email saya. Modifikasi email dianggap "seperangkat perubahan" dan oleh karena itu saya harus PATCH /users/1dengan " patch dokumen ". Dalam kasus saya itu akan menjadi dokumen json: {email: '[email protected]'}. Server kemudian mengembalikan 200 (dengan asumsi izin ok). Ini membawa saya ke pertanyaan pertama:

  • PATCH BUKAN idempoten. Dikatakan demikian di RFC 2616 dan RFC 5789. Namun jika saya mengeluarkan permintaan PATCH yang sama (dengan email baru saya), saya akan mendapatkan status sumber daya yang sama (dengan email saya diubah ke nilai yang diminta). Mengapa PATCH tidak idempoten?

PATCH adalah kata kerja yang relatif baru (RFC diperkenalkan pada Maret 2010), dan ia datang untuk memecahkan masalah "menambal" atau memodifikasi seperangkat bidang. Sebelum PATCH diperkenalkan, semua orang menggunakan PUT untuk memperbarui sumber daya. Tetapi setelah PATCH diperkenalkan, itu membuat saya bingung tentang untuk apa PUT digunakan. Dan ini membawa saya ke pertanyaan kedua (dan utama) saya:

  • Apa perbedaan nyata antara PUT dan PATCH? Saya telah membaca di suatu tempat bahwa PUT dapat digunakan untuk menggantikan seluruh entitas di bawah sumber daya tertentu, jadi orang harus mengirim entitas penuh (alih-alih set atribut seperti dengan PATCH). Apa penggunaan praktis nyata untuk kasus seperti itu? Kapan Anda ingin mengganti / menimpa entitas pada URI sumber daya tertentu dan mengapa operasi seperti itu tidak dianggap memperbarui / menambal entitas? Satu-satunya kasus penggunaan praktis yang saya lihat untuk PUT adalah mengeluarkan PUT pada koleksi, yaitu /usersuntuk mengganti seluruh koleksi. Mengeluarkan PUT pada entitas tertentu tidak masuk akal setelah PATCH diperkenalkan. Apakah aku salah?
Dmitry Kudryavtsev
sumber
1
a) RFC 2616, bukan 2612. b) RFC 2616 telah usang, spesifikasi PUT saat ini ada di greenbytes.de/tech/webdav/rfc7231.html#PUT , c) Saya tidak mendapatkan pertanyaan Anda; bukankah sudah cukup jelas bahwa PUT dapat digunakan untuk mengganti sumber daya apa pun, tidak hanya koleksi, d) sebelum PATCH diperkenalkan, orang biasanya menggunakan POST, e) akhirnya, ya, permintaan PATCH tertentu (tergantung pada format patch) bisa idempoten; hanya saja itu tidak umum.
Julian Reschke
jika itu membantu saya menulis sebuah artikel tentang PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalent8
5
Sederhana: POST membuat item dalam koleksi. PUT menggantikan item. PATCH memodifikasi suatu item. Saat POSTing, URL untuk item baru dihitung dan dikembalikan dalam respons, sedangkan PUT dan PATCH memerlukan URL dalam permintaan. Baik?
Tom Russell
Posting ini mungkin berguna: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Jawaban:

943

CATATAN : Ketika saya pertama kali menghabiskan waktu membaca tentang REST, idempoten adalah konsep yang membingungkan untuk dicoba. Saya masih belum mengerti jawaban asli saya, karena komentar lebih lanjut (dan ditunjukkan oleh jawaban Jason Hoetger ). Untuk sementara, saya telah menolak memperbarui jawaban ini secara ekstensif, untuk menghindari menjiplak Jason secara efektif, tetapi saya mengeditnya sekarang karena, yah, saya diminta (dalam komentar).

Setelah membaca jawaban saya, saya sarankan Anda juga membaca jawaban Jason Hoetger yang luar biasa untuk pertanyaan ini, dan saya akan mencoba membuat jawaban saya lebih baik tanpa hanya mencuri dari Jason.

Mengapa PUT idempoten?

Seperti yang Anda catat dalam kutipan RFC 2616 Anda, PUT dianggap idempoten. Ketika Anda PUT sumber daya, dua asumsi ini dalam permainan:

  1. Anda merujuk pada suatu entitas, bukan ke koleksi.

  2. Entitas yang Anda berikan sudah lengkap ( seluruh entitas).

Mari kita lihat salah satu contoh Anda.

{ "username": "skwee357", "email": "[email protected]" }

Jika Anda POST dokumen ini /users, seperti yang Anda sarankan, maka Anda mungkin mendapatkan kembali entitas seperti

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

Jika Anda ingin memodifikasi entitas ini nanti, Anda memilih antara PUT dan PATCH. PUT mungkin terlihat seperti ini:

PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}

Anda dapat melakukan hal yang sama menggunakan PATCH. Itu mungkin terlihat seperti ini:

PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

Anda akan segera melihat perbedaan antara keduanya. PUT menyertakan semua parameter pada pengguna ini, tetapi PATCH hanya menyertakan parameter yang sedang dimodifikasi ( email).

Saat menggunakan PUT, diasumsikan bahwa Anda mengirim entitas yang lengkap, dan entitas yang lengkap itu menggantikan entitas yang ada di URI itu. Pada contoh di atas, PUT dan PATCH mencapai tujuan yang sama: keduanya mengubah alamat email pengguna ini. Tapi PUT menanganinya dengan mengganti seluruh entitas, sementara PATCH hanya memperbarui bidang yang disediakan, meninggalkan yang lain sendirian.

Karena permintaan PUT menyertakan seluruh entitas, jika Anda mengeluarkan permintaan yang sama berulang kali, itu harus selalu memiliki hasil yang sama (data yang Anda kirim sekarang adalah seluruh data entitas). Karena itu PUT idempoten.

Menggunakan PUT salah

Apa yang terjadi jika Anda menggunakan data PATCH di atas dalam permintaan PUT?

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // new email address... and nothing else!
}

(Saya berasumsi untuk keperluan pertanyaan ini bahwa server tidak memiliki bidang khusus yang diperlukan, dan akan memungkinkan ini terjadi ... yang mungkin tidak terjadi pada kenyataannya.)

Karena kami menggunakan PUT, tetapi hanya memasok email, sekarang itulah satu-satunya hal dalam entitas ini. Ini telah menyebabkan hilangnya data.

Contoh ini ada di sini untuk tujuan ilustrasi - jangan pernah benar-benar melakukan ini. Permintaan PUT ini secara teknis idempoten, tetapi itu tidak berarti itu bukan ide yang buruk dan rusak.

Bagaimana PATCH bisa idempoten?

Dalam contoh di atas, PATCH adalah idempoten. Anda membuat perubahan, tetapi jika Anda membuat perubahan yang sama berulang kali, itu akan selalu memberikan hasil yang sama: Anda mengubah alamat email ke nilai yang baru.

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // nothing changed since last GET
}

Contoh asli saya, diperbaiki untuk akurasi

Saya awalnya punya contoh yang saya pikir menunjukkan non-idempotency, tetapi mereka menyesatkan / salah. Saya akan menyimpan contoh-contohnya, tetapi menggunakannya untuk mengilustrasikan hal yang berbeda: bahwa banyak dokumen PATCH terhadap entitas yang sama, memodifikasi atribut yang berbeda, tidak membuat PATCH non-idempoten.

Katakanlah pada waktu yang lalu, pengguna telah ditambahkan. Ini adalah negara tempat Anda memulai.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Setelah PATCH, Anda memiliki entitas yang dimodifikasi:

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Jika Anda berulang kali menerapkan PATCH, Anda akan terus mendapatkan hasil yang sama: email diubah ke nilai baru. A masuk, A keluar, oleh karena itu idempoten.

Satu jam kemudian, setelah Anda membuat kopi dan beristirahat, orang lain datang dengan PATCH mereka sendiri. Tampaknya Kantor Pos telah melakukan beberapa perubahan.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Karena PATCH dari kantor pos ini tidak berkaitan dengan email, hanya kode pos, jika diterapkan berulang kali, itu juga akan mendapatkan hasil yang sama: kode pos disetel ke nilai baru. A masuk, A keluar, oleh karena itu ini juga idempoten.

Hari berikutnya, Anda memutuskan untuk mengirim PATCH lagi.

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Tambalan Anda memiliki efek yang sama dengan kemarin: ia menetapkan alamat email. A masuk, A keluar, oleh karena itu ini idempoten juga.

Apa yang salah dalam jawaban awal saya

Saya ingin menggambar perbedaan penting (sesuatu yang saya salah dalam jawaban asli saya). Banyak server akan menanggapi permintaan REST Anda dengan mengirim kembali status entitas baru, dengan modifikasi Anda (jika ada). Jadi, ketika Anda mendapatkan respons ini kembali, itu berbeda dari yang Anda dapatkan kemarin , karena kode pos bukan yang Anda terima terakhir kali. Namun, permintaan Anda tidak terkait dengan kode pos, hanya dengan email. Jadi dokumen PATCH Anda masih idempoten - email yang Anda kirim di PATCH sekarang adalah alamat email pada entitas.

Jadi kapan PATCH tidak idempoten?

Untuk perawatan lengkap dari pertanyaan ini, saya kembali merujuk Anda ke jawaban Jason Hoetger . Saya hanya akan berhenti di situ, karena saya jujur ​​tidak berpikir saya bisa menjawab bagian ini lebih baik daripada yang sudah dia miliki.

Dan Lowe
sumber
2
Kalimat ini tidak sepenuhnya benar: "Tapi itu idempoten: setiap kali A masuk, B selalu keluar". Misalnya, jika Anda GET /users/1sebelum Kantor Pos memperbarui kode pos dan kemudian membuat GET /users/1permintaan yang sama setelah pembaruan Kantor Pos, Anda akan mendapatkan dua tanggapan berbeda (kode pos berbeda). "A" (GET permintaan) yang sama akan masuk, tetapi Anda mendapatkan hasil yang berbeda. Namun GET masih idempoten.
Jason Hoetger
@JasonHoetger GET aman (dianggap tidak menyebabkan perubahan), tetapi tidak selalu idempoten. Ada perbedaan. Lihat RFC 2616 dtk. 9.1 .
Dan Lowe
1
@DanLowe: DAPATKAN pasti dijamin idempoten. Dikatakan dengan tepat bahwa dalam Bagian 9.1.2 dari RFC 2616, dan dalam spesifikasi yang diperbarui, RFC 7231 bagian 4.2.2 , bahwa "Dari metode permintaan yang ditentukan oleh spesifikasi ini, PUT, HAPUS, dan metode permintaan yang aman adalah idempoten." Idempotensi tidak berarti "Anda mendapatkan respons yang sama setiap kali Anda membuat permintaan yang sama". 7231 4.2.2 selanjutnya mengatakan: "Mengulangi permintaan akan memiliki efek yang dimaksudkan sama, bahkan jika permintaan asli berhasil, meskipun responsnya mungkin berbeda. "
Jason Hoetger
1
@JasonHoetger saya akan mengakui itu, tapi saya tidak melihat apa hubungannya dengan jawaban ini, yang membahas PUT dan PATCH dan bahkan tidak pernah menyebutkan GET ...
Dan Lowe
1
Ah, komentar dari @JasonHoetger menjernihkannya: hanya status yang dihasilkan, alih-alih respons, dari beberapa metode permintaan idempoten yang harus identik.
Tom Russell
329

Meskipun jawaban Dan Lowe yang luar biasa menjawab pertanyaan OP tentang perbedaan antara PUT dan PATCH, jawabannya terhadap pertanyaan mengapa PATCH tidak idempoten tidak sepenuhnya benar.

Untuk menunjukkan mengapa PATCH tidak idempoten, ada baiknya Anda mulai dengan definisi idempoten (dari Wikipedia ):

Istilah idempoten digunakan lebih komprehensif untuk menggambarkan suatu operasi yang akan menghasilkan hasil yang sama jika dijalankan sekali atau beberapa kali [...] Fungsi idempoten adalah yang memiliki properti f (f (x)) = f (x) untuk nilai apa pun x.

Dalam bahasa yang lebih mudah diakses, PATCH idempoten dapat didefinisikan sebagai: Setelah PATCH sumber daya dengan dokumen tambalan, semua panggilan PATCH berikutnya ke sumber daya yang sama dengan dokumen tambalan yang sama tidak akan mengubah sumber daya.

Sebaliknya, operasi non-idempoten adalah operasi di mana f (f (x))! = F (x), yang untuk PATCH dapat dinyatakan sebagai: Setelah PATCH sumber daya dengan dokumen tambalan, PATCH berikutnya memanggil ke sumber daya yang sama dengan dokumen patch yang sama melakukan mengubah sumber daya.

Untuk mengilustrasikan PATCH non-idempoten, misalkan ada sumber daya / pengguna, dan anggap bahwa panggilan GET /usersmengembalikan daftar pengguna, saat ini:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]

Daripada PATCHing / users / {id}, seperti dalam contoh OP, misalkan server mengizinkan PATCHing / pengguna. Mari kita ajukan permintaan PATCH ini:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]

Dokumen tambalan kami menginstruksikan server untuk menambahkan pengguna baru yang dipanggil newuserke daftar pengguna. Setelah menelepon ini pertama kali, GET /usersakan kembali:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" }]

Sekarang, jika kami mengeluarkan permintaan PATCH yang sama persis seperti di atas, apa yang terjadi? (Demi contoh ini, mari kita asumsikan bahwa sumber daya / pengguna memungkinkan duplikat nama pengguna.) "Op" adalah "add", jadi pengguna baru ditambahkan ke daftar, dan GET /userspengembalian berikutnya :

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" },
 { "id": 3, "username": "newuser", "email": "[email protected]" }]

The / pengguna sumber daya telah berubah lagi , meskipun kita mengeluarkan yang sama persis PATCH terhadap yang sama persis titik akhir. Jika PATCH kami adalah f (x), f (f (x)) tidak sama dengan f (x), dan karenanya, PATCH khusus ini tidak idempoten .

Meskipun PATCH tidak dijamin sebagai idempoten, tidak ada dalam spesifikasi PATCH untuk mencegah Anda membuat semua operasi PATCH pada server idempoten Anda. RFC 5789 bahkan mengantisipasi keuntungan dari permintaan PATCH idempoten:

Permintaan PATCH dapat dikeluarkan sedemikian rupa sehingga menjadi idempoten, yang juga membantu mencegah hasil buruk dari tabrakan antara dua permintaan PATCH pada sumber daya yang sama dalam kerangka waktu yang sama.

Dalam contoh Dan, operasi PATCH-nya, sebenarnya, idempoten. Dalam contoh itu, entitas / users / 1 berubah di antara permintaan PATCH kami, tetapi bukan karena permintaan PATCH kami; sebenarnya dokumen tambalan Kantor Pos berbeda yang menyebabkan kode pos berubah. PATCH berbeda dari Kantor Pos adalah operasi yang berbeda; jika PATCH kami adalah f (x), PATCH Kantor Pos adalah g (x). Idempotence menyatakan itu f(f(f(x))) = f(x), tetapi tidak membuat jaminan f(g(f(x))).

Jason Hoetger
sumber
11
Dengan asumsi bahwa server juga memungkinkan mengeluarkan PUT di /users, ini akan membuat PUT juga non-idempoten. Semua ini berujung pada bagaimana server dirancang untuk menangani permintaan.
Uzair Sajid
13
Jadi, Kami dapat membangun API hanya dengan operasi PATCH. Lalu, apa yang menjadi prinsip REST menggunakan http VERBS untuk membuat tindakan CRUD pada Sumber Daya? Bukankah kita terlalu kompleks dengan perbatasan PATCH di sini?
bohr
6
Jika PUT diterapkan pada koleksi (misalnya /users), permintaan PUT apa pun harus mengganti konten koleksi itu. Jadi seorang PUT /usersharus mengharapkan kumpulan pengguna dan menghapus yang lainnya. Ini idempoten. Sepertinya Anda tidak akan melakukan hal seperti itu pada titik akhir / pengguna. Tetapi sesuatu seperti /users/1/emailsmungkin koleksi dan mungkin benar-benar valid untuk memungkinkan mengganti seluruh koleksi dengan yang baru.
Vectorjohn
5
Meskipun jawaban ini memberikan contoh hebat idempotensi, saya percaya ini bisa membuat lumpur berlumpur dalam skenario REST yang khas. Dalam hal ini Anda memiliki permintaan PATCH dengan optindakan tambahan yang memicu logika sisi server tertentu. Ini akan membutuhkan server dan klien untuk menyadari nilai-nilai spesifik untuk lulus untuk opbidang untuk memicu alur kerja sisi server. Dalam skenario REST yang lebih mudah, jenis opfungsi ini adalah praktik buruk dan harus ditangani secara langsung melalui kata kerja HTTP.
ivandov
7
Saya tidak akan pernah mempertimbangkan menerbitkan PATCH, hanya POST dan HAPUS, terhadap koleksi. Apakah ini benar-benar pernah dilakukan? Bisakah PATCH dianggap idempoten untuk semua tujuan praktis?
Tom Russell
72

Saya ingin tahu tentang ini juga dan menemukan beberapa artikel menarik. Saya mungkin tidak menjawab pertanyaan Anda sepenuhnya, tetapi ini setidaknya memberikan beberapa informasi lebih lanjut.

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC menentukan bahwa PUT harus mengambil representasi sumber daya baru sepenuhnya sebagai entitas permintaan. Ini berarti bahwa jika misalnya hanya atribut tertentu yang disediakan, atribut tersebut harus dihapus (yaitu disetel ke nol).

Mengingat itu, maka PUT harus mengirim seluruh objek. Contohnya,

/users/1
PUT {id: 1, username: 'skwee357', email: '[email protected]'}

Ini secara efektif akan memperbarui email. Alasan PUT mungkin tidak terlalu efektif adalah bahwa satu-satunya bidang yang benar-benar Anda modifikasi dan termasuk nama pengguna tidak berguna. Contoh berikut menunjukkan perbedaannya.

/users/1
PUT {id: 1, email: '[email protected]'}

Sekarang, jika PUT dirancang sesuai spesifikasi, maka PUT akan mengatur nama pengguna menjadi nol dan Anda akan mendapatkan yang berikut kembali.

{id: 1, username: null, email: '[email protected]'}

Saat Anda menggunakan PATCH, Anda hanya memperbarui bidang yang Anda tentukan dan membiarkan sisanya seperti dalam contoh Anda.

Pengambilan PATCH berikut sedikit berbeda dari yang belum pernah saya lihat sebelumnya.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

Perbedaan antara permintaan PUT dan PATCH tercermin dalam cara server memproses entitas terlampir untuk memodifikasi sumber daya yang diidentifikasi oleh Request-URI. Dalam permintaan PUT, entitas terlampir dianggap sebagai versi modifikasi dari sumber daya yang disimpan di server asal, dan klien meminta agar versi yang disimpan diganti. Dengan PATCH, bagaimanapun, entitas terlampir berisi serangkaian instruksi yang menggambarkan bagaimana sumber daya yang saat ini berada di server asal harus dimodifikasi untuk menghasilkan versi baru. Metode PATCH memengaruhi sumber daya yang diidentifikasi oleh Request-URI, dan itu juga MUNGKIN memiliki efek samping pada sumber daya lainnya; yaitu, sumber daya baru dapat dibuat, atau yang sudah ada dimodifikasi, oleh aplikasi PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "[email protected]" }
]

Anda kurang lebih memperlakukan PATCH sebagai cara untuk memperbarui bidang. Jadi, alih-alih mengirim objek parsial, Anda mengirim lebih dari operasi. yaitu Ganti email dengan nilai.

Artikel berakhir dengan ini.

Patut disebutkan bahwa PATCH tidak benar-benar dirancang untuk API yang benar-benar REST, karena disertasi Fielding tidak mendefinisikan cara apa pun untuk memodifikasi sebagian sumber daya. Tapi, Roy Fielding sendiri mengatakan bahwa PATCH adalah sesuatu yang [dia] buat untuk proposal HTTP / 1.1 awal karena PUT parsial tidak pernah tenang. Tentu Anda tidak mentransfer representasi yang lengkap, tetapi REST tidak memerlukan representasi yang lengkap.

Sekarang, saya tidak tahu apakah saya sangat setuju dengan artikel seperti yang ditunjukkan oleh banyak komentator. Mengirim representasi parsial dapat dengan mudah menjadi deskripsi perubahan.

Bagi saya, saya bingung menggunakan PATCH. Untuk sebagian besar, saya akan memperlakukan PUT sebagai PATCH karena satu-satunya perbedaan nyata yang saya perhatikan sejauh ini adalah bahwa PUT "harus" mengatur nilai yang hilang menjadi nol. Ini mungkin bukan cara yang 'paling benar' untuk melakukannya, tetapi semoga berhasil membuat kode yang sempurna.

Kalel Wade
sumber
7
Mungkin perlu ditambahkan: dalam artikel William Durand (dan rfc 6902) ada contoh di mana "op" adalah "add". Ini jelas bukan idempoten.
Johannes Brodwall
2
Atau Anda bisa membuatnya lebih mudah dan gunakan Patch Gabung RFC 7396 sebagai gantinya dan hindari membangun patch JSON.
Piotr Kula
untuk tabel nosql, perbedaan antara patch dan put adalah penting, karena nosql tidak memiliki kolom
stackdave
18

Perbedaan antara PUT dan PATCH adalah:

  1. PUT harus idempoten. Untuk mencapai itu, Anda harus meletakkan seluruh sumber daya yang lengkap di badan permintaan.
  2. PATCH bisa menjadi non-idempoten. Yang menyiratkan itu juga bisa idempoten dalam beberapa kasus, seperti kasus yang Anda jelaskan.

PATCH memerlukan beberapa "bahasa tambalan" untuk memberi tahu server cara memodifikasi sumber. Penelepon dan server perlu mendefinisikan beberapa "operasi" seperti "tambah", "ganti", "hapus". Sebagai contoh:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "[email protected]"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "address": "123 main street",
}

Alih-alih menggunakan bidang "operasi" yang eksplisit, bahasa tambalan dapat membuatnya tersirat dengan mendefinisikan konvensi seperti:

di badan permintaan PATCH:

  1. Keberadaan suatu bidang berarti "ganti" atau "tambahkan" bidang itu.
  2. Jika nilai bidang adalah nol, itu berarti menghapus bidang itu.

Dengan konvensi di atas, PATCH dalam contoh dapat mengambil bentuk berikut:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "[email protected]",
  "zip":
}

Yang terlihat lebih ringkas dan ramah pengguna. Tetapi para pengguna perlu menyadari konvensi yang mendasarinya.

Dengan operasi yang saya sebutkan di atas, PATCH masih idempoten. Tetapi jika Anda mendefinisikan operasi seperti: "increment" atau "append", Anda dapat dengan mudah melihatnya tidak akan lagi idempoten.

Bin Ni
sumber
7

TLDR - Versi Dumbed Down

PUT => Tetapkan semua atribut baru untuk sumber daya yang ada.

PATCH => Perbarui sebagian sumber daya yang ada (tidak semua atribut diperlukan).

Bijan
sumber
3

Biarkan saya mengutip dan berkomentar lebih dekat pada bagian 4.2.2 RFC 7231 , yang telah dikutip dalam komentar sebelumnya:

Metode permintaan dianggap "idempoten" jika efek yang dimaksudkan pada server dari beberapa permintaan yang identik dengan metode itu sama dengan efek untuk satu permintaan tersebut. Dari metode permintaan yang ditentukan oleh spesifikasi ini, PUT, DELETE, dan metode permintaan aman idempoten.

(...)

Metode idempoten dibedakan karena permintaan dapat diulang secara otomatis jika terjadi kegagalan komunikasi sebelum klien dapat membaca respons server. Misalnya, jika klien mengirim permintaan PUT dan koneksi yang mendasarinya ditutup sebelum respons apa pun diterima, maka klien dapat membuat koneksi baru dan mencoba kembali permintaan idempoten. Ia tahu bahwa mengulangi permintaan akan memiliki efek yang dimaksudkan sama, bahkan jika permintaan asli berhasil, meskipun responsnya mungkin berbeda.

Jadi, apa yang harus "sama" setelah permintaan berulang metode idempoten? Bukan status server, atau respons server, tetapi efek yang dimaksudkan . Secara khusus, metode ini harus idempoten "dari sudut pandang klien". Sekarang, saya pikir sudut pandang ini menunjukkan bahwa contoh terakhir dalam jawaban Dan Lowe , yang saya tidak ingin menjiplak di sini, memang menunjukkan bahwa permintaan PATCH dapat non-idempoten (dengan cara yang lebih alami daripada contoh di Jawaban Jason Hoetger ).

Memang, mari kita buat contoh yang sedikit lebih tepat dengan membuat eksplisit satu niat yang mungkin untuk klien pertama. Katakanlah klien ini menelusuri daftar pengguna dengan proyek untuk memeriksa email dan kode pos mereka. Dia mulai dengan pengguna 1, memperhatikan bahwa zipnya benar tetapi emailnya salah. Dia memutuskan untuk memperbaikinya dengan permintaan PATCH, yang sepenuhnya sah, dan hanya mengirim

PATCH /users/1
{"email": "[email protected]"}

karena ini adalah satu-satunya koreksi. Sekarang, permintaan gagal karena beberapa masalah jaringan dan dikirimkan kembali secara otomatis beberapa jam kemudian. Sementara itu, klien lain telah (secara keliru) memodifikasi zip pengguna 1. Kemudian, mengirimkan permintaan PATCH yang sama untuk kedua kalinya tidak mencapai efek yang diinginkan klien, karena kami berakhir dengan zip yang salah. Karenanya metode ini tidak idempoten dalam arti RFC.

Jika sebaliknya klien menggunakan permintaan PUT untuk memperbaiki email, mengirim ke server semua properti pengguna 1 bersama dengan email, efek yang dimaksudkannya akan tercapai bahkan jika permintaan tersebut harus dikirim kembali nanti dan pengguna 1 telah dimodifikasi Sementara itu --- karena permintaan PUT kedua akan menimpa semua perubahan sejak permintaan pertama.

Rolvernew
sumber
2

Menurut pendapat saya yang sederhana, idempotensi berarti:

  • TARUH:

Saya mengirim definisi sumber daya yang bersaing, jadi - status sumber daya yang dihasilkan persis seperti yang didefinisikan oleh par PUT. Setiap kali saya memperbarui sumber daya dengan par PUT yang sama - keadaan yang dihasilkan persis sama.

  • PATCH:

Saya hanya mengirim sebagian dari definisi sumber daya, sehingga mungkin terjadi pengguna lain memperbarui parameter OTHER sumber daya ini sementara itu. Konsekuensinya - tambalan berturut-turut dengan parameter yang sama dan nilainya mungkin dihasilkan dengan status sumber daya yang berbeda. Contohnya:

Anggap sebuah objek didefinisikan sebagai berikut:

CAR: - warna: hitam, - jenis: sedan, - kursi: 5

Saya menambalnya dengan:

{warna merah'}

Objek yang dihasilkan adalah:

CAR: - warna: merah, - jenis: sedan, - kursi: 5

Lalu, beberapa pengguna lain menambal mobil ini dengan:

{type: 'hatchback'}

jadi, objek yang dihasilkan adalah:

CAR: - warna: merah, - jenis: hatchback, - kursi: 5

Sekarang, jika saya menambal objek ini lagi dengan:

{warna merah'}

objek yang dihasilkan adalah:

CAR: - warna: merah, - jenis: hatchback, - kursi: 5

Apa yang BERBEDA dengan apa yang saya dapatkan sebelumnya!

Inilah sebabnya mengapa PATCH tidak idempoten sementara PUT idempoten.

Zbigniew Szczęsny
sumber
1

Untuk mengakhiri diskusi tentang idempotensi, saya harus mencatat bahwa seseorang dapat mendefinisikan idempotensi dalam konteks REST dengan dua cara. Pertama mari kita memformalkan beberapa hal:

Sebuah sumber daya adalah fungsi dengan kodomain yang menjadi kelas string. Dengan kata lain, sumber daya adalah subset dari String × Any, di mana semua kunci unik. Mari kita sebut kelas sumber daya Res.

Operasi REST pada sumber daya, adalah fungsi f(x: Res, y: Res): Res. Dua contoh operasi REST adalah:

  • PUT(x: Res, y: Res): Res = x, dan
  • PATCH(x: Res, y: Res): Res, yang berfungsi seperti PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Definisi ini secara khusus dirancang untuk berdebat tentang PUTdan POST, dan misalnya tidak masuk akal GETdan POST, karena tidak peduli tentang kegigihan).

Sekarang, dengan memperbaiki x: Res(berbicara secara informal, menggunakan currying), PUT(x: Res)dan PATCH(x: Res)merupakan fungsi tipe univariat Res → Res.

  1. Fungsi g: Res → Resini disebut global idempoten , ketika g ○ g == g, yaitu untuk setiap y: Res, g(g(y)) = g(y).

  2. Biarkan x: Ressumber daya, dan k = x.keys. Suatu fungsi g = f(x)disebut idempoten kiri , ketika untuk masing-masing y: Res, kita miliki g(g(y))|ₖ == g(y)|ₖ. Ini pada dasarnya berarti bahwa hasilnya harus sama, jika kita melihat kunci yang diterapkan.

Jadi, PATCH(x)tidak secara global idempoten, tetapi dibiarkan idempoten. Dan idempotensi kiri adalah hal yang penting di sini: jika kita menambal beberapa kunci sumber daya, kita ingin kunci itu sama jika kita menambalnya lagi, dan kita tidak peduli dengan sisa sumber daya.

Dan ketika RFC berbicara tentang PATCH yang bukan idempoten, itu berbicara tentang idempotensi global. Yah, itu bagus bahwa itu bukan idempoten global, kalau tidak itu akan menjadi operasi yang rusak.


Sekarang, jawaban Jason Hoetger berusaha menunjukkan bahwa PATCH bahkan tidak dibiarkan idempoten, tetapi terlalu banyak hal yang harus dilanggar untuk melakukannya:

  • Pertama-tama, PATCH digunakan pada set, meskipun PATCH didefinisikan untuk bekerja pada peta / kamus / objek nilai kunci.
  • Jika seseorang benar-benar ingin menerapkan PATCH ke set, maka ada terjemahan alami yang harus digunakan t: Set<T> → Map<T, Boolean>:, didefinisikan dengan x in A iff t(A)(x) == True. Dengan menggunakan definisi ini, penambalan dibiarkan idempoten.
  • Dalam contoh ini, terjemahan ini tidak digunakan, sebagai gantinya, PATCH berfungsi seperti POST. Pertama-tama, mengapa ID dibuat untuk objek? Dan kapan itu dihasilkan? Jika objek pertama kali dibandingkan dengan elemen-elemen set, dan jika tidak ada objek yang cocok ditemukan, maka ID dihasilkan, maka sekali lagi program harus bekerja secara berbeda ({id: 1, email: "[email protected]"} harus cocok dengan {email: "[email protected]"}, jika tidak, program selalu rusak dan PATCH tidak mungkin tambalan). Jika ID dibuat sebelum memeriksa set, sekali lagi program rusak.

Satu dapat membuat contoh PUT menjadi non-idempoten dengan memecahkan setengah dari hal-hal yang rusak dalam contoh ini:

  • Contoh dengan fitur tambahan yang dihasilkan adalah versi. Seseorang dapat mencatat jumlah perubahan pada satu objek. Dalam hal ini, PUT tidak idempoten: PUT /user/12 {email: "[email protected]"}menghasilkan{email: "...", version: 1} pertama kalinya, dan {email: "...", version: 2}kedua kalinya.
  • Jika menggunakan ID, seseorang dapat membuat ID baru setiap kali objek diperbarui, menghasilkan PUT yang tidak idempoten.

Semua contoh di atas adalah contoh alami yang mungkin ditemui seseorang.


Poin terakhir saya adalah, bahwa PATCH tidak boleh idempoten secara global , jika tidak, tidak akan memberi Anda efek yang diinginkan. Anda ingin mengubah alamat email pengguna Anda, tanpa menyentuh informasi lainnya, dan Anda tidak ingin menimpa perubahan pihak lain yang mengakses sumber daya yang sama.

Mohammad-Ali A'RÂBI
sumber
-1

Satu informasi tambahan yang saya hanya perlu menambahkan adalah bahwa permintaan PATCH menggunakan bandwidth lebih sedikit dibandingkan dengan permintaan PUT karena hanya sebagian dari data yang dikirim bukan seluruh entitas. Jadi cukup gunakan permintaan PATCH untuk memperbarui catatan tertentu seperti (1-3 catatan) sementara PUT meminta untuk memperbarui jumlah data yang lebih besar. Itu dia, jangan terlalu banyak berpikir atau terlalu khawatir tentang itu.

Benjamin
sumber