Paginación en una aplicación web REST

329

Esta es una reformulación más genérica de esta pregunta (con la eliminación de las partes específicas de Rails)

No estoy seguro de cómo implementar la paginación en un recurso en una aplicación web RESTful. Suponiendo que tengo un recurso llamadoproducts , ¿cuál de los siguientes crees que es el mejor enfoque y por qué?

1. Usando solo cadenas de consulta

p.ej. http://application/products?page=2&sort_by=date&sort_how=asc
El problema aquí es que no puedo usar el almacenamiento en caché de la página completa y también la URL no es muy limpia y fácil de recordar.

2. Uso de páginas como recursos y cadenas de consulta para ordenar

p.ej. http://application/products/page/2?sort_by=date&sort_how=asc
En este caso, el problema que se ve es que http://application/products/pages/1no es un recurso único, ya que el uso sort_by=pricepuede producir un resultado totalmente diferente y todavía no puedo usar el almacenamiento en caché de la página.

3. Uso de páginas como recursos y un segmento de URL para ordenar

p.ej. http://application/products/by-date/page/2
Personalmente, no veo ningún problema en usar este método, pero alguien me advirtió que no es una buena manera de hacerlo (no dio una razón, así que si sabes por qué no se recomienda, hágamelo saber)

Cualquier sugerencia, opinión, crítica es más que bienvenida. Gracias.

y yo
fuente
34
Esta es una gran pregunta.
Iain Holder
77
Pregunta adicional: ¿cómo suelen especificar las personas los tamaños de página?
Heiko Rupp
No se olvide de los parámetros de Matrix w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Respuestas:

66

Creo que el problema con la versión 3 es más un problema de "punto de vista": ¿ve la página como el recurso o los productos en la página?

Si ve la página como el recurso, es una solución perfecta, ya que la consulta de la página 2 siempre dará como resultado la página 2.

Pero si ve los productos en la página como el recurso, tiene el problema de que los productos en la página 2 pueden cambiar (productos antiguos eliminados, o lo que sea), en este caso, el URI no siempre devuelve los mismos recursos.

Por ejemplo, un cliente almacena un enlace a la página X de la lista de productos, la próxima vez que se abra el enlace el producto en cuestión ya no estará en la página X.

Fionn
fuente
66
Bueno, pero si eliminas algo, no debería haber otra cosa en el mismo URI. Si elimina todos los productos de la página X, la página X aún puede ser válida, pero ahora contiene los productos de la página X + 1. Por lo tanto, el URI de la página X se ha convertido en el URI de la página X + 1 si lo ve en la "vista de recursos del producto ".
Fionn
1
> Si ve la página como el recurso, es una solución perfecta, ya que la consulta de la página 2 siempre dará lugar a la página 2. ¿Tiene sentido? La misma URL (cualquier URL que mencione la página 2) siempre generará la página 2, sin importar cuál sea su recurso.
temoto
2
Ver la página como recurso probablemente debería introducir POST / foo / page para crear una nueva página, ¿verdad?
temoto
18
Su respuesta va suavemente a "la solución correcta es 1", pero no lo dice.
temoto
2
En mi opinión, la página es un concepto flotante y no está relacionado con el dominio subyacente. Y, por lo tanto, no debe considerarse como un recurso. Me refiero a flotar en el sentido de que es fluido, que el concepto de página cambia con el contexto; un usuario de su API puede ser una aplicación móvil, que puede consumir solo 2 productos por página, mientras que el otro es una aplicación de máquina que puede consumir toda la maldita lista. En resumen, la página es una "representación" de la entidad de dominio subyacente (producto) y no debe incluirse como parte de la URL; solo como un parámetro de consulta.
Kingz
106

Estoy de acuerdo con Fionn, pero iré un paso más allá y diré que la página no es un recurso, es una propiedad de la solicitud. Eso me hace elegir la opción 1 cadena de consulta solamente. Simplemente se siente bien. Realmente me gusta cómo la API de Twitter está estructurada de manera tranquila. No demasiado simple, no demasiado complicado, bien documentado. Para bien o para mal, es mi diseño de "ir a" cuando estoy cerca de hacer algo de una manera u otra.

