Desain URL tenang untuk pencarian

427

Saya mencari cara yang masuk akal untuk mewakili pencarian sebagai URL yang tenang.

Pengaturan: Saya punya dua model, Mobil dan Garasi, di mana Mobil bisa di Garasi. Jadi url saya terlihat seperti:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

Mobil bisa ada sendiri (karenanya / mobil), atau bisa ada di garasi. Apa cara yang tepat untuk mewakili, katakanlah, semua mobil di garasi tertentu? Sesuatu seperti:

/garage/yyy/cars     ?

Bagaimana dengan penyatuan mobil di garasi yyy dan zzz?

Apa cara yang tepat untuk mewakili pencarian mobil dengan atribut tertentu? Katakan: tunjukkan semua sedan biru dengan 4 pintu:

/car/search?color=blue&type=sedan&doors=4

atau seharusnya / mobil saja?

Penggunaan "pencarian" tampaknya tidak pantas di sana - apa cara / istilah yang lebih baik? Haruskah hanya:

/cars/?color=blue&type=sedan&doors=4

Haruskah parameter pencarian menjadi bagian dari PATHINFO atau QUERYSTRING?

Singkatnya, saya mencari panduan untuk desain url REST lintas-model, dan untuk pencarian.

[Pembaruan] Saya suka jawaban Justin, tapi dia tidak membahas kasus pencarian multi-bidang:

/cars/color:blue/type:sedan/doors:4

atau semacam itu. Bagaimana kita pergi?

/cars/color/blue

ke beberapa kasus lapangan?

Parand
sumber
16
Meskipun terlihat lebih baik dalam bahasa Inggris, campuran /carsdan /cartidak semantik dan karena itu ide yang buruk. Selalu gunakan jamak ketika ada lebih dari satu item di bawah kategori itu.
Zaz
4
Ini adalah jawaban yang buruk. Pencarian harus menggunakan string kueri. String kueri 100% TENANG bila digunakan dengan benar (yaitu, untuk pencarian).
pbreitenbach

Jawaban:

435

Untuk pencarian, gunakan querystrings. Ini sangat tenang:

/cars?color=blue&type=sedan&doors=4

Keuntungan dari querystrings reguler adalah standar dan dipahami secara luas dan dapat dihasilkan dari formulir.

pbreitenbach
sumber
42
Ini benar. Inti dari string permintaan adalah untuk melakukan hal-hal seperti pencarian.
aehlke
22
Memang ini benar karena, per RFC3986 , jalan dan querystring mengidentifikasi sumber daya. Terlebih lagi, penamaan yang tepat hanyalah /cars?color=whatever.
Lloeki
35
Bagaimana dengan kasus di mana Anda ingin pembanding (>, <, <=,> =)? / mobil? peringkat <= 3?
Jesse
3
Bagaimana jika Anda ingin mengakses sumber daya yang berada di bawah string kueri? Mis. Tidak /cars?color=blue&type=sedan&doors=4/enginesakan berfungsi
Abe Voelker
9
@ mjs /cars?param=valueadalah untuk pemfilteran sederhana pada daftar mobil dan /cars/search?param=valueuntuk membuat pencarian (dengan ou tanpa persisten) di mana hasilnya mungkin berisi skor pencarian, kategorisasi, dll. Anda juga dapat membuat / menghapus seperti pencarian yang bernama /cars/search/mysearch. Lihat itu: stackoverflow.com/a/18933902/1480391
Yves M.
121

The tenang desain URL cantik adalah tentang menampilkan sumber daya berdasarkan struktur (direktori-seperti struktur, tanggal: artikel / 2005/5/13, objek dan atribut itu, ..), garis miring /menunjukkan struktur hirarkis, gunakan -idsebagai gantinya.

Struktur hierarkis

Secara pribadi saya lebih suka:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

Jika pengguna menghapus /car-idbagian, itu membawa carspratinjau - intuitif. Pengguna tahu persis di mana pohon itu berada, apa yang ia lihat. Dia tahu dari pandangan pertama, bahwa garasi dan mobil berhubungan. /car-idjuga menunjukkan bahwa itu milik bersama tidak seperti /car/id.

Mencari

