Bagaimana cara mendesain pencarian / penyaringan yang tenang? [Tutup]

457

Saat ini saya sedang merancang dan mengimplementasikan API RESTful di PHP. Namun, saya tidak berhasil menerapkan desain awal saya.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

Sejauh ini cukup standar, bukan?

Masalah saya dengan yang pertama GET /users. Saya sedang mempertimbangkan mengirim parameter di badan permintaan untuk memfilter daftar. Ini karena saya ingin dapat menentukan filter kompleks tanpa mendapatkan url yang sangat panjang, seperti:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Sebaliknya saya ingin memiliki sesuatu seperti:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

yang jauh lebih mudah dibaca dan memberi Anda kemungkinan besar untuk mengatur filter yang kompleks.

Bagaimanapun, file_get_contents('php://input')tidak mengembalikan badan GETpermintaan untuk permintaan. Saya juga mencoba http_get_request_body(), tetapi shared hosting yang saya gunakan tidak ada pecl_http. Tidak yakin itu akan membantu.

Saya menemukan pertanyaan ini dan menyadari bahwa GET mungkin tidak seharusnya memiliki badan permintaan. Itu agak tidak meyakinkan, tetapi mereka menyarankan untuk tidak melakukannya.

Jadi sekarang saya tidak yakin apa yang harus saya lakukan. Bagaimana Anda merancang fungsi pencarian / penyaringan RESTful?

Saya kira saya bisa menggunakannya POST, tapi itu sepertinya tidak begitu tenang.

Erik B
sumber
7
kemungkinan duplikat dari RESTful URL design for search
outis
60
Hati-hati!!! Metode GET harus IDEMPOTENT, dan harus "cacheable". Jika Anda mengirim informasi dalam tubuh Bagaimana sistem bisa cache permintaan Anda? HTTP memungkinkan caching permintaan GET hanya menggunakan URL, bukan badan permintaan. Misalnya, dua permintaan ini: example.com {test: "some"} example.com {anotherTest: "some2"} dianggap sama oleh sistem cache: Keduanya memiliki URL yang persis sama
jfcorugedo
15
Hanya untuk menambahkan, Anda harus POST ke / pengguna (koleksi) dan bukan / pengguna (pengguna tunggal).
Mladen B.
1
Hal lain yang perlu dipertimbangkan adalah sebagian besar server aplikasi memiliki log akses yang mencatat url dan mungkin ada apa pun di antaranya. Jadi mungkin ada beberapa info yang tidak diinginkan bocor pada GET.
user3206144
2
Kemungkinan duplikat desain URL
RESTful

Jawaban:

396

Cara terbaik untuk mengimplementasikan pencarian RESTful adalah dengan menganggap pencarian itu sendiri sebagai sumber daya. Kemudian Anda dapat menggunakan kata kerja POST karena Anda membuat pencarian. Anda tidak harus benar-benar membuat sesuatu dalam database untuk menggunakan POST.

Sebagai contoh:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Anda membuat pencarian dari sudut pandang pengguna. Detail implementasi ini tidak relevan. Beberapa API yang tenang bahkan mungkin tidak membutuhkan kegigihan. Itu adalah detail implementasi.

Jason Harrelson
sumber
209
Satu batasan signifikan untuk menggunakan permintaan POST untuk titik akhir pencarian adalah bahwa itu tidak dapat di-bookmark. Bookmark hasil pencarian (terutama pertanyaan kompleks) bisa sangat berguna.
sofa dan
73
Menggunakan POST untuk melakukan pencarian dapat mematahkan batasan cache REST. whatisrest.com/rest_constraints/cache_excerps
Filipe
56
Pencarian berdasarkan sifatnya bersifat sementara: data berevolusi antara dua pencarian dengan parameter yang sama, jadi saya pikir permintaan GET tidak memetakan secara bersih ke pola pencarian. Sebaliknya, permintaan pencarian harus POST (/ Resource / pencarian), maka Anda dapat menyimpan pencarian itu dan mengarahkan ulang ke hasil pencarian, misalnya / Resource / search / iyn3zrt. Dengan begitu, GET permintaan berhasil dan masuk akal.
sleblanc
32
Saya tidak berpikir posting adalah metode yang cocok untuk pencarian, data untuk permintaan GET normal juga dapat bervariasi dari waktu ke waktu.
heran
82
Ini benar-benar jawaban yang paling buruk. Saya tidak percaya ini memiliki begitu banyak upvotes. Jawaban ini menjelaskan alasannya: programmers.stackexchange.com/questions/233164/…
richard
141

