REST - masukkan ID ke dalam tubuh atau tidak?

103

Katakanlah saya ingin memiliki sumber daya RESTful untuk orang-orang, di mana klien dapat menetapkan ID.

Seseorang terlihat seperti ini: {"id": <UUID>, "name": "Jimmy"}

Sekarang, bagaimana cara klien menyimpan (atau "PUT") itu?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - sekarang kita memiliki duplikasi buruk yang harus kita verifikasi setiap saat: Apakah ID di badan cocok dengan yang ada di jalur?
  2. Representasi asimetris:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID kembali {"id": <UUID>, "name": "Jimmy"}
  3. Tidak ada ID di badan - ID hanya di lokasi:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID kembali {"name": "Jimmy"}
  4. Tidak ada jenis POSTtampaknya seperti ide yang baik karena ID yang dihasilkan oleh klien.

Apa pola umum dan cara mengatasinya? ID yang hanya berada di lokasi tampaknya merupakan cara yang paling benar secara dogmatis, tetapi ini juga mempersulit penerapan praktis.

Konrad Garus
sumber

Jawaban:

65

Tidak ada yang salah dalam memiliki model baca / tulis yang berbeda: klien dapat menulis satu representasi sumber daya di mana setelah server dapat mengembalikan representasi lain dengan elemen yang ditambahkan / dihitung di dalamnya (atau bahkan representasi yang sama sekali berbeda - tidak ada spesifikasi apa pun yang menentangnya , satu-satunya persyaratan adalah PUT harus membuat atau mengganti sumber daya).

Jadi saya akan mencari solusi asimetris di (2) dan menghindari "pemeriksaan duplikasi buruk" di sisi server saat menulis:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}
Jørn Wildt
sumber
2
Dan jika Anda menerapkan pengetikan (statis atau dinamis), Anda tidak dapat dengan mudah memiliki model tanpa ID ... Jadi, jauh lebih mudah untuk menghapus ID dari URL untuk permintaan PUT. Ini tidak akan "tenang" tetapi itu akan benar.
Ivan Kleshnin
2
Pertahankan tambahan TO tanpa iddisertai TO dengan id dan entitas serta konverter tambahan dan overhead yang terlalu besar untuk pemrogram.
Grigory Kislin
Bagaimana jika saya mendapatkan ID dari BODY ex .: PUT / person {"id": 1, "name": "Jimmy"}. Apakah itu praktik yang buruk?
Bruno Santos
Menempatkan ID di badan akan baik-baik saja. Gunakan GUID untuk ID, bukan bilangan bulat - jika tidak, Anda berisiko mengalami duplikat ID.
Jørn Wildt
Ini salah. Lihat jawabanku. PUT harus berisi seluruh sumber daya. Gunakan PATCH jika Anda ingin mengecualikan id dan memperbarui hanya sebagian dari record.
CompEng88
27

Jika ini adalah API publik, Anda harus bersikap konservatif saat membalas, tetapi terima dengan bebas.

Maksud saya, Anda harus mendukung 1 dan 2. Saya setuju bahwa 3 tidak masuk akal.

Cara untuk mendukung 1 dan 2 adalah dengan mendapatkan id dari url jika tidak ada yang disediakan di badan permintaan, dan jika ada di badan permintaan, maka validasikan bahwa itu cocok dengan id di url. Jika keduanya tidak cocok, kembalikan respons 400 Permintaan Buruk.

Saat mengembalikan resource person, bersikaplah konservatif dan selalu sertakan id di json, meskipun itu opsional di put.

Jay Pete
sumber
3
Ini harus menjadi solusi yang diterima. API harus selalu ramah pengguna. Ini harus menjadi opsional di tubuh. Saya seharusnya tidak menerima ID dari POST dan kemudian harus membuatnya tidak ditentukan dalam PUT. Juga, 400 titik respons yang dibuat sudah benar.
Michael
Sekitar 400 kode lihat juga diskusi softwareengineering.stackexchange.com/questions/329229/… . Singkatnya, kode 400 tidak pantas, hanya kurang spesifik, dibandingkan dengan 422.
Grigory Kislin
8

