Apa metode RESTful terbaik untuk mengembalikan jumlah item dalam suatu objek?

146

Saya sedang mengembangkan layanan REST API untuk situs web jejaring sosial besar tempat saya terlibat. Sejauh ini, ini berfungsi dengan baik. Aku bisa mengeluarkan GET, POST, PUTdan DELETEpermintaan untuk URL objek dan mempengaruhi data saya. Namun, data ini disimpan dalam halaman (dibatasi hingga 30 hasil pada satu waktu).

Namun, apa cara RESTful terbaik untuk mendapatkan jumlah total anggota, melalui API saya?

Saat ini, saya mengeluarkan permintaan ke struktur URL seperti berikut:

  • / api / members —Mengembalikan daftar anggota (30 sekaligus seperti yang disebutkan di atas)
  • / api / members / 1 —Mempengaruhi satu anggota, tergantung pada metode permintaan yang digunakan

Pertanyaan saya adalah: bagaimana saya kemudian menggunakan struktur URL yang serupa untuk mendapatkan jumlah total anggota dalam aplikasi saya? Jelas meminta hanya idbidang (mirip dengan API Grafik Facebook) dan menghitung hasil tidak akan efektif karena hanya potongan dari 30 hasil yang hanya akan dikembalikan.

Martin Bean
sumber

Jawaban:

85

Sementara respon ke / API / users dihalaman dan mengembalikan hanya 30, catatan, tidak ada yang mencegah Anda dari termasuk dalam tanggapan juga jumlah catatan, dan info relevan lainnya, seperti ukuran halaman, nomor halaman / offset, dll .

StackOverflow API adalah contoh bagus dari desain yang sama. Berikut dokumentasi untuk metode Pengguna - https://api.stackexchange.com/docs/users

Franci Penov
sumber
3
+1: Jelas hal yang paling tenang untuk dilakukan jika batas pengambilan akan diberlakukan sama sekali.
Donal Fellows
2
@bzim Anda akan tahu ada halaman berikutnya yang harus diambil karena ada link dengan rel = "next".
Darrel Miller
4
@Donal rel "berikutnya" terdaftar di IANA iana.org/assignments/link-relations/link-relations.txt
Darrel Miller
1
@Darrel - ya, ini bisa dilakukan dengan semua jenis flag "berikutnya" dalam payload. Saya hanya merasa bahwa memiliki jumlah total item koleksi dalam respons itu berharga dengan sendirinya dan berfungsi sebagai bendera "berikutnya" sama saja.
Franci Penov
5
Untuk mengembalikan objek yang bukan daftar item bukanlah implementasi yang tepat dari REST API tetapi REST tidak menyediakan cara apa pun untuk mendapatkan daftar hasil sebagian. Jadi untuk menghormatinya, saya pikir kita harus menggunakan header untuk mengirimkan informasi lain seperti total, token halaman berikutnya, dan token halaman sebelumnya. Saya tidak pernah mencobanya dan saya membutuhkan saran dari pengembang lain.
Loenix
75

Saya telah melakukan beberapa penelitian ekstensif tentang ini dan pertanyaan terkait halaman REST lainnya akhir-akhir ini dan berpikir itu konstruktif untuk menambahkan beberapa temuan saya di sini. Saya memperluas pertanyaan sedikit untuk memasukkan pemikiran tentang paging serta hitungan yang terkait erat.

Header

Metadata halaman disertakan dalam tanggapan dalam bentuk header tanggapan. Manfaat besar dari pendekatan ini adalah bahwa payload respons itu sendiri hanyalah permintaan data sebenarnya yang diminta. Membuat pemrosesan respons lebih mudah untuk klien yang tidak tertarik dengan informasi halaman.

Ada banyak header (standar dan kustom) yang digunakan di alam liar untuk mengembalikan informasi terkait halaman, termasuk jumlah total.

X-Jumlah-Hitung

X-Total-Count: 234

Ini digunakan di beberapa API yang saya temukan di alam liar. Ada juga paket NPM untuk menambahkan dukungan untuk header ini, misalnya Loopback. Beberapa artikel merekomendasikan pengaturan tajuk ini juga.

Ini sering digunakan dalam kombinasi dengan Linktajuk, yang merupakan solusi yang cukup bagus untuk halaman, tetapi kekurangan informasi jumlah total.