Jika Anda menggunakan badan permintaan dalam permintaan GET, Anda melanggar prinsip REST, karena permintaan GET Anda tidak akan dapat di-cache, karena sistem cache hanya menggunakan URL.

Dan yang lebih buruk, URL Anda tidak dapat di-bookmark, karena URL tidak berisi semua informasi yang diperlukan untuk mengarahkan pengguna ke halaman ini

Gunakan parameter URL atau Pertanyaan alih-alih parameter isi permintaan.

misalnya:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

Bahkan, HTTP RFC 7231 mengatakan bahwa:

Muatan dalam pesan permintaan GET tidak memiliki semantik yang ditentukan; mengirim badan muatan pada permintaan GET dapat menyebabkan beberapa implementasi yang ada menolak permintaan tersebut.

Untuk informasi lebih lanjut lihat di sini

jfcorugedo
sumber
29
Belajar dari kesalahan saya - Saya merancang api menggunakan saran jawaban yang diterima (POSTing json), tetapi saya pindah ke parameter url. Kemampuan bookmark mungkin lebih penting daripada yang Anda pikirkan. Dalam kasus saya, ada kebutuhan untuk mengarahkan lalu lintas ke permintaan pencarian tertentu (kampanye iklan). Juga, menggunakan API riwayat lebih masuk akal dengan parameter URL.
Jake
2
Itu tergantung pada bagaimana menggunakannya. Jika Anda menautkan ke URL yang memuat halaman berdasarkan parameter-parameter itu, itu masuk akal, tetapi jika halaman utama melakukan panggilan AJAX hanya untuk mendapatkan data berdasarkan parameter filter, Anda tetap tidak dapat mem-bookmark karena itu merupakan panggilan ajax dan tidak ada hubungannya. Secara alami, Anda juga bisa membuat bookmark URL yang ketika Anda pergi ke sana membangun filter dan POST untuk panggilan ajax dan itu akan berfungsi dengan baik.
Daniel Lorenz
@DanielLorenz Untuk pengalaman pengguna terbaik, URL masih harus diubah melalui API Riwayat dalam kasus itu. Saya tidak tahan ketika situs web tidak mengizinkan penggunaan fungsionalitas browser untuk menavigasi ke halaman sebelumnya. Dan jika itu adalah halaman yang dibuat oleh sisi server standar, satu-satunya cara untuk membuatnya dapat dip-bookmark adalah menggunakan permintaan GET. Tampaknya parameter kueri yang bagus adalah solusi terbaik.
Nathan
@Nathan Saya pikir saya salah membaca jawaban ini. Saya sedang berbicara tentang menggunakan parameter string kueri dalam get. Anda tidak boleh menggunakan parameter tubuh dalam panggilan GET karena itu akan sama sekali tidak berguna. Saya berbicara lebih banyak tentang GET dengan string kueri yang dapat digunakan / ditandai dan kemudian pada saat permulaan halaman, Anda dapat menggunakan parameter tersebut untuk membangun filter ke POST, menggunakan parameter tersebut untuk mendapatkan data. Sejarah masih akan bekerja dengan baik dalam skenario itu.
Daniel Lorenz
@DanielLorenz Ah oke itu masuk akal. Saya pikir saya salah mengerti apa yang Anda katakan.
Nathan
70