slf
fuente
28
+1: las cadenas de consulta no son identificadores de recursos de primera clase; solo aclaran para ordenar y agrupar el recurso.
S.Lott
1
@ S.Lott La solicitud es el recurso. Lo que llama "recursos de primera clase" se definen como valores en Fielding en la sección 5.2.1.1 de su disertación . Además, en la misma sección, Fielding ofrece la última revisión de un archivo de código fuente como ejemplo de un recurso. ¿Cómo puede ser un recurso pero los últimos 10 productos pueden ser "propiedades de la solicitud en el recurso de productos"? Entiendo que su punto de vista es más práctico, pero creo que es menos RESTANTE.
edsioufi
Tenga en cuenta que mi comentario no significa que no estoy de acuerdo con la elección de usar cadenas de consulta sobre las URL: ambas son soluciones viables siempre que la API esté impulsada por hipermedia, como @RichApodaca ha mencionado en su respuesta. Solo estoy señalando que la página debe considerarse como un recurso desde el punto de vista REST.
edsioufi
37

HTTP tiene un gran encabezado Range que también es adecuado para la paginación. Puedes enviar

Range: pages=1

tener solo la primera página. Eso puede obligarlo a repensar lo que es una página. Tal vez el cliente quiere una gama diferente de artículos. El encabezado de rango también funciona para declarar un pedido:

Range: products-by-date=2009_03_27-

para obtener todos los productos más nuevos que esa fecha o

Range: products-by-date=0-2009_11_30

para obtener todos los productos anteriores a esa fecha. Probablemente '0' no sea la mejor solución, pero RFC parece querer algo para el inicio del rango. Puede haber implementados analizadores HTTP que no analizarían unidades = -range_end.

Si los encabezados no son una opción (aceptable), creo que la primera solución (todo en una cadena de consulta) es una forma de manejar las páginas. Pero, por favor, normalice las cadenas de consulta (ordena (pares = clave) pares en orden alfabético). Esto resuelve el problema de diferenciación "? A = 1 & b = x" y "? B = x & a = 1".

temoto
fuente
34
Los encabezados pueden verse bien a primera vista, pero no permiten compartir la página (por ejemplo, copiando la URL). Entonces, para la solicitud de ajax, podrían ser una buena solución (ya que las páginas modificadas por ajax no se pueden compartir en su estado actual de todos modos), pero no las usaría para la paginación regular.
Markus
3
Y el encabezado Range es solo para rangos de bytes. Consulte [la especificación de encabezados HTTP] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), sección 14.35.
Chris Westin
16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 utiliza unidades de rango en los campos de encabezado Range (sección 14.35) y Content-Range (sección 14.16). range-unit = bytes-unit | other-range-unit Quizás te refieras a The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.Eso no es lo mismo que tu declaración.
temoto
1
@ Markus No puedo imaginar el caso de uso cuando compartes recursos de API de descanso :)
JakubKnejzlik
@JakubKnejzlik Compartir no es un problema, pero el uso de encabezados HTTP para la paginación evita el uso de enlaces HATEOAS para la paginación.
xarx
25

La opción 1 parece la mejor, en la medida en que su aplicación ve la paginación como una técnica para producir una vista diferente del mismo recurso.

Dicho esto, el esquema de URL es relativamente insignificante. Si está diseñando su aplicación para que funcione con hipertexto funcione con (como todas las aplicaciones REST deben ser por definición), entonces su cliente no construirá ningún URI por sí solo. En cambio, su aplicación proporcionará los enlaces al cliente y el cliente los seguirá.

Un tipo de enlace que su cliente puede proporcionar es un enlace de paginación.

El efecto secundario agradable de todo esto es que, incluso si cambia de opinión sobre la estructura de URI de paginación e implementa algo totalmente diferente la próxima semana, sus clientes pueden continuar trabajando sin ninguna modificación.

Rich Apodaca
fuente
3
Bonito recordatorio sobre el uso de hipermedia como enlaces en los servicios web REST.
Paul D. Eden
11