Tautan

Link: </TheBook/chapter2>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

Saya merasa, dari banyak membaca tentang subjek ini, bahwa konsensus umum adalah menggunakan Linktajuk untuk menyediakan tautan paging ke klien yang menggunakan rel=next, rel=previousdll. Masalahnya adalah kurangnya informasi tentang berapa banyak total catatan yang ada, yaitu mengapa banyak API menggabungkan ini dengan X-Total-Counttajuk.

Sebagai alternatif, beberapa API dan misalnya standar JsonApi , menggunakan Linkformat tersebut, tetapi menambahkan informasi dalam amplop tanggapan alih-alih ke header. Ini menyederhanakan akses ke metadata (dan menciptakan tempat untuk menambahkan informasi jumlah total) dengan mengorbankan kerumitan pengaksesan data aktual itu sendiri (dengan menambahkan amplop).

Rentang Konten

Content-Range: items 0-49/234

Dipromosikan oleh artikel blog bernama Range header, saya memilih Anda (untuk pagination)! . Penulis membuat alasan kuat untuk menggunakan Rangedan Content-Rangeheader untuk penomoran halaman. Ketika kita hati-hati membaca yang RFC pada header ini, kami menemukan bahwa memperluas maknanya luar rentang byte sebenarnya diantisipasi oleh RFC dan secara eksplisit diperbolehkan. Saat digunakan dalam konteks itemsalih - alih bytes, header Rentang sebenarnya memberi kita cara untuk meminta rentang item tertentu dan menunjukkan kisaran hasil total yang terkait dengan item respons. Header ini juga memberikan cara yang bagus untuk menampilkan jumlah total. Dan itu adalah standar sejati yang sebagian besar memetakan satu-ke-satu ke paging. Itu juga digunakan di alam liar .

Amplop

Banyak API, termasuk yang dari situs web Tanya Jawab favorit kami menggunakan amplop , pembungkus di sekitar data yang digunakan untuk menambahkan informasi meta tentang data. Juga, standar OData dan JsonApi keduanya menggunakan amplop tanggapan.

Kelemahan besar dari ini (imho) adalah bahwa pemrosesan data respons menjadi lebih kompleks karena data aktual harus ditemukan di suatu tempat di dalam amplop. Juga ada banyak format berbeda untuk amplop itu dan Anda harus menggunakan yang benar. Dikatakan bahwa amplop respons dari OData dan JsonApi sangat berbeda, dengan OData mencampurkan metadata di beberapa titik dalam respons.

Pisahkan titik akhir

Saya pikir ini sudah cukup tercakup dalam jawaban lain. Saya tidak menyelidiki sebanyak ini karena saya setuju dengan komentar yang membingungkan karena Anda sekarang memiliki beberapa jenis titik akhir. Saya pikir paling baik jika setiap titik akhir mewakili (kumpulan) sumber daya.

Pikiran lebih lanjut