Tampaknya penyaringan / pencarian sumber daya dapat diimplementasikan dengan tenang. Idenya adalah untuk memperkenalkan titik akhir baru yang disebut /filters/atau /api/filters/.

Menggunakan filter titik akhir ini dapat dianggap sebagai sumber daya dan karenanya dibuat melalui POSTmetode. Cara ini - tentu saja - badan dapat digunakan untuk membawa semua parameter serta struktur pencarian / filter yang kompleks dapat dibuat.

Setelah membuat filter seperti itu, ada dua kemungkinan untuk mendapatkan hasil pencarian / filter.

  1. Sumber daya baru dengan ID unik akan dikembalikan bersama dengan 201 Createdkode status. Kemudian menggunakan ID ini GETpermintaan dapat dibuat untuk /api/users/menyukai:

    GET /api/users/?filterId=1234-abcd
    
  2. Setelah filter baru dibuat melalui POSTitu tidak akan membalas dengan 201 Createdtetapi sekaligus 303 SeeOtherdengan Locationheader yang menunjuk ke /api/users/?filterId=1234-abcd. Redirect ini akan ditangani secara otomatis melalui pustaka yang mendasarinya.

Dalam kedua skenario, dua permintaan perlu dibuat untuk mendapatkan hasil yang difilter - ini dapat dianggap sebagai kelemahan, terutama untuk aplikasi seluler. Untuk aplikasi seluler saya akan menggunakan POSTpanggilan tunggal untuk /api/users/filter/.

Bagaimana cara menjaga filter yang dibuat?

Mereka dapat disimpan dalam DB dan digunakan nanti. Mereka juga dapat disimpan di beberapa penyimpanan sementara misalnya redis dan memiliki beberapa TTL setelah itu mereka akan kedaluwarsa dan akan dihapus.

Apa kelebihan dari ide ini?

Filter, hasil yang difilter dapat di-cache dan bahkan dapat di-bookmark.

Opal
sumber
2
nah ini seharusnya jawaban yang diterima. Anda tidak melanggar prinsip-prinsip REST dan Anda dapat membuat pertanyaan panjang yang rumit untuk sumber daya. Ini bagus, bersih dan kompatibel dengan bookmark. Satu-satunya kelemahan tambahan adalah kebutuhan untuk menyimpan pasangan kunci / nilai untuk filter yang dibuat, dan dua langkah permintaan yang telah disebutkan.
dantebarba
2
Satu-satunya perhatian dengan pendekatan ini adalah, jika Anda memiliki filter tanggal-waktu dalam kueri (atau nilai yang terus berubah). Maka jumlah filter untuk disimpan dalam db (atau cache) tidak terhitung.
Rvy Pandey
17

Saya pikir Anda harus pergi dengan parameter permintaan tetapi hanya selama tidak ada header HTTP yang sesuai untuk mencapai apa yang ingin Anda lakukan. The spesifikasi HTTP tidak secara eksplisit mengatakan, bahwa GET tidak bisa memiliki tubuh. Namun makalah ini menyatakan:

Dengan konvensi, ketika metode GET digunakan, semua informasi yang diperlukan untuk mengidentifikasi sumber daya dikodekan dalam URI. Tidak ada konvensi dalam HTTP / 1.1 untuk interaksi yang aman (misalnya, pengambilan) di mana klien memasok data ke server dalam badan entitas HTTP daripada di bagian permintaan URI. Ini berarti bahwa untuk operasi yang aman, URI mungkin panjang.

Daff
sumber
6
ElasticSearch juga melakukan GET dengan tubuh dan bekerja dengan baik!
Tarun Sapra
Ya, tetapi mereka mengontrol implementasi server mungkin bukan pada jalinan.
user432024
7

