Mejores prácticas de paginación API

Resuelto 2arrs2ells asked hace 11 años • 13 respuestas

Me encantaría recibir ayuda para manejar un caso extremo extraño con una API paginada que estoy construyendo.

Como muchas API, ésta pagina resultados grandes. Si consulta /foos, obtendrá 100 resultados (es decir, foo #1-100) y un enlace a /foos?page=2 que debería devolver foo #101-200.

Desafortunadamente, si foo #10 se elimina del conjunto de datos antes de que el consumidor de API realice la siguiente consulta, /foos?page=2 se compensará en 100 y devolverá foos #102-201.

Este es un problema para los consumidores de API que intentan extraer todos los foos: no recibirán el foo #101.

¿Cuál es la mejor práctica para manejar esto? Nos gustaría hacerlo lo más liviano posible (es decir, evitar manejar sesiones para solicitudes de API). ¡Se agradecerían mucho los ejemplos de otras API!

2arrs2ells avatar Dec 14 '12 10:12 2arrs2ells
Aceptado

No estoy completamente seguro de cómo se manejan sus datos, por lo que esto puede funcionar o no, pero ¿ha considerado paginar con un campo de marca de tiempo?

Cuando consultas /foos obtienes 100 resultados. Su API debería devolver algo como esto (asumiendo JSON, pero si necesita XML se pueden seguir los mismos principios):

{
    "data" : [
        {  data item 1 with all relevant fields    },
        {  data item 2   },
        ...
        {  data item 100 }
    ],
    "paging":  {
        "previous":  "http://api.example.com/foo?since=TIMESTAMP1" 
        "next":  "http://api.example.com/foo?since=TIMESTAMP2"
    }

}

Solo una nota: el uso de solo una marca de tiempo depende de un "límite" implícito en sus resultados. Es posible que desee agregar un límite explícito o también utilizar una untilpropiedad.

La marca de tiempo se puede determinar dinámicamente utilizando el último elemento de datos de la lista. Así parece ser más o menos cómo pagina Facebook en su Graph API (desplácese hacia abajo para ver los enlaces de paginación en el formato que proporcioné anteriormente).

Un problema puede ser si agrega un elemento de datos, pero según su descripción parece que se agregarían al final (si no, hágamelo saber y veré si puedo mejorar esto).

ramblinjan avatar Dec 16 '2012 21:12 ramblinjan

Si tienes paginación, también ordenas los datos por alguna clave. ¿Por qué no permitir que los clientes API incluyan la clave del último elemento de la colección devuelta anteriormente en la URL y agreguen una WHEREcláusula a su consulta SQL (o algo equivalente, si no está usando SQL) para que devuelva solo aquellos elementos para los cuales ¿La clave es mayor que este valor?

kamilk avatar Dec 16 '2012 21:12 kamilk

Tienes varios problemas.

Primero, tienes el ejemplo que citaste.

También tiene un problema similar si se insertan filas, pero en este caso el usuario obtiene datos duplicados (posiblemente más fácil de administrar que los datos faltantes, pero sigue siendo un problema).

Si no está tomando una instantánea del conjunto de datos original, entonces esto es simplemente una realidad.

Puede hacer que el usuario haga una instantánea explícita:

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

Cuales resultados:

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

Luego puedes paginarlo todo el día, ya que ahora es estático. Esto puede ser razonablemente liviano, ya que solo puede capturar las claves del documento real en lugar de las filas completas.

Si el caso de uso es simplemente que sus usuarios quieren (y necesitan) todos los datos, entonces simplemente puede dárselos:

GET /query/12345?all=true

y solo envía el kit completo.

Will Hartung avatar Dec 18 '2012 21:12 Will Hartung

Puede haber dos enfoques dependiendo de la lógica del lado del servidor.

Enfoque 1: cuando el servidor no es lo suficientemente inteligente para manejar los estados de los objetos.

Puede enviar todos los ID únicos de los registros almacenados en caché al servidor, por ejemplo ["id1","id2","id3","id4","id5","id6","id7","id8","id9", "id10"] y un parámetro booleano para saber si está solicitando registros nuevos (extraer para actualizar) o registros antiguos (cargar más).

Su servidor debe ser responsable de devolver nuevos registros (cargar más registros o registros nuevos mediante extracción para actualizar), así como las identificaciones de los registros eliminados de ["id1","id2","id3","id4","id5"," id6","id7","id8","id9","id10"].

Ejemplo: - Si solicita cargar más, su solicitud debería verse así: -

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]
}

Ahora suponga que está solicitando registros antiguos (cargue más) y suponga que alguien actualiza el registro "id2" y que los registros "id5" e "id8" se eliminan del servidor, entonces la respuesta de su servidor debería verse así:

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

Pero en este caso, si tiene muchos registros en caché locales, supongamos 500, entonces la cadena de solicitud será demasiado larga, como esta:

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10",………,"id500"]//Too long request
}

Enfoque 2: cuando el servidor es lo suficientemente inteligente como para manejar los estados de los objetos según la fecha.

Puede enviar la identificación del primer registro y el último registro y la hora de la solicitud anterior. De esta manera su solicitud siempre es pequeña incluso si tiene una gran cantidad de registros en caché.

Ejemplo: - Si solicita cargar más, su solicitud debería verse así: -

{
        "isRefresh" : false,
        "firstId" : "id1",
        "lastId" : "id10",
        "last_request_time" : 1421748005
}

Su servidor es responsable de devolver las identificaciones de los registros eliminados que se eliminan después del last_request_time, así como de devolver el registro actualizado después del last_request_time entre "id1" y "id10".

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

Tirar para actualizar: -

ingrese la descripción de la imagen aquí

Carga más

ingrese la descripción de la imagen aquí

Mohd Iftekhar Qurashi avatar Jan 20 '2015 10:01 Mohd Iftekhar Qurashi

Puede ser difícil encontrar mejores prácticas, ya que la mayoría de los sistemas con API no se adaptan a este escenario, porque es una ventaja extrema o, por lo general, no eliminan registros (Facebook, Twitter). Facebook en realidad dice que es posible que cada "página" no tenga la cantidad de resultados solicitados debido al filtrado realizado después de la paginación. https://developers.facebook.com/blog/post/478/

Si realmente necesita adaptarse a este caso extremo, debe "recordar" dónde lo dejó. La sugerencia de Jandjorgesen es casi acertada, pero yo usaría un campo garantizado como único como la clave principal. Es posible que necesite utilizar más de un campo.

Siguiendo el flujo de Facebook, puedes (y debes) almacenar en caché las páginas ya solicitadas y simplemente devolver aquellas con filas eliminadas filtradas si solicitan una página que ya habían solicitado.

Brent Baisley avatar Dec 16 '2012 21:12 Brent Baisley