Permintaan pencarian tidak apa-apa , hanya ada preferensi Anda, apa yang harus diperhitungkan. Bagian yang lucu muncul ketika bergabung dengan pencarian (lihat di bawah).

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

Atau pada dasarnya apa pun yang bukan garis miring seperti yang dijelaskan di atas.
Rumus:, /cars[?;]color[=-:]blue[,;+&]* meskipun saya tidak akan menggunakan &tanda karena tidak dapat dikenali dari teks pada pandangan pertama.

** Tahukah Anda bahwa meneruskan objek JSON di URI adalah RESTful? **

Daftar opsi

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

fitur yang mungkin?

Negasikan string pencarian (!)
Untuk mencari mobil apa pun, tetapi tidak hitam dan merah :
?color=!black,!red
color:(!black,!red)

Pencarian yang tergabung
Cari mobil merah atau biru atau hitam dengan 3 pintu di garasi id 1..20 atau 101..103 atau 999 tetapi tidak 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Anda dapat membuat kueri pencarian yang lebih kompleks. (Lihatlah CSS3 atribut yang cocok untuk gagasan pencocokan substring. Misalnya mencari pengguna yang mengandung "bar" user*=bar.)

Kesimpulan

Anyway, ini mungkin menjadi bagian paling penting bagi Anda, karena Anda dapat melakukannya namun Anda seperti setelah semua, hanya perlu diingat bahwa tenang URI merupakan struktur yang mudah dipahami misalnya direktori-seperti /directory/file, /collection/node/item, tanggal /articles/{year}/{month}/{day}.. Dan ketika Anda menghilangkan salah satu segmen terakhir, Anda langsung tahu apa yang Anda dapatkan.

Jadi .., semua karakter ini dibiarkan tanpa kode :

  • unreserved: a-zA-Z0-9_.-~
    Biasanya diizinkan dikodekan dan tidak, kedua penggunaan kemudian setara.
  • karakter spesial: $-_.+!*'(),
  • dilindungi undang-undang: ;/?:@=&
    Dapat digunakan tanpa kode untuk tujuan yang mereka wakili, jika tidak mereka harus dikodekan.
  • tidak aman: <>"#%{}|\^~[]`
    Mengapa tidak aman dan mengapa lebih baik dikodekan: RFC 1738 lihat 2.2

    Lihat juga RFC 1738 # halaman-20 untuk kelas karakter lainnya.

RFC 3986 lihat 2.2
Terlepas dari apa yang saya katakan sebelumnya, berikut adalah perbedaan umum dari pembatas, yang berarti bahwa beberapa " lebih penting" dari yang lain.

  • pembatas generik: :/?#[]@
  • sub-delimeter: !$&'()*+,;=

Lebih banyak bacaan:
Hierarki: lihat 2.3 , lihat 1.2.3
parameter jalan sintaks parameter
pencocokan CSS3 atribut
IBM: RESTful Web services - Dasar-dasar
Catatan: RFC 1738 telah diperbarui oleh RFC 3986

Qwerty
sumber
3
Saya tidak percaya saya belum berpikir untuk menggunakan JSON dalam string kueri. Ini jawaban untuk masalah yang saya hadapi - struktur pencarian yang kompleks tanpa menggunakan POST. Juga, ide-ide lain yang Anda berikan dalam jawaban Anda juga sangat dihargai. Terima kasih banyak!
gustavohenke
4
@Qwerty: pos bagus! Saya bertanya-tanya: satu-satunya alasan menggunakan ;dibandingkan dengan &keterbacaan? Karena jika demikian, saya pikir saya sebenarnya akan memilih &pembatas yang lebih umum ... bukan? :) terima kasih!
Flo
3
@Flo Ya persis :), tetapi perlu diingat bahwa &sebagai pembatas hanya diketahui oleh pengembang. Orang tua, kakek-nenek, dan populasi yang tidak berpendidikan menerima pembatas sebagaimana digunakan dalam teks tertulis umum.
Qwerty
17
Mengapa membuat skema yang tidak standar ketika string kueri dipahami dengan baik dan standar?
pbreitenbach
1
@Qwerty tidak ada yang menghentikan Anda dari / cari? Mobil = merah, biru, hijau & garasi = 1,2,3 Atau jika Anda menggunakan formulir <multiselect>: / cari? Mobil = merah & mobil = biru & garasi = 1 & garasi = 2
pbreitenbach
36