Karena saya menggunakan backend laravel / php saya cenderung menggunakan sesuatu seperti ini:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP secara otomatis mengubah []params menjadi array, jadi dalam contoh ini saya akan berakhir dengan $filtervariabel yang menampung array / objek filter, bersama dengan halaman dan sumber daya terkait yang ingin saya muat.

Jika Anda menggunakan bahasa lain, ini mungkin masih merupakan konvensi yang bagus dan Anda bisa membuat parser untuk dikonversi []ke array.

the-a-train
sumber
Pendekatan ini terlihat bagus, tetapi mungkin ada masalah dengan menggunakan tanda kurung siku di URL, lihat apa-karakter-dapat-satu-gunakan-dalam-url
Sky
2
@Sky Ini bisa dihindari dengan pengkodean URI [dan ]. Menggunakan representasi yang dikodekan dari karakter-karakter ini untuk mengelompokkan parameter permintaan adalah praktik yang sudah dikenal luas. Ini bahkan digunakan di JSON: spesifikasi API .
jelhan
6

Jangan terlalu khawatir jika API awal Anda sepenuhnya tenang atau tidak (khususnya ketika Anda baru saja dalam tahap alpha). Dapatkan pipa back-end bekerja terlebih dahulu. Anda selalu dapat melakukan semacam transformasi URL / penulisan ulang untuk memetakan berbagai hal, menyempurnakannya sampai Anda mendapatkan sesuatu yang cukup stabil untuk pengujian luas ("beta").

Anda dapat menentukan URI yang parameternya dikodekan oleh posisi dan konvensi pada URI sendiri, diawali oleh jalur yang Anda tahu akan selalu dipetakan ke sesuatu. Saya tidak tahu PHP, tapi saya berasumsi bahwa fasilitas seperti itu ada (seperti yang ada dalam bahasa lain dengan kerangka kerja web):

.yaitu. Lakukan jenis pencarian "pengguna" dengan param [i] = nilai [i] untuk i = 1..4 di toko # 1 (dengan value1, value2, value3, ... sebagai singkatan untuk parameter kueri URI):

1) GET /store1/search/user/value1,value2,value3,value4

atau

2) GET /store1/search/user,value1,value2,value3,value4

atau sebagai berikut (meskipun saya tidak akan merekomendasikannya, lebih lanjut tentang itu nanti)

3) GET /search/store1,user,value1,value2,value3,value4

