Saya akan mengajukan pertanyaan ini dengan cara ini - apa masalah rekayasa perangkat lunak untuk tidak mengimplementasikan REST API saya dengan cara yang "benar"?
Apa maksud Anda dengan cara yang "benar"? Baiklah, izinkan saya untuk menjelaskan persepsi saya tentang cara yang benar, maka saya akan memberitahu Anda bagaimana saya melakukannya (juga, anggap saya berbicara tentang API SISA JSON).
Jalan yang benar
Kewarganegaraan. Ini adalah bagian yang saya dapatkan. Klien mempertahankan negara selalu 100% waktu selamanya. Itu bukan pekerjaan server, itu adalah klien.
Tindakan dan respons yang diharapkan untuk setiap kata kerja:
- DAPATKAN - Mendapat sumber daya yang ditentukan secara penuh penuh, hanya dibatasi oleh otorisasi dalam permintaan atau parameter kueri. Ini tidak menjamin modifikasi sumber daya apa pun dalam proses.
- POST - Diberikan seluruh deskripsi sumber daya (seperti objek JSON), membuat sumber daya, lalu mengembalikan sumber daya itu, dengan properti sisi server apa pun juga dibuat, seperti tanggal atau ID.
- HAPUS - Menghapus sumber daya yang ditentukan, hanya memberikan semacam 200 OK sebagai tanggapan
- PUT - Diberikan seluruh deklarasi objek sebagai input, memperbarui sumber daya di lokasi tertentu, memperbarui semua bidang sumber daya untuk setiap bidang yang diberikan dalam input. Agar jelas, ini mengharapkan seluruh objek untuk diteruskan sebagai input. Seluruh sumber daya yang diperbarui dikembalikan, dengan semua bidang (sesuai dengan otorisasi atau tanda input lainnya).
- PATCH - Hanya diberikan bidang yang ingin dimodifikasi untuk sumber daya, memperbarui hanya bidang dalam sumber daya tertentu yang diberikan sebagai input. (Di sinilah saya tidak jelas): Seluruh sumber daya dikembalikan? (Atau hanya bidang yang diperbarui? Entahlah. Tidak peduli.)
- Jalur sumber daya. Mengingat hubungan sumber daya satu sama lain, jalur sumber daya mungkin terlihat seperti salah satu dari:
- / parentresource /: id
- / parentresource /: id / childresource
- / parentresource /: id / childresource /: childId
- / parentresource /: id / childresource /: childId / subresource /: subresourceId (Dalam contoh ini, sub-sumber daya adalah milik sumber daya anak, yang termasuk dalam sumber daya induk).
Cara saya ingin melakukannya
Di atas adalah pemahaman saya tentang bagaimana seharusnya REST API berfungsi. Sekarang izinkan saya mendaftar beberapa variasi saya di atas:
- PUT / PATCH - Apa gunanya melewati seluruh sumber daya untuk modifikasi? Saya hanya menggunakan PUT untuk memodifikasi sumber daya, dan saya hanya meneruskan bidang yang ingin saya perbarui. Akibatnya, saya tidak perlu menggunakan PATCH
Resource Paths - Saya menggunakan GUID dalam aplikasi saya. Akibatnya, mereka akan menjadi unik secara global. Mengapa saya memerlukan jalur sumber daya penuh, termasuk sumber daya induk, jika saya bisa merujuk sumber daya secara unik saja? Seperti:
/ subresource /: subresourceId
Jika saya harus melakukannya dengan "benar", mencoba untuk referensi subresource akan memerlukan path lengkap seperti:
/ parentresource /: id / childresource /: childId / subresource /: subresourceId
Apakah semua yang diperlukan ? Karena sekarang saya harus memiliki penanganan kesalahan tambahan jika jalur saya berisi: subresourceId yang sebenarnya tidak dimiliki oleh yang diberikan: childId, dan ditto untuk: childId tidak dimiliki oleh induk: id. Sisi server saya mengurus otorisasi sumber daya. Tidak bisakah saya hanya merujuk sumber daya itu sendiri, daripada path lengkap?Respon balik. Katakanlah, misalnya, bahwa struktur data saya adalah hierarki pohon, tanpa batas praktis pada kedalaman pohon. Sumber daya terletak pada tingkat yang berbeda di bawah pohon, secara hierarkis.
- GET jelas. Jika saya mendapatkan seluruh pohon ini, saya mengharapkan seluruh pohon sebagai tanggapan, dengan sumber daya yang terkandung dalam sumber daya.
- Jika saya POST untuk membuat sumber daya baru, PUT untuk memperbarui, atau HAPUS untuk menghapus, saya ingin melihat delta di pohon, daripada hanya melihat sumber daya yang saya buat / perbarui / hapus. Saya tidak ingin harus lagi memanggil GET dari pohon induk setelah setiap POST, PUT, atau DELETE, terutama jika ada sedikit perubahan pada pohon dan saya hanya ingin melihat delta.
Semoga pertanyaan saya jelas.
Jika Anda melihat implementasi REST seperti yang saya jelaskan, akankah Anda melihatnya dan memberi tahu saya tentang masalah rekayasa perangkat lunak Anda? Jika demikian, apa jadinya mereka?
Jawaban:
Jawaban umum yang mendasari di sini adalah bahwa ide-ide Anda dapat bekerja pada tingkat teknis, tetapi itu tidak berarti mereka sesuai dengan konvensi standar REST.
Ide Anda bekerja pada level teknis, tetapi itu bukan bagaimana REST telah dijelaskan. Perlu diingat bahwa setiap diskusi tentang kode kerja (yaitu tidak ada kesalahan kompilasi atau runtime) selalu akan menjadi masalah konvensi , belum tentu keunggulan teknis yang jelas.
Ada banyak nuansa bagaimana kita mendefinisikan entitas "anak / orang tua". Paling umum, ini merujuk untuk hubungan satu-ke-banyak (orangtua-ke-anak).
Namun saya curiga bahwa untuk REST, bagian dari apa yang menjadikan seorang anak anak adalah bahwa ada harapan hanya bisa mengaksesnya melalui orang tua, bahwa mereka tidak membawa pengenal mereka yang unik secara global (dan dikenal secara eksternal).
Saya menduga ini mengikuti filosofi yang sama (tetapi tidak harus karena alasan yang sama) dengan agregat (dan akarnya) dalam Pengembangan Domain Driven .
Dalam kasus Anda, apa yang Anda sebut "induk" berfungsi sebagai akar agregat. Satu titik kontak (jika Anda mau) untuk penelepon luar.
Anda mungkin ingin menyimpulkan dari sini bahwa anak Anda sebenarnya agregat yang berbeda. Mungkin itu masalahnya, tapi saya ingin mengeluarkan peringatan dengan keputusan itu. Anda tidak harus mendasarkan arsitektur Anda pada jenis bidang tertentu. Anda tidak memiliki cara untuk mengetahui apakah Anda akan selalu tetap menggunakan ID unik global untuk semua entitas Anda. Jika itu pernah berubah, untuk alasan apa pun, Anda akan membahayakan kelayakan arsitektur REST Anda; karena Anda mungkin berakhir dalam situasi di mana anak tidak lagi dapat diidentifikasi secara unik dan karenanya perlu dirujuk melalui orang tuanya.
Anda melanggar urutan operasi desain. API REST secara khusus dimaksudkan sebagai agnostik konsumen. API tidak boleh dibangun sesuai dengan spesifikasi salah satu konsumennya.
Ketika Anda mengatakan "Saya ingin melihat delta di pohon", apa yang sebenarnya Anda katakan adalah "aplikasi pemakai hanya perlu melihat delta di pohon". Tapi itu tidak terlalu penting bagi api REST. Ini hanya menyediakan pendekatan standar.
Ini adalah sifat dari pendekatan standar untuk sering kali tidak memiliki alat yang sangat dapat dikustomisasi , dan sebagai gantinya mendukung alat yang paling umum digunakan .
Bisakah Anda menyimpang dari jalan? Ya, itu akan bekerja pada level teknis. Tapi itu tidak akan menjadi SISA murni lagi. Ini adalah sesuatu yang sangat kontekstual dan Anda perlu mempertimbangkan pilihan.
sumber
Yang utama, menurut saya, adalah kemampuan untuk mendelegasikan pekerjaan ke komponen generik yang hanya mengetahui standar, bukan kasus bisnis spesifik Anda.
Jika Anda mengikuti antarmuka yang seragam, maka lebih mudah bagi pihak lain untuk membangun komponen yang terintegrasi dengan baik dengan Anda.
Ini tulisan Fielding pada 2008
Salah satu cara kami mengelola "berumur panjang" adalah dengan memiliki standar yang jelas menggambarkan semantik pesan yang kami sampaikan. Jika semua orang setuju apa
PUT
artinya, maka konsumen dan produsen permintaan tersebut dapat dikembangkan secara independen, dan komponen perantara di antara keduanya dapat mengambil tindakan yang masuk akal tanpa perlu mengetahui detail pesan dalam konteks spesifik Anda.Apa gunanya menggunakan
PUT
itu?Itu adalah baris permintaan yang valid untuk pesan HTTP, dan perubahan dalam semantik tidak akan membingungkan siapa pun.
Setara
Yang cukup banyak memiliki semantik yang tidak dibatasi; server dapat mengimplementasikan penanganan permintaan itu dengan cara apa pun yang diinginkan.
Keengganan Anda untuk menggunakan PATCH sangat aneh dalam kasus ini, karena sudah ada standar yang diusulkan untuk JSON Patch dan JSON Merge Patch - pekerjaan standardisasi format dokumen patch mungkin sudah dilakukan untuk Anda.
Alternatif lain yang valid adalah memperlakukan dokumen tambalan sebagai sumber daya terpisah. Semantik, Anda mungkin membayangkan sesuatu seperti
Itu membuat Anda jujur, seragam, semantik pesan, mengorbankan invalidasi cache standar .
Dalam pengaturan tinjauan kode, saya akan menolak perubahan yang diajukan yang mencoba mendefinisikan ulang semantik PUT.
Pertimbangan yang sama berlaku
PUT
juga; jika implementasi PUT Anda menyimpang dari semantik standar, maka implementasi Anda bertanggung jawab atas kerusakan yang terjadi.Tidak apa-apa. REST tidak peduli ejaan apa yang Anda gunakan untuk pengidentifikasi sumber daya Anda.
Pertimbangkan halaman arahan google. Apakah Anda perlu memperhatikan ejaan URI untuk coretan hari ini? atau di mana formulir pencarian dikirim? Tidak, tentu saja tidak. Payload HTML menyertakan URI di dalamnya, dan klien hanya menggunakan pengidentifikasi yang disediakan, dengan cara standar, tanpa perlu menganalisis pengidentifikasi tersebut.
Informasi yang dikodekan ke dalam URI adalah kebijaksanaan server asal, untuk keperluannya sendiri.
Saya tidak ingin menggunakan URI sebagai titik masuk API Anda.
https://www.example.org/df8f5f87-15ff-4212-8fb8-4fbca2c7efcf
sedikit canggung untuk konsumsi manusia. URI yang dapat dibaca manusia yang mengalihkan ke sumber daya UUID akan baik-baik saja, URI yang dapat dibaca manusia yang mengembalikan konten sumber daya UUID akan lebih baik.Tidak apa-apa - sekali lagi, lihat standarnya .
Dalam beberapa kasus, masuk akal untuk mengirim representasi baru dari sumber daya sebagai bagian dari respons (untuk menghindarkan klien dari latensi permintaan / respons GET).
sumber
What's the point in using PUT then?
-- Memang. POST akan bekerja dengan baik.PATCH
? Definisi ini lebih cocok.REST adalah gaya arsitektur untuk memanipulasi sumber daya dalam keadaan tanpa kewarganegaraan (di mana kewarganegaraan berarti bahwa setiap manipulasi berdiri sendiri dan tidak bergantung pada manipulasi lain yang mungkin telah terjadi).
Penggunaan kata kerja PUT / PATCH / POST / GET / DELETE berasal dari penggunaan umum protokol HTTP yang digunakan untuk mentransfer dan memanipulasi sumber daya. Arti kata kerja tersebut didefinisikan oleh standar internet ( RFC7231 ).
Mengingat latar belakang itu, penggunaan PUT Anda tidak standar dan dapat membingungkan pengembang lain yang ingin menggunakan API Anda.
Mengenai jalur sumber daya, hampir tidak ada yang peduli tentang ejaan mereka yang tepat (termasuk jika sumber daya anak terdaftar sebagai anak). Yang dipedulikan orang adalah bahwa setiap sumber daya diidentifikasi secara unik. Sistem
/parent/:pid/child/:cid
ini sering digunakan ketika id anak hanya unik dalam satu orangtua untuk tetap memiliki jalur unik global untuk sumber daya.sumber
Menurut saya Anda benar-benar harus menggunakan PATCH sebagai semantik di sini.
PUT menjelaskan kondisi yang tepat yang diinginkan. Ini berguna ketika sumber daya mungkin sering berubah, dan perubahan yang diinginkan perlu terjadi dalam konteks tertentu.
PATCH menjelaskan delta yang diinginkan untuk apa pun yang ada di sana. Ini berguna ketika perubahan tidak mempedulikan konteksnya, atau konteks yang relevan jauh lebih kecil dari keseluruhan sumber daya.
Sebuah gambar adalah contoh yang baik jika masuk akal untuk mengunggah semuanya. Penting bahwa seluruh sumber daya dikomunikasikan untuk memastikan konteks yang relevan.
Sebaliknya, memperbarui jumlah pemutaran pada daftar putar musik mungkin lebih baik menjadi delta. Bukan karena ini adalah perubahan kecil, tetapi karena mengirimkan kembali seluruh daftar dapat dengan mudah membatalkan perubahan pada isi daftar.
Tidak, Anda benar-benar tidak perlu menggunakan jalur - sebelumnya. Katakanlah apakah Anda menyimpan semua file Anda di desktop? Tidak? Kenapa tidak?
Mungkin ada hubungannya dengan membuatnya lebih mudah dilihat, masalah yang sama di sini. GUID tidak memberi tahu Anda apa yang Anda tangani saat Anda mengatur ini, men-debug-nya, atau menjalankannya.
Jadi bagaimana rasanya mendukung sistem ini? atau berinteraksi dengannya? Jika Anda belum memikirkannya, perlu waktu dan pertimbangkan pekerjaan yang Anda lakukan.
Memiliki informasi jalur yang jelas berfungsi untuk membantu memvalidasi permintaan. Ini juga membantu untuk membedakan informasi yang cukup yang mendukung dan pengembang sistem hilir dapat mendekati url ini dan menggunakannya.
Anda mungkin ingin menerapkan batas kedalaman, hanya agar beberapa anak pintar tidak hanya mendapatkan root dari situs Anda untuk setiap operasi yang dapat mereka pikirkan.
Jika memperbarui sumber daya memengaruhi induknya dengan cara yang tidak sepele dan dapat diprediksi, Anda memiliki masalah lain ... Anda benar-benar perlu melihat model negara bagian dan mencari tahu mengapa informasi tersebut melompat-lompat.
Jika Anda hanya ingin mengembalikan daftar delta terperinci, mengapa tidak? Mengapa tidak mendukung beberapa tampilan keluaran yang diubah oleh berbagai parameter? Jenkins mengembalikan respons API dalam pilihan xml, atau json dan memungkinkan Anda menentukan beberapa filter untuk mengekstraksi sub-pohon yang diinginkan.
Gunakan Sendiri
Terus terang, mundurlah dari apa yang Anda buat dan coba untuk mendukungnya, atau buat aplikasi lain untuk menggunakannya (bukan salah satu aplikasi yang sudah ada sebelumnya). Lakukan sesuatu yang serupa untuk API pihak ketiga sehingga Anda memiliki sedikit konteks latar belakang.
Kapan pun Anda merasa harus melakukan sesuatu yang tidak secara langsung menyelesaikan permintaan dukungan, atau secara langsung diperlukan untuk aplikasi klien maka API kurang dari ideal, dan Anda tahu mengapa itu kurang dari ideal yang bahkan lebih baik, karena Anda dapat memperbaikinya atau tidak melakukan kesalahan yang sama.
Misalnya, jika permintaan ke url yang diberikan selalu gagal, berapa banyak usaha yang harus Anda investasikan untuk menentukan apa yang gagal dan mengapa? Langkah apa yang Anda ambil, bisakah Anda menghindari salah satu dari langkah-langkah itu dengan memiliki URI yang lebih baik, atau penebangan yang lebih baik, atau pemantauan yang lebih baik, atau lain-lain ...?
Demikian pula, jika Anda menulis klien baru, berapa kali Anda perlu merujuk ke dokumentasi, atau ke kode sumber API? Apa yang bisa Anda lakukan untuk mengurangi kebutuhan itu? Apa yang bisa Anda lakukan untuk berhenti melanggar harapan Anda sendiri? Apa yang bisa Anda lakukan yang menyederhanakan masalah aplikasi klien tanpa membuat server menjadi mimpi buruk untuk dipertahankan?
Cara yang benar
Terus terang jalan yang benar tidak langsung. REST adalah seperangkat praktik yang ditunjukkan untuk bekerja di sejumlah keadaan untuk sejumlah masalah. Jika masalah Anda tidak cocok, jangan membuatnya cocok, tetapi kemudian juga jangan mengklaim menggunakan praktik-praktik itu.
sumber
Sebagian besar fitur REST apis ada karena suatu alasan, tetapi itu mungkin bukan alasan yang relevan bagi Anda. Memberi seluruh sumber daya, seperti dalam PUT, misalnya relevan jika Anda memerlukan idempotensi, jika tidak, maka tidak diperlukan. (Meskipun saya pikir akan lebih baik bagi pengguna / kolega / apa pun untuk mengiklankan fakta bahwa titik akhir Anda tidak idempoten dengan menggunakan POST atau PATCH sebagai gantinya.)
Untuk jalan hal yang saya tidak pernah mendengar yang terkait dengan istirahat.
/root/345dd4dc-e175-455f-b545-85b1b1ce3e82
adalah bagian dari pohon sebanyak/foo/bar/baz
. Mungkin sedikit kurang ramah pengguna tetapi tidak kurang tenang sejauh yang saya bisa lihat.Jika Anda ingin alasan yang lebih terperinci tentang mengapa REST dirancang sebagaimana adanya, saya pikir Anda harus membaca disertasi aslinya: https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf
Membaca bahwa Anda mungkin menemukan bahwa itu sangat berbeda dari bagaimana REST direpresentasikan dalam percakapan atau digunakan dalam API hari ini. Jelas banyak orang telah menemukan alasan, baik atau buruk, untuk menyimpang darinya.
Saya sangat suka kutipan ini yang mungkin Anda temukan relevan:
sumber
PUT
permintaan beberapa kali tanpa Anda sadari, dan mereka diizinkan untuk melakukan itu karena spec HTTP mengatakan bahwaPUT
idempoten . Jika AndaPUT
tidak idempoten, maka ini akan merusak layanan Anda .GET
danHEAD
murni serta bebas efek samping. Beberapa orang kehilangan data, karena mereka menggunakan aplikasi web yang dirancang dengan buruk, di mana penghapusan konten dilakukan denganGET
permintaan, dan akselerator web dengan senang hati mengirimGET
permintaan ke semua URI yang dapat ditemukannya.