Quelle est la meilleure méthode RESTful pour retourner le nombre total d'éléments dans un objet?
Je développe un service D'API REST pour un grand site de réseautage social dans lequel je suis impliqué. Jusqu'à présent, c'est génial de travailler. Je peux émettre GET
, POST
, PUT
et DELETE
demande d'objecter les URL et d'affecter mes données. Cependant, ces données sont paginées (limitées à 30 résultats à la fois).
Cependant, quel serait le meilleur moyen reposant d'obtenir le nombre total de membres, par exemple, via mon API?
Actuellement, j'envoie des requêtes à une structure D'URL comme suivant:
- / api / members - renvoie une liste de membres (30 à la fois comme mentionné ci-dessus)
- / api / members / 1 - affecte un seul membre, en fonction de la méthode de requête utilisée
Ma question Est la suivante: Comment puis-je utiliser une structure D'URL similaire pour obtenir le nombre total de membres dans mon application? Évidemment, Demander seulement le champ id
(similaire à L'API Graph de Facebook) et compter les résultats serait inefficace étant donné seulement une tranche de 30 les résultats ne seraient retournés.
10 réponses
Alors que la réponse à / API / users est paginée et ne renvoie que 30 enregistrements, rien ne vous empêche d'inclure dans la réponse le nombre total d'enregistrements et d'autres informations pertinentes, comme la taille de la page, le numéro de page/décalage, etc.
L'API StackOverflow est un bon exemple de cette même conception. Voici la documentation pour la méthode Users - https://api.stackexchange.com/docs/users
Je préfère utiliser des en-têtes HTTP pour ce genre d'informations contextuelles.
Pour le nombre total d'éléments, j'utilise l'en-tête X-total-count
.
Pour les liens vers la page suivante, précédente, etc. J'utilise l'en-tête http Link
:
http://www.w3.org/wiki/LinkHeader
Github le fait de la même manière: https://developer.github.com/v3/#pagination
À mon avis, c'est plus propre car il peut être utilisé aussi lorsque vous renvoyez du contenu qui ne supporte pas les hyperliens (c'est-à-dire les binaires, les images).
j'ai fait des recherches approfondies sur ce sujet et d'autres questions liées à la pagination de repos dernièrement et j'ai pensé qu'il était constructif d'ajouter certaines de mes conclusions ici. J'élargit un peu la question pour inclure des réflexions sur la pagination ainsi que le nombre car ils sont intimement liés.
En-têtes
Les métadonnées de pagination sont incluses dans la réponse sous la forme d'en-têtes de réponse. Le grand avantage de cette approche est que la charge utile de réponse elle-même est juste la réelle le demandeur de données demandait. Faciliter le traitement de la réponse pour les clients qui ne sont pas intéressés par les informations de pagination.
Il y a un tas d'en-têtes (standard et personnalisés) utilisés dans la nature pour renvoyer des informations relatives à la pagination, y compris le nombre total.
X-Total-Comte
X-Total-Count: 234
Ceci est utilisé dans certains Api, j'ai trouvé dans la nature. Il existe également des paquets npm pour ajouter le support de cet en-tête à, par exemple, Loopback. Quelque Les articles recommandent également de définir cet en-tête.
Il est souvent utilisé en combinaison avec l'en-tête Link
, qui est une très bonne solution pour la pagination, mais manque les informations de comptage total.
Lien
Link: </TheBook/chapter2>;
rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
</TheBook/chapter4>;
rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel
Je pense, en lisant beaucoup sur ce sujet, que le consensus général est d'utiliser le Link
en-tête pour fournir des liens de pagination aux clients utilisant rel=next
, rel=previous
etc. Le problème avec ceci est qu'il manque l'information de combien total il y a des enregistrements, c'est pourquoi de nombreuses API combinent cela avec l'en-tête X-Total-Count
.
Alternativement, certaines API et par exemple la norme jsonapi, utilisent le format Link
, mais ajoutent les informations dans une enveloppe de réponse au lieu d'un en-tête. Cela simplifie l'accès aux métadonnées (et crée un endroit pour ajouter les informations de comptage total) au détriment de la complexité croissante de l'accès aux données réelles elles-mêmes (en ajoutant un enveloppe).
Contenu-Plage
Content-Range: items 0-49/234
Promu par un article de blog nommé Range header, je vous choisis (pour la pagination)!. L'auteur plaide fermement pour l'utilisation des en-têtes Range
et Content-Range
pour la pagination. Lorsque nous lisons attentivement Le RFC sur ces en-têtes, nous constatons que l'extension de leur signification au-delà des plages d'octets était en fait anticipée par la RFC et est explicitement autorisée. Lorsqu'il est utilisé dans le contexte de items
au lieu de bytes
, La Plage header nous donne en fait un moyen de demander à la fois une certaine plage d'éléments et d'indiquer à quelle plage du résultat total les éléments de réponse se rapportent. Cet en-tête donne également un excellent moyen de montrer le nombre total. Et c'est une véritable norme qui mappe principalement un à un à la pagination. Il est également utilisé dans la nature .
Enveloppe
De nombreuses API, y compris celle de notre site Web Q&A préféré utilisent une enveloppe , un wrapper autour des données utilisées pour ajouter des méta informations sur les données. En outre, les normesOData etjsonapi utilisent toutes deux une enveloppe de réponse.
Le gros inconvénient de cela (à mon humble avis) est que le traitement des données de réponse devient plus complexe car les données réelles doivent être trouvées quelque part dans l'enveloppe. Il existe également de nombreux formats différents pour cette enveloppe et vous devez utiliser le bon. Il est révélateur que les enveloppes de réponse D'OData et de JsonApi sont très différentes, avec OData mélangeant les métadonnées à plusieurs points dans la réponse.
Point de terminaison séparé
Je pense que cela a été suffisamment couvert dans les autres réponses. Je n'ai pas beaucoup étudié parce que je suis d'accord avec les commentaires que cela est déroutant car vous avez maintenant plusieurs types de points de terminaison. Je pense que c'est plus agréable si chaque point de terminaison représente une (collection de) ressource(s).
Autres pensées
Nous ne devons pas seulement communiquer les méta-informations de pagination liées à la réponse, mais aussi permettre client pour demander des pages/plages spécifiques. Il est intéressant de regarder aussi cet aspect pour aboutir à une solution cohérente. Ici aussi, nous pouvons utiliser des en-têtes (l'en-tête Range
semble très approprié), ou d'autres mécanismes tels que les paramètres de requête. Certaines personnes préconisent de traiter les pages de résultats comme des ressources séparées, ce qui peut avoir du sens dans certains cas d'utilisation (par exemple /books/231/pages/52
. J'ai fini par sélectionner une gamme sauvage de paramètres de requête fréquemment utilisés tels que pagesize
, page[size]
et limit
etc en plus de soutenir l'en-tête Range
(et en tant que paramètre de requête).
Vous pouvez renvoyer le compte en tant qu'en-tête HTTP personnalisé en réponse à une requête HEAD. De cette façon, si un client veut seulement le nombre, vous n'avez pas besoin de retourner la liste réelle, et il n'y a pas besoin d'une URL supplémentaire.
(ou, si vous êtes dans un environnement contrôlé d'un point de terminaison à l'autre, vous pouvez utiliser un verbe HTTP personnalisé tel que COUNT.)
Alternative lorsque vous n'avez pas besoin d'éléments réels
La réponse de Franci Penov est certainement la meilleure façon d'aller de sorte que vous renvoyez toujours des éléments avec toutes les métadonnées supplémentaires sur vos entités demandées. C'est la façon dont il devrait être fait.
Mais parfois renvoyer toutes les données n'a pas de sens, car vous n'en avez peut-être pas besoin du tout. Peut-être que tout ce dont vous avez besoin est que les métadonnées sur votre ressource demandée. Comme le nombre total ou le nombre de pages ou autre chose. Dans de tels cas vous pouvez toujours demander à votre service de ne pas renvoyer d'éléments mais simplement des métadonnées comme:
/api/members?metaonly=true
/api/members?includeitems=0
Ou quelque chose de similaire...
Je recommande d'ajouter des en-têtes pour le même, comme:
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"
}
]
Pour plus de détails, voir:
Https://github.com/adnan-kamili/rest-api-response-format
Pour le fichier swagger:
Qu'en est-il d'un nouveau point final > / api / members / count qui appelle simplement les membres.Count() et renvoie le résultat
Semble plus facile d'ajouter un
GET
/api/members/count
Et renvoie le nombre total de membres
Parfois, les frameworks (comme $resource/AngularJS) nécessitent un tableau comme résultat de requête, et vous ne pouvez pas vraiment avoir une réponse comme {count:10,items:[...]}
dans ce cas, je stocke "count" dans responseHeaders.
P. S. en fait, vous pouvez le faire avec $resource / AngularJS, mais il a besoin de quelques réglages.
Lorsque vous demandez des données paginées, vous connaissez (par valeur de paramètre de taille de page explicite ou valeur de taille de page par défaut) la taille de la page, donc vous savez si vous avez toutes les données en réponse ou non. Quand il y a moins de données en réponse qu'une taille de page, alors vous avez des données entières. Quand une page complète est retournée, vous devez demander à nouveau une autre page.
Je préfère avoir un point de terminaison séparé pour count (ou le même point de terminaison avec le paramètre countOnly). Parce que vous pouvez préparer l'utilisateur final pour un processus long/long en montrant la barre de progression correctement initiée.
Si vous voulez renvoyer datasize dans chaque réponse, il devrait y avoir pageSize, offset mentionné aussi. Pour être honnête, le meilleur moyen est de répéter un filtre de demande aussi. Mais la réponse est devenue très complexe. Donc, je préfère le point de terminaison dédié à return count.
<data>
<originalRequest>
<filter/>
<filter/>
</originalReqeust>
<totalRecordCount/>
<pageSize/>
<offset/>
<list>
<item/>
<item/>
</list>
</data>
Couleage de la mine, préférez un paramètre countOnly au point de terminaison existant. Ainsi, lorsqu'elle est spécifiée, la réponse contient des métadonnées seulement.
Point final?filtre=valeur
<data>
<count/>
<list>
<item/>
...
</list>
</data>
Point final?filtre = valeur & countOnly=true
<data>
<count/>
<!-- empty list -->
<list/>
</data>