Kami tidak hanya harus mengkomunikasikan informasi meta halaman yang terkait dengan respons, tetapi juga mengizinkan klien untuk meminta halaman / rentang tertentu. Menarik juga untuk melihat aspek ini untuk mendapatkan solusi yang koheren. Di sini juga kita dapat menggunakan header ( Rangeheader tampaknya sangat cocok), atau mekanisme lain seperti parameter kueri. Beberapa orang menganjurkan memperlakukan halaman hasil sebagai sumber daya terpisah, yang mungkin masuk akal dalam beberapa kasus penggunaan (mis /books/231/pages/52. Saya akhirnya memilih kisaran liar parameter permintaan yang sering digunakan seperti pagesize, page[size]dan limitlain - lain selain mendukung Rangeheader (dan sebagai parameter permintaan demikian juga).

Stijn de Witt
sumber
Saya sangat tertarik pada Rangetajuk, namun saya tidak dapat menemukan cukup bukti bahwa menggunakan apa pun selain bytessebagai jenis rentang, adalah valid.
VisioN
2
Saya pikir bukti paling jelas dapat ditemukan di bagian 14.5 dari RFC : acceptable-ranges = 1#range-unit | "none"Saya pikir formulasi ini secara eksplisit menyisakan ruang untuk unit jangkauan lain selain bytes, meskipun spesifikasi itu sendiri hanya mendefinisikan bytes.
Stijn de Witt
75

Saya lebih suka menggunakan HTTP Headers untuk jenis informasi kontekstual ini.

Untuk jumlah total elemen saya menggunakan X-total-countheader.
Untuk link ke halaman berikutnya, sebelumnya, dll. Saya menggunakan http Linkheader:
http://www.w3.org/wiki/LinkHeader

Github melakukannya dengan cara yang sama: https://developer.github.com/v3/#pagination

Menurut pendapat saya, ini lebih bersih karena dapat digunakan juga saat Anda mengembalikan konten yang tidak mendukung hyperlink (yaitu binari, gambar).

Ondrej Bozek
sumber
7
RFC6648 menghentikan konvensi mengawali nama parameter yang tidak standar dengan string X-.
JDawg
25

Alternatif saat Anda tidak membutuhkan barang yang sebenarnya

Jawaban Franci Penov tentunya merupakan cara terbaik untuk pergi sehingga Anda selalu mengembalikan item bersama dengan semua metadata tambahan tentang entitas Anda yang diminta. Itu cara yang harus dilakukan.

tetapi terkadang mengembalikan semua data tidak masuk akal, karena Anda mungkin tidak membutuhkannya sama sekali. Mungkin yang Anda butuhkan hanyalah metadata tentang sumber daya yang Anda minta. Seperti jumlah total atau jumlah halaman atau yang lainnya. Dalam kasus seperti itu, Anda selalu dapat meminta kueri URL memberi tahu layanan Anda untuk tidak mengembalikan item melainkan hanya metadata seperti:

/api/members?metaonly=true
/api/members?includeitems=0

atau sesuatu yang serupa ...

Robert Koritnik
sumber
11
Menanamkan informasi ini di tajuk memiliki keuntungan bahwa Anda dapat membuat permintaan HEAD untuk sekadar menghitungnya.
felixfbecker
1
@felixfbecker tepatnya, terima kasih telah menemukan kembali roda dan mengacaukan API dengan semua jenis mekanisme yang berbeda :)
EralpB
1
@EralpB Terima kasih telah menemukan kembali roda dan mengacaukan API !? HEAD ditentukan dalam HTTP. metaonlyatau includeitemstidak.
felixfbecker
2
@felixfbecker hanya "persis" dimaksudkan untuk Anda, sisanya untuk OP. Maaf bila membingungkan.
EralpB
1
REST adalah tentang memanfaatkan HTTP dan memanfaatkannya untuk tujuan semaksimal mungkin. Content-Range (RFC7233) harus digunakan dalam kasus ini. Solusi di dalam tubuh tidak baik, terutama karena tidak akan bekerja dengan KEPALA. membuat tajuk baru seperti yang disarankan di sini tidak perlu dan salah.
Vance Shipley
23

Anda dapat mengembalikan hitungan tersebut sebagai header HTTP khusus sebagai tanggapan atas permintaan HEAD. Dengan cara ini, jika klien hanya menginginkan hitungan, Anda tidak perlu mengembalikan daftar sebenarnya, dan tidak perlu URL tambahan.

(Atau, jika Anda berada dalam lingkungan yang terkontrol dari titik akhir ke titik akhir, Anda dapat menggunakan kata kerja HTTP khusus seperti COUNT.)