Meskipun memiliki parameter di jalur memiliki beberapa keunggulan, ada, IMO, beberapa faktor yang lebih penting.

  • Tidak semua karakter yang dibutuhkan untuk permintaan pencarian diizinkan dalam URL. Sebagian besar tanda baca dan karakter Unicode harus dikodekan URL sebagai parameter string kueri. Saya bergulat dengan masalah yang sama. Saya ingin menggunakan XPath di URL, tetapi tidak semua sintaks XPath kompatibel dengan jalur URI. Jadi untuk jalur sederhana, /cars/doors/driver/lock/combinationakan lebih tepat untuk menemukan combinationelemen ' ' di dokumen XML pintu pengemudi. Tetapi /car/doors[id='driver' and lock/combination='1234']tidak begitu ramah.

  • Ada perbedaan antara memfilter sumber daya berdasarkan salah satu atributnya dan menentukan sumber daya.

    Misalnya, sejak

    /cars/colors mengembalikan daftar semua warna untuk semua mobil (sumber daya yang dikembalikan adalah kumpulan objek warna)

    /cars/colors/red,blue,green akan mengembalikan daftar objek warna yang merah, biru atau hijau, bukan koleksi mobil.

    Untuk mengembalikan mobil, jalannya adalah

    /cars?color=red,blue,green atau /cars/search?color=red,blue,green

  • Parameter di jalur lebih sulit dibaca karena pasangan nama / nilai tidak terisolasi dari sisa jalur, yang bukan pasangan nama / nilai.

Satu komentar terakhir. Saya lebih suka /garages/yyy/cars(selalu jamak) daripada /garage/yyy/cars(mungkin itu salah ketik dalam jawaban asli) karena itu menghindari mengubah jalan antara tunggal dan jamak. Untuk kata-kata dengan 's' yang ditambahkan, perubahannya tidak terlalu buruk, tetapi berubah /person/yyy/friendsmenjadi /people/yyyrumit.

Doug Domeny
sumber
2
ya, saya setuju ... selain saya url struktur jalan harus mencerminkan hubungan alami antara entitas, semacam peta sumber daya saya, seperti garasi memiliki banyak mobil, mobil milik garasi dan sebagainya ... dan biarkan parameter filter, karena itu yang kita bicarakan, untuk que querystring ... bagaimana menurutmu?
opensas
31

Untuk memperluas jawaban Peter - Anda dapat menjadikan Cari sumber daya kelas satu:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

Sumber daya pencarian akan memiliki bidang untuk warna, membuat model, status garasi, dll dan dapat ditentukan dalam XML, JSON, atau format lainnya. Seperti sumber daya Mobil dan Garasi, Anda dapat membatasi akses ke Pencarian berdasarkan otentikasi. Pengguna yang sering menjalankan Pencarian yang sama dapat menyimpannya di profil mereka sehingga mereka tidak perlu dibuat kembali. URL akan cukup pendek sehingga dalam banyak kasus mereka dapat dengan mudah diperdagangkan melalui email. Penelusuran yang disimpan ini dapat menjadi dasar dari umpan RSS khusus, dan sebagainya.

Ada banyak kemungkinan untuk menggunakan Pencarian saat Anda menganggapnya sebagai sumber daya.

Idenya dijelaskan lebih terinci dalam Railscast ini .

Apodaca kaya
sumber
6
bukankah pendekatan ini bertentangan dengan gagasan bekerja dengan protokol yang gelisah? Maksudku, melanjutkan pencarian ke db adalah semacam memiliki koneksi stateful ... bukan?
opensas
5
Ini lebih seperti memiliki layanan negara. Kami juga mengubah status layanan setiap kali kami menambah Mobil atau Garasi baru. Pencarian hanyalah sumber daya lain yang dapat digunakan dengan berbagai kata kerja HTTP.
Rich Apodaca
2
Bagaimana cara di atas mendefinisikan konvensi URI?
Rich Apodaca
3
REST tidak ada hubungannya dengan URI cantik atau sarang URI dll. Jika Anda mendefinisikan URI sebagai bagian dari API Anda, itu bukan REST.
aehlke
2
Saya pernah berdebat tentang ini sebelumnya. Ini bukan cara yang pasti, tetapi itu adalah hal yang mengerikan. 'Hapus' pencarian tidak sepenuhnya jelas, di sini Anda mengatakan itu menghapus entitas pencarian ini, tetapi saya ingin menggunakannya menghapus hasil yang saya temukan melalui pencarian itu. Harap jangan tambahkan 'pencarian' sebagai sumber.
thecoshman
12

