Estoy desarrollando un servicio API REST para un gran sitio web de redes sociales en el que estoy involucrado. Hasta ahora, funciona muy bien. Puedo emitir GET
, POST
, PUT
y DELETE
peticiones a las URL de objetos y afectar mis datos. Sin embargo, esta información está paginada (limitada a 30 resultados a la vez).
Sin embargo, ¿cuál sería la mejor manera RESTful de obtener el número total de miembros, digamos, a través de mi API?
Actualmente, publico solicitudes a una estructura de URL como la siguiente:
- / api / members: devuelve una lista de miembros (30 a la vez como se mencionó anteriormente)
- / api / members / 1: afecta a un solo miembro, según el método de solicitud utilizado
Mi pregunta es: ¿cómo utilizaría una estructura de URL similar para obtener el número total de miembros en mi aplicación? Obviamente, solicitar solo el id
campo (similar a Graph API de Facebook) y contar los resultados sería ineficaz dado que solo se devolverían una porción de 30 resultados.
fuente
Respuestas:
Si bien la respuesta a / API / usuarios está paginada y devuelve solo 30 registros, no hay nada que le impida incluir en la respuesta también el número total de registros y otra información relevante, como el tamaño de la página, el número de página / desplazamiento, etc. .
La API StackOverflow es un buen ejemplo de ese mismo diseño. Aquí está la documentación para el método de los usuarios: https://api.stackexchange.com/docs/users
fuente
Prefiero usar encabezados HTTP para este tipo de información contextual.
Para el número total de elementos, uso el
X-total-count
encabezado.Para enlaces a la página siguiente, anterior, etc. Uso el
Link
encabezado http :http://www.w3.org/wiki/LinkHeader
Github lo hace de la misma manera: https://developer.github.com/v3/#pagination
En mi opinión, es más limpio, ya que se puede usar también cuando devuelve contenido que no admite hipervínculos (es decir, binarios, imágenes).
fuente
X-
.Últimamente he estado haciendo una investigación exhaustiva sobre esta y otras preguntas relacionadas con la paginación REST y pensé que era constructivo agregar algunos de mis hallazgos aquí. Estoy ampliando la pregunta un poco para incluir pensamientos sobre paginación, así como el recuento, ya que están íntimamente relacionados.
Encabezados
Los metadatos de paginación se incluyen en la respuesta en forma de encabezados de respuesta. El gran beneficio de este enfoque es que la carga útil de respuesta en sí misma es solo el solicitante de datos real que estaba solicitando. Facilitar el procesamiento de la respuesta para los clientes que no están interesados en la información de paginación.
Hay un montón de encabezados (estándar y personalizados) utilizados en la naturaleza para devolver información relacionada con la paginación, incluido el recuento total.
X-Total-Count
Esto se usa en algunas API que encontré en la naturaleza. También hay paquetes NPM para agregar soporte para este encabezado, por ejemplo, para Loopback. Algunos artículos recomiendan configurar este encabezado también.
A menudo se usa en combinación con el
Link
encabezado, que es una solución bastante buena para la paginación, pero carece de la información de recuento total.Enlace
Siento, al leer mucho sobre este tema, que el consenso general es usar el
Link
encabezado para proporcionar enlaces de paginación a los clientes que usanrel=next
,rel=previous
etc. El problema con esto es que carece de la información de cuántos registros totales hay, que es por qué muchas API combinan esto con elX-Total-Count
encabezado.Alternativamente, algunas API y, por ejemplo, el estándar JsonApi , usan el
Link
formato, pero agregan la información en un sobre de respuesta en lugar de un encabezado. Esto simplifica el acceso a los metadatos (y crea un lugar para agregar la información del recuento total) a expensas de aumentar la complejidad del acceso a los datos reales (agregando un sobre).Rango de contenido
Promocionado por un artículo de blog llamado encabezado Range, ¡te elijo (para paginación)! . El autor presenta un argumento sólido para usar los encabezados
Range
yContent-Range
para la paginación. Cuando leemos cuidadosamente el RFC en estos encabezados, encontramos que extender el significado más allá de los rangos de bytes fue realmente anticipado por el RFC y está explícitamente permitido. Cuando se usa en el contexto de, enitems
lugar debytes
, el encabezado Rango nos da una forma de solicitar un cierto rango de elementos e indicar a qué rango del resultado total se refieren los elementos de respuesta. Este encabezado también ofrece una excelente manera de mostrar el recuento total. Y es un verdadero estándar que se asigna principalmente uno a uno a la paginación. También se usa en la naturaleza .Sobre
Muchas API, incluida la de nuestro sitio web favorito de preguntas y respuestas, usan un sobre , una envoltura alrededor de los datos que se utiliza para agregar metainformación sobre los datos. Además, OData estándares y JsonApi usan un sobre de respuesta.
La gran desventaja de esto (en mi humilde opinión) es que el procesamiento de los datos de respuesta se vuelve más complejo ya que los datos reales deben encontrarse en algún lugar del sobre. También hay muchos formatos diferentes para ese sobre y debe usar el correcto. Es revelador que los sobres de respuesta de OData y JsonApi son muy diferentes, con OData mezclando metadatos en múltiples puntos de la respuesta.
Punto final separado
Creo que esto se ha cubierto lo suficiente en las otras respuestas. No investigé tanto porque estoy de acuerdo con los comentarios de que esto es confuso ya que ahora tiene múltiples tipos de puntos finales. Creo que es mejor si cada punto final representa una (colección de) recurso (s).
Pensamientos adicionales
No solo tenemos que comunicar la metainformación de paginación relacionada con la respuesta, sino que también le permitimos al cliente solicitar páginas / rangos específicos. Es interesante observar también este aspecto para terminar con una solución coherente. Aquí también podemos usar encabezados (el
Range
encabezado parece muy adecuado) u otros mecanismos como los parámetros de consulta. Algunas personas abogan por el tratamiento de las páginas de resultados como recursos separados, que pueden tener sentido en algunos casos de uso (por ejemplo/books/231/pages/52
. Terminé la selección de una gama salvaje de parámetros de la petición uso frecuente, tales comopagesize
,page[size]
ylimit
etc, además de apoyar laRange
cabecera (y como parámetro de la petición también).fuente
Range
encabezado, sin embargo, no pude encontrar suficiente evidencia de que usar algo aparte debytes
un tipo de rango sea válido.acceptable-ranges = 1#range-unit | "none"
creo que esta formulación deja explícitamente espacio para otras unidades de rango quebytes
, aunque la especificación en sí misma solo definebytes
.Alternativa cuando no necesita elementos reales
La respuesta de Franci Penov es sin duda la mejor manera de hacerlo, por lo que siempre devuelve elementos junto con todos los metadatos adicionales sobre las entidades que se solicitan. Así es como debe hacerse.
pero a veces devolver todos los datos no tiene sentido, porque es posible que no los necesite en absoluto. Quizás todo lo que necesita son esos metadatos sobre su recurso solicitado. Como recuento total o número de páginas o algo más. En tal caso, siempre puede hacer que la consulta de URL le indique a su servicio que no devuelva elementos, sino solo metadatos como:
o algo similar...
fuente
metaonly
oincludeitems
no lo es.Puede devolver el recuento como un encabezado HTTP personalizado en respuesta a una solicitud HEAD. De esta manera, si un cliente solo quiere el recuento, no necesita devolver la lista real, y no hay necesidad de una URL adicional.
(O, si se encuentra en un entorno controlado de punto final a punto final, puede usar un verbo HTTP personalizado como COUNT).
fuente
Recomendaría agregar encabezados para el mismo, como:
Para más detalles, consulte:
https://github.com/adnan-kamili/rest-api-response-format
Para archivo swagger:
https://github.com/adnan-kamili/swagger-response-template
fuente
A partir de "X -" - El prefijo quedó en desuso. (ver: https://tools.ietf.org/html/rfc6648 )
Encontramos que "Accept-Ranges" es la mejor apuesta para mapear el rango de paginación: https://tools.ietf.org/html/rfc7233#section-2.3 Como las "Unidades de rango" pueden ser "bytes" o " simbólico". Ambos no representan un tipo de datos personalizado. (ver: https://tools.ietf.org/html/rfc7233#section-4.2 ) Aún así, se afirma que
Lo que indica: el uso de unidades de rango personalizadas no está en contra del protocolo, pero PUEDE ser ignorado.
De esta manera, tendríamos que establecer los rangos de aceptación en "miembros" o cualquier tipo de unidad a distancia, es de esperar. Y además, también establezca Content-Range en el rango actual. (ver: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )
De cualquier manera, me apegaría a la recomendación de RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) para enviar un 206 en lugar de 200:
Entonces, como resultado, tendríamos los siguientes campos de encabezado HTTP:
Para contenido parcial:
Para contenido completo:
fuente
Parece más fácil simplemente agregar un
y devolver el recuento total de miembros
fuente
¿Qué pasa con un nuevo punto final> / api / members / count que simplemente llama a Members.Count () y devuelve el resultado
fuente
members
colección se puede crear mediante una solicitud POST a/api
,/api/members/count
se creará también como un efecto secundario, o tengo que hacer una solicitud POST explícita para crearla antes de solicitarla? :-)A veces, los marcos (como $ resource / AngularJS) requieren una matriz como resultado de la consulta, y realmente no puede tener una respuesta como
{count:10,items:[...]}
en este caso almaceno "count" en responseHeaders.PD En realidad, puedes hacer eso con $ resource / AngularJS, pero necesita algunos ajustes.
fuente
isArray: false|true
Podrías considerarlo
counts
como un recurso. La URL sería entonces:fuente
Al solicitar datos paginados, usted sabe (por valor de parámetro de tamaño de página explícito o valor de tamaño de página predeterminado) el tamaño de página, por lo que sabe si recibió todos los datos en respuesta o no. Cuando hay menos datos en respuesta que el tamaño de una página, entonces obtienes datos completos. Cuando se devuelve una página completa, debe volver a solicitar otra página.
Prefiero tener un punto final separado para el recuento (o el mismo punto final con el parámetro countOnly). Porque podría preparar al usuario final para un proceso que requiera mucho tiempo o tiempo mostrando una barra de progreso iniciada correctamente.
Si desea devolver el tamaño de datos en cada respuesta, también debe haber pageSize, offset mencionado. Para ser sincero, la mejor manera es repetir una solicitud de filtros también. Pero la respuesta se volvió muy compleja. Por lo tanto, prefiero un punto final dedicado para devolver el recuento.
Couleage of mine, prefiere un parámetro countOnly al punto final existente. Entonces, cuando se especifica, la respuesta contiene solo metadatos.
punto final? filtro = valor
punto final? filter = value & countOnly = true
fuente