Siempre he usado el estilo de la opción 1. El almacenamiento en caché no ha sido una preocupación ya que los datos cambian con frecuencia de todos modos en mi caso. Si permite que el tamaño de la página sea configurable, nuevamente los datos no se pueden almacenar en caché.

No encuentro la url difícil de recordar o impura. Para mí, este es un buen uso de los parámetros de consulta. El recurso es claramente una lista de productos y los parámetros de consulta solo indican cómo desea que se muestre la lista, ordenada y qué página.

John Snyders
fuente
1
+1 Creo que tienes razón e iré con los parámetros de consulta (opción 1)
andi
"No encuentro la URL difícil de recordar". Esta observación es inútil en las aplicaciones REST, ya que normalmente deberían tener un solo marcador ... Si un usuario (o una aplicación cliente) intenta "recordar" la URL, esta es una buena señal de que la API no es tranquila.
edsioufi
8

Es extraño que nadie haya señalado que la Opción 3 tiene parámetros en un orden específico. http // application / products / Date / Descending / Name / Ascending / page / 2 y http // application / products / Name / Ascending / Date / Descending / page / 2

apuntan al mismo recurso, pero tienen URL completamente diferentes.

Para mí, la opción 1 parece la más aceptable, ya que separa claramente "Lo que quiero" y "Cómo quiero" (incluso tiene un signo de interrogación entre ellos, jajaja). El almacenamiento en caché de página completa se puede implementar utilizando una URL completa (todas las opciones sufrirán el mismo problema de todos modos).

Con el enfoque de Parámetros en URL, el único beneficio es una URL limpia. Aunque tiene que encontrar alguna forma de codificar parámetros y decodificarlos sin pérdidas. Por supuesto, puedes usar URLencode / decode, pero volverá a poner las URL feas de nuevo :)

TEHEK
fuente
1
Esos son dos ordenamientos diferentes. El primero ordena por fecha descendente, y solo rompe lazos por nombre ascendente; el segundo se ordena por nombre ascendente, y solo rompe los lazos por fecha descendente.
Imran Rashid
De hecho, las dos URL de ejemplo que se dan aquí no solo son diferentes por escrito, sino también por significado. Dado que denota una ruta, no se garantiza que encuentre lo mismo al girar a la izquierda primero y luego a la derecha o viceversa. Dicho esto, los parámetros de clasificación como partes de la ruta URL tienen ventajas formales sobre los parámetros URL que deberían ser intercambiables conmutativamente sin cambiar el significado general, pero de hecho sufren codificaciones de trampas como se dice aquí.
Christian Gosch
7

Prefiero usar los parámetros de consulta offset y limit.

offset : para el índice del artículo en la colección.

límite : para el recuento de artículos.

El cliente simplemente puede seguir actualizando el desplazamiento de la siguiente manera

offset = offset + limit

para la página siguiente

La ruta se considera el identificador del recurso. Y una página no es un recurso sino un subconjunto de la colección de recursos. Dado que la paginación es generalmente una solicitud GET, los parámetros de consulta son más adecuados para la paginación que para los encabezados.

Referencia: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page

Clasificador
fuente
5

Buscando las mejores prácticas me encontré con este sitio:

http://www.restapitutorial.com

En la página de recursos hay un enlace para descargar un archivo .pdf que contiene las mejores prácticas REST completas sugeridas por el autor. En el que, entre otras cosas, hay una sección sobre paginación.

El autor sugiere agregar soporte tanto para usar un encabezado Range como para usar parámetros de cadena de consulta.

Solicitud

Ejemplo de encabezado HTTP:

Range: items=0-24

Ejemplo de parámetros de cadena de consulta:

GET http://api.example.com/resources?offset=0&limit=25

Donde offset es el número de artículo inicial y el límite es el número máximo de artículos a devolver.

Respuesta

La respuesta debe incluir un encabezado de rango de contenido que indique cuántos elementos se devuelven y cuántos elementos totales aún no se han recuperado

Ejemplos de encabezado HTTP:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

En el .pdf hay algunas otras sugerencias para casos más específicos.

Mario Arturo
fuente
4

Actualmente estoy usando un esquema similar a este en mis aplicaciones ASP.NET MVC:

p.ej http://application/products/by-date/page/2

