Bir nesnedeki toplam öğe sayısını döndürmek için en iyi RESTful yöntemi nedir?


139

Katıldığım büyük bir sosyal ağ sitesi için bir REST API hizmeti geliştiriyorum. Şimdiye kadar harika çalışıyor. Ben verebilir GET, POST, PUTve DELETEnesne URL istekleri ve benim verileri etkileyen. Ancak, bu veriler sayfalanır (bir seferde 30 sonuçla sınırlıdır).

Bununla birlikte, API’m aracılığıyla toplam üye sayısını almanın en iyi RESTful yolu ne olurdu?

Şu anda, aşağıdaki gibi bir URL yapısına istekte bulunuyorum:

  • / api / members - Üyelerin listesini döndürür (yukarıda belirtildiği gibi aynı anda 30 tane)
  • / api / members / 1 —Kullanılan istek yöntemine bağlı olarak tek bir üyeyi etkiler

Sorum şu: Uygulamamdaki toplam üye sayısını almak için benzer bir URL yapısını nasıl kullanırım? Açıkçası sadece idalanı talep etmek (Facebook'un Grafik API'sine benzer) ve sonuçların sayılması, yalnızca 30 sonuçtan oluşan bir dilim yalnızca döndürüleceği için etkisiz olacaktır.


Yanıtlar:


84

/ API / kullanıcılarına yanıt sayfalanmış ve yalnızca 30 kayıt döndürürken, yanıta toplam kayıt sayısını ve sayfa boyutu, sayfa numarası / ofset vb. Gibi diğer ilgili bilgileri de dahil etmenizi engelleyen hiçbir şey yoktur. .

StackOverflow API'si aynı tasarıma iyi bir örnektir. Kullanıcılar yönteminin belgeleri - https://api.stackexchange.com/docs/users


3
+1: Getirme limitleri uygulanacaksa kesinlikle yapılacak en RESTful şey.
Donal Fellows

2
@bzim Getirilecek bir sonraki sayfa olduğunu bilirsiniz çünkü rel = "next" ile bir bağlantı vardır.
Darrel Miller


1
@Darrel - evet, faydalı yükteki herhangi bir "sonraki" bayrakla yapılabilir. Yanıttaki koleksiyon öğelerinin toplam sayısına sahip olmanın kendi başına değerli olduğunu ve aynı şekilde bir "sonraki" bayrağı olarak çalıştığını hissediyorum.
Franci Penov

5
Öğe listesi olmayan bir nesneyi döndürmek bir REST API'sinin düzgün bir uygulaması değildir, ancak REST sonuçların kısmi listesini almak için herhangi bir yol sağlamaz. Buna saygı duymak için, toplam, sonraki sayfa jetonu ve önceki sayfa jetonu gibi diğer bilgileri iletmek için başlıkları kullanmamız gerektiğini düşünüyorum. Hiç denemedim ve diğer geliştiricilerin tavsiyelerine ihtiyacım var.
Loenix

74

Bu tür bağlamsal bilgiler için HTTP Üstbilgileri kullanmayı tercih ederim.

Toplam öğe sayısı için X-total-countüstbilgi kullanıyorum.
Sonraki, önceki sayfaya vb. Bağlantılar için http Linküstbilgisini kullanıyorum:
http://www.w3.org/wiki/LinkHeader

Github da aynı şekilde yapar: https://developer.github.com/v3/#pagination

Bence köprüleri desteklemeyen içeriği (ikili dosyalar, resimler) desteklemediğinizde de kullanılabileceğinden daha temiz.


5
RFC6648 , standart olmayan parametrelerin adlarını dize ile önekleme kuralını kaldırırX- .
JDawg

70

Son zamanlarda bu ve diğer REST sayfalama ile ilgili soruları araştırıyorum ve bulgularımın bazılarını buraya eklemenin yapıcı olduğunu düşündüm. Soruyu, sayfalar hakkındaki düşünceleri ve sayımları yakından ilişkili oldukları için içerecek şekilde biraz genişletiyorum.

Başlıkları

Disk belleği meta verileri yanıta yanıt başlıkları biçiminde eklenir. Bu yaklaşımın en büyük yararı, yanıt yükünün kendisinin sadece istediği gerçek veri olmasıdır. Çağrı bilgisiyle ilgilenmeyen müşteriler için yanıtın işlenmesini kolaylaştırmak.

Toplam sayı da dahil olmak üzere, disk belleği ile ilgili bilgileri döndürmek için vahşi ortamda kullanılan bir grup (standart ve özel) başlık vardır.

X Toplam-Count

X-Total-Count: 234

Bu, vahşi doğada bulduğum bazı API'larda kullanılıyor . Bu başlığa örneğin Loopback'e destek eklemek için NPM paketleri de vardır . Bazı makaleler bu başlığın da ayarlanmasını önermektedir.