Jawaban Justin mungkin adalah cara yang harus ditempuh, walaupun dalam beberapa aplikasi mungkin masuk akal untuk mempertimbangkan pencarian tertentu sebagai sumber daya sendiri, seperti jika Anda ingin mendukung pencarian yang disimpan bernama:

/search/{searchQuery}

atau

/search/{savedSearchName}
Peter Hilton
sumber
11
tidak. tidak pernah masuk akal untuk memiliki tindakan menjadi sumber daya.
thecoshman
3
@thecoshman sebagaimana disebutkan dalam komentar di atas, pencarian juga merupakan kata benda.
andho
6

Saya menggunakan dua pendekatan untuk mengimplementasikan pencarian.

1) Kasing paling sederhana, untuk menanyakan elemen terkait, dan untuk navigasi.

    /cars?q.garage.id.eq=1

Ini berarti, permintaan mobil yang memiliki ID garasi sama dengan 1.

Dimungkinkan juga untuk membuat pencarian yang lebih kompleks:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Mobil di semua garasi di FirstStreet yang tidak berwarna merah (halaman ke-3, 100 elemen per halaman).

2) Kueri kompleks dianggap sebagai sumber daya reguler yang dibuat dan dapat dipulihkan.

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

Badan POST untuk pembuatan pencarian adalah sebagai berikut:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

Berbasis di Grails (kriteria DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

pengguna2108278
sumber
5

Ini bukan REST. Anda tidak dapat menentukan URI untuk sumber daya di dalam API Anda. Navigasi sumber daya harus digerakkan oleh hypertext. Tidak apa-apa jika Anda menginginkan URI yang cantik dan kopling dalam jumlah besar, tetapi jangan menyebutnya REST, karena secara langsung melanggar batasan arsitektur RESTful.

Lihat artikel ini oleh penemu REST.

aehlke
sumber
28
Anda benar bahwa itu bukan REST, itu desain URL untuk sistem RESTful. Anda juga salah mengatakan bahwa itu melanggar arsitektur RESTful. Kendala hypertext dari REST adalah ortogonal ke desain URL yang baik untuk sistem RESTful; Saya ingat ada diskusi dengan Roy T. Fielding pada daftar REST beberapa tahun yang lalu bahwa saya berpartisipasi di mana dia menyatakannya secara eksplisit. Mengatakan dengan cara lain adalah mungkin untuk memiliki desain hypertext dan URL. Desain URL untuk sistem RESTful seperti lekukan dalam pemrograman; tidak diperlukan tetapi ide yang sangat bagus (mengabaikan Python, dll.)
MikeSchinkel
2
Maaf, kamu benar. Saya baru saja mendapat kesan dari OP bahwa dia akan membuat klien sadar bagaimana membangun URL - dia akan membuat URL "layout" bagian dari API-nya. Itu akan menjadi pelanggaran REST.
aehlke
@ aehlke, Anda harus memperbarui jawaban Anda agar sesuai dengan komentar Anda.
James McMahon
1
Ini adalah model kematangan Richardson level 2 yang sesuai. Anda merujuk ke level 3. Hanya menerima REST sebagai sesuatu yang semakin dapat diadopsi.
Jules Randolph
1
@Jules Randolph - permintaan maaf, jawaban saya ditulis hanya beberapa bulan setelah model kematangan Richardson pertama kali diciptakan dan sebelum Martin Fowler dan penulis lain mempopulerkannya :) Memang, ini adalah model yang instruktif untuk diikuti. Jangan ragu untuk mengedit jawabannya.
aehlke
1

Meskipun saya menyukai respons Justin, saya merasa itu lebih akurat mewakili filter daripada pencarian. Bagaimana jika saya ingin tahu tentang mobil dengan nama yang dimulai dengan cam?