bzlm
sumber
4
“Header HTTP khusus”? Itu akan berada di bawah judul agak mengejutkan, yang pada gilirannya bertentangan dengan apa yang menurut saya seharusnya RESTful API. Pada akhirnya, ini seharusnya tidak mengejutkan.
Donal Fellows
21
@Donal saya tahu. Tapi semua jawaban bagus sudah diambil. :(
bzlm
1
Saya juga tahu, tapi terkadang Anda harus membiarkan orang lain yang menjawab. Atau buat kontribusi Anda lebih baik dengan cara lain, seperti penjelasan rinci tentang mengapa itu harus dilakukan dengan cara terbaik daripada yang lain.
Donal Fellows
4
Dalam lingkungan yang terkendali, ini mungkin tidak mengejutkan, karena kemungkinan besar akan digunakan secara internal & berdasarkan kebijakan API pengembang Anda. Saya akan mengatakan ini adalah solusi yang baik dalam beberapa kasus & layak dimiliki di sini sebagai catatan kemungkinan solusi yang tidak biasa.
James Billingham
1
Saya sangat suka menggunakan header HTTP untuk hal semacam ini (itu benar-benar tempatnya). Header Link standar mungkin sesuai dalam kasus ini (Github API menggunakan ini).
Mike Marcacci
11

Saya akan merekomendasikan menambahkan header untuk hal yang sama, seperti:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

Untuk detailnya, lihat:

https://github.com/adnan-kamili/rest-api-response-format

Untuk file swagger:

https://github.com/adnan-kamili/swagger-response-template

adnan kamili
sumber
7

Sejak "X -" - Awalan sudah tidak digunakan lagi. (lihat: https://tools.ietf.org/html/rfc6648 )

Kami menemukan "Accept-Ranges" sebagai taruhan terbaik untuk memetakan rentang pagination: https://tools.ietf.org/html/rfc7233#section-2.3 Karena "Unit Jangkauan" bisa berupa "byte" atau " token". Keduanya tidak mewakili tipe data khusus. (lihat: https://tools.ietf.org/html/rfc7233#section-4.2 ) Namun, dinyatakan bahwa

Penerapan HTTP / 1.1 MUNGKIN mengabaikan rentang yang ditentukan menggunakan unit lain.

Yang menunjukkan: menggunakan Unit Rentang kustom tidak bertentangan dengan protokol, tetapi MUNGKIN diabaikan.

Dengan cara ini, kita harus menyetel Rentang Terima ke "anggota" atau jenis unit jarak apa pun, yang kita harapkan. Dan sebagai tambahan, atur juga Content-Range ke range saat ini. (lihat: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )

Bagaimanapun, saya akan tetap berpegang pada rekomendasi RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) untuk mengirim 206 alih-alih 200:

Jika semua prasyarat benar, server mendukung
bidang header Range untuk sumber daya target, dan kisaran yang ditentukan
valid dan memuaskan (seperti yang didefinisikan di Bagian 2.1), server HARUS
mengirim respons 206 (Konten Sebagian) dengan muatan yang berisi satu
atau lebih representasi parsial yang sesuai dengan
rentang yang memuaskan yang diminta, sebagaimana didefinisikan dalam Bagian 4.

Jadi, sebagai hasilnya, kita akan memiliki field header HTTP berikut:

Untuk Konten Parsial:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

Untuk Konten lengkap:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20
Lepidopteron
sumber
3

Tampaknya paling mudah hanya menambahkan file

GET
/api/members/count

dan mengembalikan jumlah total anggota

willcodejavaforfood
sumber
11
Bukan ide yang bagus. Anda mewajibkan klien untuk membuat 2 permintaan untuk membangun pagination di halaman mereka. Permintaan pertama untuk mendapatkan daftar sumber daya dan kedua untuk menghitung total.
Jekis
Saya pikir ini adalah pendekatan yang baik ... Anda juga dapat mengembalikan daftar hasil saja sebagai json dan di sisi klien periksa ukuran koleksi jadi kasus seperti itu adalah contoh bodoh ... selain itu Anda dapat memiliki / api / members / count dan kemudian / api / members? offset = 10 & limit = 20
Michał Ziobro
1
Juga perlu diingat bahwa banyak jenis penomoran halaman tidak memerlukan hitungan (seperti gulir tak terbatas) - Mengapa menghitung ini saat klien mungkin tidak membutuhkannya
tofarr
2

Bagaimana dengan titik akhir baru> / api / members / count yang hanya memanggil Anggota.Count () dan mengembalikan hasilnya

Steve Woods
sumber
28
Memberikan hitungan titik akhir eksplisit membuatnya menjadi sumber daya beralamat yang berdiri sendiri. Ini akan berhasil, tetapi akan menimbulkan pertanyaan menarik bagi siapa pun yang baru mengenal API Anda - Apakah jumlah anggota koleksi merupakan sumber daya yang terpisah dari koleksi? Bisakah saya memperbaruinya dengan permintaan PUT? Apakah itu ada untuk koleksi kosong atau hanya jika ada item di dalamnya? Jika memberskoleksi dapat dibuat dengan permintaan POST ke /api, akan /api/members/countdibuat sebagai efek samping juga, atau apakah saya harus melakukan permintaan POST eksplisit untuk membuatnya sebelum memintanya? :-)
Franci Penov
2