Genellikle Linksayfalama için oldukça iyi bir çözüm olan başlık ile birlikte kullanılır , ancak toplam sayım bilgisinden yoksundur.

bağlantı

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

Ben genel bir konsensüs kullanmak olduğunu, bu konuda bir çok okumaktan, hissetmek Linkbaşlığını kullanan müşterilerine ait bağlantıları çağrı sağlamak için rel=next, rel=previousbu vb sorunu olan kaç tane toplam kayıtların bilgi yoksun olduğunu neden birçok API bunu X-Total-Countbaşlık ile birleştirir .

Alternatif olarak, bazı API'ler ve örneğin JsonApi standardı Linkbiçimi kullanır , ancak üstbilgi yerine bilgileri yanıt zarfına ekler. Bu, meta verilere erişimi basitleştirir (ve toplam sayım bilgisini eklemek için bir yer oluşturur), gerçek verilere erişimin karmaşıklığını arttırmak pahasına (bir zarf ekleyerek).

İçerik Menzilli

Content-Range: items 0-49/234

Aralık başlığı adlı bir blog makalesi tarafından tanıtılan , sizi seçin (sayfalandırma için)! . Yazar, kullanımı için güçlü bir durum Rangeve Content-Rangesayfalama için üstbilgileri yapar. Bu başlıklarda RFC'yi dikkatle okuduğumuzda , anlamlarını bayt aralıklarının ötesine genişletmenin aslında RFC tarafından beklendiğini ve açıkça izin verildiğini görüyoruz. Bunun yerine bağlam bağlamında kullanıldığında , Aralık başlığı bize hem belirli bir öğe aralığı istemek hem de yanıt öğelerinin toplam sonuç aralığının hangi aralıklarla ilgili olduğunu belirtmek için bir yol sunar. Bu başlık ayrıca toplam sayıyı göstermek için harika bir yol sunar. Ve çoğunlukla bire bir sayfalamayı eşleştiren gerçek bir standarttır. Ayrıca vahşi doğada kullanılır .itemsbytes

Zarf

En sevdiğimiz Soru-Cevap web sitemiz dahil birçok API, veriler hakkında meta bilgi eklemek için kullanılan verilerin etrafında bir zarf , bir sargı kullanır . Ayrıca, OData ve JsonApi standartları hem bir yanıt zarf kullanın.

Bunun (imho) en büyük dezavantajı, gerçek verilerin zarf içinde bir yerde bulunması gerektiğinden yanıt verilerinin işlenmesinin daha karmaşık hale gelmesidir. Ayrıca bu zarf için birçok farklı format vardır ve doğru olanı kullanmanız gerekir. OData ve JsonApi'nin yanıt zarflarının çılgınca farklı olduğunu ve yanıtta birden fazla noktada meta verilerde OData'nın karıştırıldığını söylüyor.

Ayrı uç nokta

Bunun diğer cevaplarda yeterince ele alındığını düşünüyorum. Bunu çok araştırmadım çünkü şu anda birden fazla uç noktaya sahip olduğunuz için bunun kafa karıştırıcı olduğu yorumlarına katılıyorum. Her son noktanın bir (kaynakları) kaynak (temsil) temsil etmesi en iyisi olduğunu düşünüyorum.

Diğer düşünceler