Satu solusi untuk masalah ini melibatkan konsep yang agak membingungkan dari "Hypertext As The Engine Of Application State," atau "HATEOAS." Ini berarti bahwa respons REST berisi sumber daya atau tindakan yang tersedia untuk dilakukan sebagai hyperlink. Dengan menggunakan metode ini, yang merupakan bagian dari konsep asli REST, pengidentifikasi / ID unik dari sumber daya itu sendiri adalah hyperlink. Misalnya, Anda dapat memiliki sesuatu seperti:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Kemudian, jika Anda ingin mengupdate resource tersebut, Anda dapat melakukan (pseudocode):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Satu keuntungan dari ini adalah bahwa klien tidak harus memiliki ide tentang representasi internal server dari ID Pengguna. ID dapat berubah, dan bahkan URL itu sendiri dapat berubah, selama klien memiliki cara untuk menemukannya. Misalnya, saat mendapatkan sekumpulan orang, Anda bisa membalas seperti ini:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Anda dapat, tentu saja, juga mengembalikan objek orang penuh untuk setiap orang, tergantung pada kebutuhan aplikasi).

Dengan metode ini, Anda lebih memikirkan objek Anda dalam hal sumber daya dan lokasi, dan lebih sedikit dalam hal ID. Representasi internal pengenal unik dengan demikian dipisahkan dari logika klien Anda. Ini adalah dorongan asli di balik REST: untuk membuat arsitektur klien-server yang lebih longgar digabungkan daripada sistem RPC yang ada sebelumnya, dengan menggunakan fitur HTTP. Untuk informasi lebih lanjut tentang HATEOAS, lihat artikel Wikipedia serta artikel pendek ini .

bthecohen
sumber
4

Dalam menyisipkan Anda tidak perlu menambahkan id di URL. Dengan cara ini jika Anda mengirim ID dalam PUT Anda dapat diartikan sebagai UPDATE untuk mengubah kunci utama.

  1. MEMASUKKAN:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. MEMPERBARUI

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

The JSON API menggunakan standar ini dan memecahkan beberapa masalah kembali objek dimasukkan atau diperbarui dengan link ke objek baru. Beberapa pembaruan atau sisipan mungkin menyertakan beberapa logika bisnis yang akan mengubah bidang tambahan

Anda juga akan melihat bahwa Anda dapat menghindari get setelah penyisipan dan pembaruan.

borjab.dll
sumber
3

Ini telah ditanyakan sebelumnya - pembahasannya menarik untuk dilihat:

Haruskah respons RESTful GET mengembalikan ID sumber daya?

Ini adalah salah satu pertanyaan di mana mudah untuk terjebak dalam perdebatan tentang apa yang "tenang" dan apa yang tidak .

Untuk apa nilainya, saya mencoba untuk berpikir dalam hal sumber daya yang konsisten dan tidak mengubah desainnya di antara metode. Namun, IMHO, hal terpenting dari perspektif kegunaan adalah Anda konsisten di seluruh API!

Ben Morris
sumber
2

Meskipun boleh saja memiliki representasi yang berbeda untuk operasi yang berbeda, rekomendasi umum untuk PUT adalah memuat muatan SELURUH . Itu artinya idharus ada juga. Jika tidak, Anda harus menggunakan PATCH.

Karena itu, saya pikir PUT sebagian besar harus digunakan untuk pembaruan dan idharus selalu diteruskan di URL juga. Akibatnya, menggunakan PUT untuk memperbarui pengenal sumber daya adalah ide yang buruk. Itu membuat kita dalam situasi yang tidak diinginkan ketika iddi URL bisa berbeda dari iddi dalam tubuh.

Jadi, bagaimana kita menyelesaikan konflik seperti itu? Kami pada dasarnya memiliki 2 opsi:

  • melempar pengecualian 4XX
  • tambahkan tajuk Warning( X-API-Warndll).

Sedekat itu saya bisa menjawab pertanyaan ini karena topiknya secara umum adalah masalah opini.

yuranos
sumber
2

FYI saja, jawabannya di sini salah.

Lihat:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

TARUH

Gunakan PUT API terutama untuk memperbarui sumber daya yang ada (jika sumber daya tidak ada, maka API dapat memutuskan untuk membuat sumber daya baru atau tidak). Jika sumber daya baru telah dibuat oleh PUT API, server asal HARUS memberi tahu agen pengguna melalui respons kode respons HTTP 201 (Dibuat) dan jika sumber daya yang ada diubah, baik 200 (OK) atau 204 (Tanpa Konten) kode respon HARUS dikirim untuk menunjukkan penyelesaian permintaan yang berhasil.

Jika permintaan melewati cache dan Request-URI mengidentifikasi satu atau lebih entitas yang saat ini disimpan dalam cache, entri tersebut HARUS diperlakukan sebagai usang. Tanggapan untuk metode ini tidak dapat disimpan dalam cache.

Gunakan PUT bila Anda ingin mengubah sumber daya tunggal yang sudah menjadi bagian dari kumpulan sumber daya. PUT menggantikan sumber daya secara keseluruhan. Gunakan PATCH jika permintaan memperbarui bagian sumber daya.