Dengan opsi 1, Anda memetakan semua URI yang diawali dengan /store1/search/userpenangan pencarian (atau apa pun penandaan PHP) default untuk melakukan pencarian sumber daya di bawah store1 (setara dengan /search?location=store1&type=user.

Dengan konvensi yang didokumentasikan dan ditegakkan oleh API, nilai parameter 1 hingga 4 dipisahkan dengan koma dan disajikan dalam urutan itu.

Opsi 2 menambahkan tipe pencarian (dalam hal ini user) sebagai parameter posisi # 1. Pilihan mana pun hanyalah pilihan kosmetik.

Opsi 3 juga dimungkinkan, tetapi saya rasa saya tidak menyukainya. Saya pikir kemampuan pencarian dalam sumber daya tertentu harus disajikan dalam URI itu sendiri sebelum pencarian itu sendiri (seolah-olah menunjukkan dengan jelas dalam URI bahwa pencarian itu spesifik dalam sumber daya itu.)

Keuntungan dari melewati parameter pada URI adalah bahwa pencarian adalah bagian dari URI (sehingga memperlakukan pencarian sebagai sumber daya, sumber daya yang isinya dapat - dan akan - berubah dari waktu ke waktu.) Kerugiannya adalah bahwa urutan parameter adalah wajib .

Setelah Anda melakukan sesuatu seperti ini, Anda dapat menggunakan GET, dan itu akan menjadi sumber daya baca-saja (karena Anda tidak dapat POST atau PUT untuk itu - itu akan diperbarui ketika GET'ed). Ini juga akan menjadi sumber daya yang hanya ada saat dijalankan.

Anda juga bisa menambahkan lebih banyak semantik dengan menyimpan hasil untuk periode waktu tertentu atau dengan DELETE yang menyebabkan cache dihapus. Namun, ini mungkin bertentangan dengan apa yang biasanya digunakan DELETE untuk orang (dan karena orang biasanya mengontrol caching dengan header caching.)

Bagaimana Anda melakukannya akan menjadi keputusan desain, tetapi ini akan menjadi cara saya akan pergi tentang. Itu tidak sempurna, dan saya yakin akan ada kasus di mana melakukan ini bukanlah hal terbaik untuk dilakukan (khusus untuk kriteria pencarian yang sangat kompleks).

luis.espinal
sumber
7
Yo, jika Anda (seseorang, siapa pun / apa pun) hal-hal yang tepat untuk menurunkan jawaban saya, apakah itu akan menyakiti ego Anda untuk setidaknya memberikan komentar yang menunjukkan apa yang sebenarnya tidak Anda setujui? Saya tahu ini adalah interweebz, tapi ...;)
luis.espinal
107
Saya tidak mengundurkan diri, tetapi kenyataan bahwa pertanyaannya dimulai dengan: "Saya sedang merancang dan mengimplementasikan API RESTful" dan jawaban Anda dimulai dengan "Jangan terlalu khawatir jika API awal Anda sepenuhnya tenang atau tidak" terasa salah bagiku. Jika Anda merancang API, Anda merancang API. Pertanyaannya adalah bertanya bagaimana merancang API yang terbaik, bukan tentang apakah API harus dirancang.
gardarh
14
API adalah sistem, bekerja pada API pertama, bukan pipa backend, implementasi pertama bisa / seharusnya hanya menjadi tiruan. HTTP memiliki mekanisme untuk melewatkan parameter, Anda menyarankannya diciptakan kembali, tetapi lebih buruk (parameter yang dipesan daripada pasangan nilai nama). Karena itu suara turun.
Steven Herod
14
@ Gardarh - ya, itu terasa salah, tetapi kadang-kadang, itu pragmatis. Tujuan utamanya adalah merancang API yang berfungsi untuk konteks bisnis yang ada. Jika pendekatan RESTFULL sepenuhnya sesuai dengan bisnis yang dihadapi, maka lakukanlah. Jika tidak, maka jangan lakukan itu. Yaitu, rancang API yang memenuhi persyaratan bisnis spesifik Anda. Berkeliaran mencoba membuatnya RESTfull sebagai persyaratan utamanya tidak jauh berbeda dengan bertanya "bagaimana cara menggunakan pola adaptor dalam masalah X / Y." Jangan memakai paradigma tanduk kecuali jika mereka memecahkan masalah yang sebenarnya dan berharga.
luis.espinal
1
Saya melihat sumber daya sebagai beberapa kumpulan negara, dan parameter sebagai sarana untuk memanipulasi representasi negara itu secara parametrik. Pikirkan seperti ini, jika Anda dapat menggunakan tombol dan sakelar untuk menyesuaikan cara sumber daya ditampilkan (perlihatkan / sembunyikan bit tertentu, atur secara berbeda, dll ...) kontrol tersebut adalah params. Jika itu sebenarnya sumber yang berbeda ('/ album' vs '/ artis', misalnya), saat itulah ia harus diwakili di jalur. Lagipula, itulah yang intuitif bagi saya.
Eric Elliott
2

FYI: Saya tahu ini agak terlambat tetapi bagi siapa saja yang tertarik. Tergantung pada seberapa tenang Anda ingin menjadi, Anda harus menerapkan strategi penyaringan Anda sendiri karena spesifikasi HTTP tidak begitu jelas tentang hal ini. Saya ingin menyarankan pengkodean url semua parameter filter misalnya

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Saya tahu itu jelek tapi saya pikir itu cara yang paling tenang untuk melakukannya dan harus mudah diurai di sisi server :)

betis
sumber