Cara saya melihatnya, Anda bisa membangunnya ke cara Anda menangani sumber daya tertentu:
/ mobil / cam *

Atau, Anda bisa menambahkannya ke filter:
/ mobil / pintu / 4 / nama / cam * / warna / merah, biru, hijau

Secara pribadi, saya lebih suka yang terakhir, namun saya sama sekali bukan ahli REST (setelah pertama kali mendengarnya sekitar 2 minggu lalu ...)


sumber
Seperti ini:/cars?name=cam*
DanMan
1

RESTful tidak merekomendasikan penggunaan kata kerja di URL's / cars / search tidak nyenyak. Cara yang tepat untuk memfilter / mencari / paginate API Anda adalah melalui Parameter Kueri. Namun mungkin ada kasus ketika Anda harus melanggar norma. Misalnya, jika Anda mencari di beberapa sumber, maka Anda harus menggunakan sesuatu seperti / search? Q = query

Anda dapat mengunjungi http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ untuk memahami praktik terbaik untuk merancang API RESTful

java_geek
sumber
1
Cari juga kata benda 😀
jith912
1

Selain itu saya juga menyarankan:

/cars/search/all{?color,model,year}
/cars/search/by-parameters{?color,model,year}
/cars/search/by-vendor{?vendor}

Di sini, Searchdianggap sebagai sumber daya anak Cars.

aux
sumber
1

Ada banyak opsi bagus untuk kasus Anda di sini. Anda tetap harus mempertimbangkan menggunakan tubuh POST.

String kueri sempurna untuk contoh Anda, tetapi jika Anda memiliki sesuatu yang lebih rumit, misalnya daftar panjang item atau persyaratan boolean yang sewenang-wenang, Anda mungkin ingin mendefinisikan posting sebagai dokumen, yang dikirim klien melalui POST.

Ini memungkinkan deskripsi pencarian yang lebih fleksibel, serta menghindari batas panjang URL Server.

estani
sumber
-4

Saran saya adalah ini:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

Edit:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

Semoga itu memberi Anda ide. Pada dasarnya API Istirahat Anda harus mudah ditemukan dan memungkinkan Anda untuk menelusuri data Anda. Keuntungan lain dengan menggunakan URL dan bukan string kueri adalah Anda dapat memanfaatkan mekanisme caching asli yang ada di server web untuk lalu lintas HTTP.

Berikut ini tautan ke halaman yang menjelaskan kejahatan string kueri di REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Saya menggunakan cache Google karena halaman normal tidak berfungsi untuk saya di sini juga tautan itu: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Justin Bozonier
sumber
1
Terima kasih atas jawaban terinci. Yang terakhir, bagaimana jika saya ingin mencari berdasarkan warna dan jumlah pintu? / mobil / warna / merah, biru, hijau / pintu / 4 Itu sepertinya tidak benar.
Parand
2
Koma di URL tidak terasa benar bagi saya, tetapi masih berlaku untuk yang lain. Saya pikir itu hanya perubahan paradigma.
Justin Bozonier
21
Saya tidak suka saran ini. Bagaimana Anda tahu perbedaan antara /cars/colors/red,blue,greendan /cars/colors/green,blue,red? Elemen jalur URI harus hierarkis, dan saya tidak benar-benar melihat hal itu terjadi di sini. Saya pikir ini adalah situasi di mana query-string adalah pilihan yang paling tepat.
troelskn
62
Ini jawaban yang buruk. Bahkan, cara yang tepat untuk menerapkan pencarian adalah dengan string kueri. String pertanyaan tidak jahat sedikitpun ketika digunakan dengan benar. Artikel yang dikutip tidak mengacu pada pencarian. Contoh-contoh yang diberikan jelas disiksa dan tidak cocok dengan lebih banyak parameter.
pbreitenbach
4
querystrings dibuat terutama untuk menyelesaikan masalah permintaan sumber daya, bahkan dengan beberapa parameter. Memutar balik URI untuk mengaktifkan API "RESTful" tampaknya berbahaya dan pandangan pendek - terutama karena Anda harus menulis pemetaan kompleks Anda sendiri hanya untuk menangani berbagai permutasi parameter pada URI. Lebih baik lagi, gunakan gagasan yang sudah ada tentang menggunakan titik koma di URI Anda: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G