específicamente es: http://application/products/Date/Ascending/3

Sin embargo, no estoy realmente contento con incluir información de paginación y clasificación en la ruta de esta manera.

La lista de artículos (productos en este caso) es mutable. es decir, la próxima vez que alguien regrese a una url que incluye parámetros de paginación y clasificación, los resultados que obtienen pueden haber cambiado. Por lo tanto http://application/products/Date/Ascending/3, se pierde la idea de una URL única que apunta a un conjunto de productos definido e inmutable.

Steve Willcock
fuente
1
El primer problema, con la clasificación en varias columnas, se aplica a los 3 métodos en mi opinión. Por lo tanto, no es realmente un pro / con para ninguno de ellos. Con respecto al segundo problema: ¿no puede pasar eso a ningún recurso? Un producto, por ejemplo, también se puede editar / eliminar.
andi
Creo que ordenar en varias columnas es realmente una 'estafa' para los 3 métodos, ya que la url se vuelve más grande y más inmanejable, por lo tanto, una de las razones por las que estoy considerando pasar a los parámetros de clasificación / página basados ​​en formularios. Para el segundo problema, creo que hay una diferencia conceptual fundamental entre un identificador persistente único, como una identificación de producto, que una lista transitoria de productos. Para los productos eliminados, un mensaje, por ejemplo, "Ese producto no existe en el sistema", le dice algo concreto sobre ese producto.
Steve Willcock
1
Eliminar toda la información de paginación y clasificación de la ruta es bueno. Y empujarlo a los parámetros POST es malo. ¿Hola? La pregunta es sobre REST. No estamos utilizando POST solo para acortar la URL en REST. El verbo tiene sentido.
temoto
1
Personalmente, no usaría parámetros de formulario para una consulta porque casi requeriría un método HTTP POST o PUT (ya que ahora hay un cuerpo en la solicitud). GET me parece el método más apropiado para usar, ya que tanto POST como PUT implican modificar el recurso. Debido a eso, agregaría más parámetros de consulta a la URL cuando se necesita ordenar por varias columnas.
Paul D. Eden
1

Tiendo a estar de acuerdo con slf en que "página" no es realmente un recurso. Por otro lado, la opción 3 es más limpia, más fácil de leer y el usuario puede adivinarla más fácilmente e incluso escribirla si es necesario. Estoy dividido entre las opciones 1 y 3, pero no veo ninguna razón para no usar la opción 3.

Además, aunque se ven bien, una desventaja de usar parámetros ocultos, como alguien mencionó, en lugar de cadenas de consulta o segmentos de URL es que el usuario no puede marcar o vincular directamente a una página en particular. Eso puede o no ser un problema dependiendo de la aplicación, pero solo algo a tener en cuenta.

soñadora soñadora
fuente
1
Con respecto a su mención de ser más fácil de adivinar, esto no debería importar. Si crea una API hipermedia, los usuarios nunca deberían TENER que adivinar los URI.
JR Garcia
0

He usado la solución 3 antes (escribo MUCHAS aplicaciones de django). Y no creo que haya nada malo en ello. Es tan generable como los otros dos (en caso de que necesite hacer un raspado en masa o similar) y se ve más limpio. Además, sus usuarios pueden adivinar las URL (si es una aplicación pública), y a las personas les gusta poder ir directamente a donde quieran, y adivinar las url se siente fortalecedor.

Alex
fuente
0

Utilizo en mis proyectos las siguientes URL:

http://application/products?page=2&sort=+field1-field2

lo que significa: "dame la segunda página ordenada ascendente por campo1 y luego descendente por campo2". O si necesito aún más flexibilidad, uso:

http://application/products?skip=20&limit=20&sort=+field1-field2
Eugene
fuente
0

Utilizo los siguientes patrones para obtener el siguiente registro de página. http: // application / products? lastRecordKey =? & pageSize = 20 & sort = ASC

RecordKey es la columna de una tabla que contiene un valor secuencial en DB. Esto se usa para obtener solo datos de una página a la vez desde DB. pageSize se utiliza para determinar cuántos registros recuperar. sort se usa para ordenar el registro en orden ascendente o descendente.

Susanta Ghosh
fuente