Yalnızca yanıtla ilgili disk belleği meta bilgilerini iletmek zorunda değiliz, aynı zamanda istemcinin belirli sayfaları / aralıkları istemesine de izin veririz. Tutarlı bir çözüm elde etmek için bu yönüne bakmak ilginçtir. Burada da üstbilgileri ( Rangeüstbilgi çok uygun görünüyor) veya sorgu parametreleri gibi diğer mekanizmaları kullanabiliriz. Bazı insanlar bazı kullanım durumlarında mantıklı olabilir ayrı kaynaklar (örn olarak sonuçların sayfalarını tedavi savunuyorlar /books/231/pages/52ben gibi sık kullanılan isteği parametrelerinden vahşi aralığını seçerek sona erdi. pagesize, page[size]Ve limitvb destekleyen ek olarak Rangebaşlık (ve istek parametresi olarak de).


Özellikle Rangebaşlıkla ilgileniyordum , ancak bytesaralık türü dışında bir şey kullanmanın geçerli olduğuna dair yeterli kanıt bulamadım .
VisioN

2
Ben en açık kanıt bulunabilir düşünüyorum RFC bölümüne 14.5 : acceptable-ranges = 1#range-unit | "none"Bu formülasyon açıkça dışındaki menzil birimleri için yer bırakır düşünüyorum bytes, Spec kendisi sadece tanımlayıp olsa bytes.
Stijn de Witt

24

Gerçek öğelere ihtiyacınız olmadığında alternatif

Franci Penov'un cevabı kesinlikle gitmenin en iyi yoludur, bu nedenle her zaman talep edilen varlıklarınızla ilgili tüm ek meta verilerle birlikte öğeleri iade edersiniz. Bu şekilde yapılmalı.

ancak bazen tüm verilerin döndürülmesi mantıklı değildir, çünkü onlara hiç ihtiyacınız olmayabilir. Belki de tek ihtiyacınız olan, istediğiniz kaynak hakkındaki meta verilerdir. Toplam sayım veya sayfa sayısı veya başka bir şey gibi. Bu durumda, URL sorgunuzun her zaman hizmetinize öğeleri iade etmemesini değil, yalnızca aşağıdaki gibi meta verileri bildirmesini sağlayabilirsiniz:

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

Veya benzeri...


10
Bu bilgileri başlıklara gömmek, yalnızca sayımı almak için bir HEAD isteği yapabilmeniz avantajına sahiptir.
felixfbecker

1
@felixfbecker tam olarak, tekerleği yeniden icat etti ve API'leri her türlü farklı mekanizma ile karıştırdığın için teşekkürler :)
EralpB

1
@EralpB Tekerleği yeniden icat ettiğiniz ve API'leri karıştırdığınız için teşekkürler !? HEAD HTTP'de belirtildi. metaonlyya includeitemsda değil.
felixfbecker

2
@felixfbecker sadece "tam" sizin için, geri kalanı OP içindir. Karışıklık için özür dilerim.
EralpB

REST, HTTP'den faydalanmak ve onu mümkün olduğu kadar kullanmak için kullanmakla ilgilidir. Bu durumda İçerik Aralığı (RFC7233) kullanılmalıdır. Vücuttaki çözümler iyi değildir, özellikle de HEAD ile çalışmaz. burada önerildiği gibi yeni başlıklar oluşturmak gereksiz ve yanlıştır.
Vance Shipley

23

HEAD isteğine yanıt olarak sayımı özel bir HTTP başlığı olarak döndürebilirsiniz. Bu şekilde, bir müşteri yalnızca sayımı istiyorsa, gerçek listeyi döndürmeniz gerekmez ve ek bir URL'ye gerek yoktur.

(Veya uç noktadan uç noktaya kadar kontrollü bir ortamdaysanız, COUNT gibi özel bir HTTP fiili kullanabilirsiniz.)


4
“Özel HTTP başlığı”? Bu biraz şaşırtıcı olma başlığı altında gelecektir, bu da bir RESTful API olması gerektiğini düşündüğümün aksine. Sonuçta, şaşırtıcı olmamalı.
Donal Fellows

21
@ Donal biliyorum. Ancak tüm iyi cevaplar zaten alınmıştı. :(
bzlm

1
Ben de biliyorum, ama bazen diğer insanların cevap vermesine izin vermelisin. Ya da katkınızı, diğerlerinden ziyade neden en iyi şekilde yapılması gerektiğinin ayrıntılı bir açıklaması gibi başka şekillerde daha iyi hale getirin.
Donal Fellows

4
Kontrollü bir ortamda, bu muhtemelen şaşırtıcı olabilir, çünkü muhtemelen dahili olarak ve geliştiricilerinizin API politikasına dayanır. Bunun bazı durumlarda iyi bir çözüm olduğunu ve burada olağandışı bir çözümün notu olarak görülmeye değer olduğunu söyleyebilirim.
James Billingham

1
Bu tür bir şey için HTTP üstbilgilerini kullanmayı çok seviyorum (gerçekten ait olduğu yer). Bu durumda standart Bağlantı başlığı uygun olabilir (Github API bunu kullanır).
Mike Marcacci


7

"X -" itibariyle - Önek kullanımdan kaldırıldı. (bkz: https://tools.ietf.org/html/rfc6648 )

"Kabul Et Aralıklarını", sayfa aralığını eşleştirmek için en iyi bahis olarak gördük: https://tools.ietf.org/html/rfc7233#section-2.3 "Aralık Birimleri", "bayt" veya " jeton". Her ikisi de özel bir veri türünü temsil etmez. (bkz: https://tools.ietf.org/html/rfc7233#section-4.2 ) Yine de,

HTTP / 1.1 uygulamaları, diğer birimler kullanılarak belirtilen aralıkları yoksayabilir.

Bu, özel Aralık Birimlerinin kullanılması protokole karşı değildir, ancak yok sayılabilir.

Bu şekilde, Kabul Aralıklarını "üyelere" veya birim türü ne olursa olsun, beklediğimiz gibi ayarlamamız gerekirdi. Ayrıca, İçerik Aralığı'nı geçerli aralığa ayarlayın. (bkz: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )

Her iki durumda da , 200 yerine 206 göndermek için RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) önerisine sadık kalacağım :

Tüm ön koşullar doğruysa, sunucu
hedef kaynak için Aralık başlığı alanını destekler ve belirtilen aralık (lar)
geçerli ve tatmin edilebilir (Bölüm 2.1'de tanımlandığı gibi), sunucu
206 (Kısmi İçerik) yanıtı göndermelidir Bölüm 4'te tanımlandığı gibi istenen
tatmin edici
aralıklara karşılık gelen bir veya daha fazla kısmi gösterim içeren bir yük ile .

Sonuç olarak, aşağıdaki HTTP başlık alanlarına sahip oluruz:

Kısmi İçerik için:

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

Tam İçerik için:

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

3

Sadece eklemek için en kolay görünüyor

GET
/api/members/count

ve toplam üye sayısını iade et


11
İyi bir fikir değil. Müşterileri, sayfalarında sayfalandırmayı oluşturmak için 2 istekte bulunma zorunluluğu vardır. Kaynakların listesini almak için ilk istek ve toplamı saymak için ikinci istek.
Jekis

Bence bu iyi bir yaklaşım ... ayrıca json olarak sonuçların listesini ve koleksiyonun istemci tarafında kontrol boyutunu döndürebilirsiniz, bu nedenle bu tür aptal bir örnektir ... ayrıca / api / members / count ve sonra / api olabilir / üye? offset = 10 & limit = 20
Michał Ziobro

1
Ayrıca, sayfalandırma türlerinin
çoğunun

2

Sadece Members.Count () öğesini çağıran ve sonucu döndüren yeni bir bitiş noktası> / api / members / count


27
Sayıya açık bir bitiş noktası vermek, bağımsız bir adreslenebilir kaynak olmasını sağlar. Çalışacak, ancak API'nizde yeni olan herkes için ilginç sorular ortaya çıkaracak - Koleksiyon üyelerinin sayısı koleksiyondan ayrı bir kaynak mı? Bir PUT isteğiyle güncelleyebilir miyim? Boş bir koleksiyon için mi yoksa sadece içinde öğeler varsa var mı? Eğer memberstoplama için bir POST isteği ile oluşturulabilir /api, olacak /api/members/counthem de bir yan etkisi olarak oluşturulan veya bunu istemeden önce onu oluşturmak için açık bir POST isteği yapmak zorunda mıyız edilecek? :-)
Franci Penov

2

Bazen çerçeveler ($ resource / AngularJS gibi) bir sorgu sonucu olarak bir dizi gerektirir ve gerçekten {count:10,items:[...]}bu durumda olduğu gibi bir yanıtınız olamaz bu durumda responseHeaders "count" depolamak.

PS Aslında bunu $ resource / AngularJS ile yapabilirsiniz, ancak bazı ince ayarlara ihtiyacı vardır.


Bu ince ayarlar neler? Bunun gibi sorularda yardımcı
olurlar

Açısal bir diziyi sorgu sonucu olarak GEREKTİRMEZ, sadece kaynağınızı seçenek nesnesi özelliği ile yapılandırmanız gerekir:isArray: false|true
Rémi Becheras

0

countsBir kaynak olarak düşünebilirsiniz . URL daha sonra şöyle olur:

/api/counts/member

-1

Sayfalandırılmış veri isterken, sayfa boyutunu (açık sayfa boyutu parametre değeri veya varsayılan sayfa boyutu değeri ile) bilirsiniz, böylece tüm verilerin yanıt verip vermediğini bilirsiniz. Yanıt olarak sayfa boyutundan daha az veri olduğunda, tüm verileri alırsınız. Tam sayfa döndürüldüğünde, tekrar başka bir sayfa istemeniz gerekir.

Ben saymak için ayrı bir uç nokta (veya parametre countOnly ile aynı uç noktası) tercih ederim. Çünkü uygun şekilde başlatılan ilerleme çubuğunu göstererek son kullanıcıyı uzun / zaman alan bir işleme hazırlayabilirsiniz.

Her yanıtta veri boyutu döndürmek istiyorsanız, pageSize, ofset mentionded de olmalıdır. Dürüst olmak gerekirse, en iyi yol da bir istek filtrelerini tekrarlamaktır. Ancak yanıt çok karmaşık bir hal aldı. Bu yüzden, sayıyı döndürmek için özel uç noktasını tercih ederim.

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

Benim couleage, mevcut bitiş noktasına bir countOnly parametresini tercih edin. Bu nedenle, yanıt belirtildiğinde yalnızca meta veriler içerir.

Son nokta? filtre = değer

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

Son nokta? filtre = değer ve countOnly doğru =

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.