PATCH

Permintaan HTTP PATCH adalah untuk membuat pembaruan parsial pada sumber daya. Jika Anda melihat permintaan PUT juga mengubah entitas sumber daya sehingga untuk memperjelasnya - metode PATCH adalah pilihan yang tepat untuk memperbarui sebagian sumber daya yang ada dan PUT hanya boleh digunakan jika Anda mengganti sumber daya secara keseluruhan.

Jadi Anda harus menggunakannya dengan cara ini:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Praktik RESTful menunjukkan bahwa tidak masalah apa yang Anda PUT di / {id} - konten record harus diperbarui ke yang disediakan oleh payload - tetapi GET / {id} harus tetap ditautkan ke resource yang sama.

Dengan kata lain, PUT / 3 dapat mengupdate ke payload id menjadi 4, tetapi GET / 3 harus tetap menautkan ke payload yang sama (dan mengembalikan yang dengan id disetel ke 4).

Jika Anda memutuskan bahwa API Anda memerlukan pengenal yang sama di URI dan payload, tugas Anda adalah memastikannya cocok, tetapi gunakan PATCH alih-alih PUT jika Anda mengecualikan id dalam payload yang seharusnya ada secara keseluruhan . Di sinilah jawaban yang diterima salah. PUT harus menggantikan seluruh sumber daya, di mana tambalan mungkin parsial.

CompEng88
sumber
Di paragraf Anda: "Dengan kata lain, PUT / 3 dapat memperbarui ke payload id menjadi 4, tetapi GET / 3 harus tetap menautkan ke payload yang sama (dan mengembalikan yang dengan id disetel ke 4 )." - maksudmu 3 bukannya 4 di id terakhir?
mlst
Re: "gunakan PATCH sebagai pengganti PUT jika Anda tidak menyertakan id" . Ini kurang informasi. RFC 7231 2014 (salah satu RFC yang menghapus RFC 2616 1999) menyebutkan di sini bahwa server dapat memiliki transformation applied to the body. Bahkan ada mekanisme untuk klien itu allows a user agent to know when the representation body it has in memory remains current, yaitu: server MUST NOT send ... an ETag or Last-Modified ... unless the request's representation was saved without any transformation.
Slawomir Brzezinski
Lihat juga pertanyaan + jawaban SO ini dari pengguna lain yang berisi justifikasi mengapa PUT pantas digunakan. Ini dengan baik meringkas "Yang penting, adalah memahami apa maksud dari permintaan klien. Klien bermaksud untuk mengganti sepenuhnya konten sumber daya dengan nilai yang diteruskan."
Slawomir Brzezinski
1

Tidak ada yang buruk dalam menggunakan pendekatan yang berbeda. tetapi saya pikir cara terbaik adalah solusi dengan 2nd .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

itu sebagian besar digunakan dengan cara ini bahkan kerangka entitas menggunakan teknik ini ketika entitas ditambahkan dalam dbContext kelas tanpa ID yang dihasilkan adalah ID yang dihasilkan oleh referensi di Entity Framework.

Shan Khan
sumber
1

Saya melihat ini dari sudut pandang JSON-LD / Semantic Web karena itu cara yang baik untuk mencapai kesesuaian REST yang nyata seperti yang telah saya uraikan dalam slide ini . Melihat dari perspektif itu, tidak ada pertanyaan untuk memilih opsi (1.) karena ID (IRI) dari sumber daya Web harus selalu sama dengan URL yang dapat saya gunakan untuk mencari / mendereferensi sumber daya. Saya pikir verifikasi tidak terlalu sulit untuk diterapkan juga tidak intens secara komputasi; jadi saya tidak menganggap ini sebagai alasan yang valid untuk menggunakan opsi (2.). Saya pikir opsi (3.) sebenarnya bukan opsi karena POST (buat baru) memiliki semantik yang berbeda dari PUT (perbarui / ganti).

vanthome
sumber
0

Anda mungkin perlu melihat jenis permintaan PATCH / PUT.

Permintaan PATCH digunakan untuk memperbarui sumber daya sebagian sedangkan dalam permintaan PUT, Anda harus mengirim seluruh sumber daya ke tempat yang ditimpa di server.

Sejauh menyangkut ID di url, saya pikir Anda harus selalu memilikinya karena ini adalah praktik standar untuk mengidentifikasi sumber daya. Bahkan API Stripe bekerja seperti itu.

Anda dapat menggunakan permintaan PATCH untuk memperbarui sumber daya di server dengan ID untuk mengidentifikasinya tetapi tidak memperbarui ID sebenarnya.

Noman Ur Rehman
sumber