Terkadang kerangka kerja (seperti $ resource / AngularJS) memerlukan sebuah array sebagai hasil kueri, dan Anda tidak dapat benar-benar memiliki respons seperti {count:10,items:[...]}dalam kasus ini saya menyimpan "count" di responseHeaders.

PS Sebenarnya Anda dapat melakukannya dengan $ resource / AngularJS, tetapi perlu beberapa penyesuaian.

Vahe Hovhannisyan
sumber
Apa sajakah perubahan itu? Mereka akan sangat membantu dalam pertanyaan seperti ini: stackoverflow.com/questions/19140017/…
JBCP
Angular tidak MEMERLUKAN array sebagai hasil kueri, Anda hanya perlu mengkonfigurasi sumber daya Anda dengan properti objek opsi:isArray: false|true
Rémi Becheras
0

Anda bisa mempertimbangkan countssebagai sumber daya. URL selanjutnya akan menjadi:

/api/counts/member
jujur
sumber
0

Diskusi menarik tentang Mendesain REST API untuk mengembalikan hitungan beberapa objek: https://groups.google.com/g/api-craft/c/qbI2QRrpFew/m/h30DYnrqEwAJ?pli=1

Sebagai konsumen API, saya berharap setiap nilai hitungan akan direpresentasikan baik sebagai sub-sumber daya ke sumber daya yang dapat dihitung (yaitu GET / tugas / hitungan untuk hitungan tugas), atau sebagai bidang dalam agregasi yang lebih besar dari metadata terkait dengan yang bersangkutan sumber daya (yaitu GET / tugas / metadata). Dengan membatasi titik akhir terkait di bawah sumber daya induk yang sama (yaitu / tugas), API menjadi intuitif, dan tujuan titik akhir dapat (biasanya) disimpulkan dari jalur dan metode HTTPnya.

Pikiran tambahan:

  1. Jika setiap hitungan individu hanya berguna dalam kombinasi dengan penghitungan lain (untuk dasbor statistik, misalnya), Anda mungkin dapat mengekspos satu titik akhir yang menggabungkan dan mengembalikan semua penghitungan sekaligus.
  2. Jika Anda memiliki titik akhir yang ada untuk mendaftar semua sumber daya (yaitu GET / tugas untuk mendaftar semua tugas), jumlah tersebut dapat dimasukkan dalam tanggapan sebagai metadata, baik sebagai header HTTP atau dalam isi tanggapan. Melakukan hal ini akan menimbulkan beban yang tidak perlu pada API, yang mungkin dapat diabaikan bergantung pada kasus penggunaan Anda.
Kiryl Plyashkevich
sumber
-1

Saat meminta data paginasi, Anda tahu (dengan nilai parameter ukuran halaman eksplisit atau nilai ukuran halaman default) ukuran halaman, jadi Anda tahu apakah Anda mendapatkan semua data sebagai tanggapan atau tidak. Ketika ada lebih sedikit data sebagai tanggapan daripada ukuran halaman, maka Anda mendapatkan seluruh data. Ketika sebuah halaman penuh dikembalikan, Anda harus meminta halaman lain lagi.

Saya lebih suka memiliki titik akhir terpisah untuk hitungan (atau titik akhir yang sama dengan parameter countOnly). Karena Anda dapat mempersiapkan pengguna akhir untuk proses yang memakan waktu / waktu dengan menampilkan progressbar yang dimulai dengan benar.

Jika Anda ingin mengembalikan datasize di setiap respons, harus ada pageSize, juga disebutkan offset. Sejujurnya, cara terbaik adalah dengan mengulangi filter permintaan juga. Tetapi tanggapannya menjadi sangat kompleks. Jadi, saya lebih suka titik akhir khusus untuk mengembalikan hitungan.

<data>
  <originalRequest>
    <filter/>
    <filter/>
  </originalReqeust>
  <totalRecordCount/>
  <pageSize/>
  <offset/>
  <list>
     <item/>
     <item/>
  </list>
</data>

Couleage saya, lebih suka parameter countOnly ke titik akhir yang ada. Jadi, ketika ditentukan, respons hanya berisi metadata.

titik akhir? filter = nilai

<data>
  <count/>
  <list>
    <item/>
    ...
  </list>
</data>

endpoint? filter = value & countOnly = true

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
Wooff
sumber