Me encantaría obtener ayuda para manejar un caso marginal extraño con una API paginada que estoy construyendo.
Al igual que muchas API, esta página ofrece grandes resultados. 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 atraer a todos los foos: no recibirán el foo # 101.
¿Cuál es la mejor práctica para manejar esto? Nos gustaría que sea lo más ligero posible (es decir, evitar el manejo de sesiones para solicitudes de API). ¡Los ejemplos de otras API serían muy apreciados!
fuente
Respuestas:
No estoy completamente seguro de cómo se manejan sus datos, por lo que esto puede o no funcionar, pero ¿ha considerado paginar con un campo de marca de tiempo?
Cuando consulta / foos obtiene 100 resultados. Su API debería devolver algo como esto (suponiendo JSON, pero si necesita XML, se pueden seguir los mismos principios):
Solo una nota, solo usar una marca de tiempo se basa en un "límite" implícito en sus resultados. Es posible que desee agregar un límite explícito o también usar una
until
propiedad.La marca de tiempo se puede determinar dinámicamente utilizando el último elemento de datos de la lista. Esto parece ser más o menos cómo Facebook pagina en su API Graph (desplácese hacia abajo para ver los enlaces de paginación en el formato que di anteriormente).
Un problema puede ser si agrega un elemento de datos, pero según su descripción, parece que se agregarán al final (si no, hágamelo saber y veré si puedo mejorar en esto).
fuente
Tienes varios problemas
Primero, tiene el ejemplo que citó.
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á capturando instantáneamente el conjunto de datos original, entonces esto es solo una realidad.
Puede hacer que el usuario realice una instantánea explícita:
Cuales resultados:
Luego puede paginar eso todo el día, ya que ahora es estático. Esto puede ser razonablemente ligero, ya que puede capturar las claves de documento reales en lugar de las filas completas.
Si el caso de uso es simplemente que sus usuarios desean (y necesitan) todos los datos, simplemente puede dárselos:
y solo envíe el kit completo.
fuente
Si tiene paginación, también ordena 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
WHERE
cláusula a su consulta SQL (o algo equivalente, si no está utilizando SQL) para que solo devuelva aquellos elementos para los cuales la clave es mayor que este valor?fuente
Puede haber dos enfoques dependiendo de la lógica del lado del servidor.
Enfoque 1: cuando el servidor no es lo suficientemente inteligente como para manejar estados de objetos.
Puede enviar todos los ID únicos de registro 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 nuevos registros (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 los ID de los registros eliminados de ["id1", "id2", "id3", "id4", "id5", " id6 "," id7 "," id8 "," id9 "," id10 "].
Ejemplo: - Si está solicitando cargar más, su solicitud debería verse así: -
Ahora suponga que está solicitando registros antiguos (cargue más) y suponga que alguien actualiza el registro "id2" y los registros "id5" e "id8" se eliminan del servidor, entonces su respuesta del servidor debería verse así:
Pero en este caso, si tiene muchos registros en caché locales, supongamos 500, entonces su cadena de solicitud será demasiado larga como esta:
Enfoque 2: cuando el servidor es lo suficientemente inteligente como para manejar estados de objetos según la fecha.
Puede enviar la identificación del primer registro y el último registro y el tiempo de solicitud anterior. De esta manera, su solicitud siempre es pequeña, incluso si tiene una gran cantidad de registros en caché
Ejemplo: - Si está solicitando cargar más, su solicitud debería verse así: -
Su servidor es responsable de devolver los id de los registros eliminados que se eliminan después de last_request_time, así como devolver el registro actualizado después de last_request_time entre "id1" e "id10".
Tire para actualizar: -
Carga más
fuente
Puede ser difícil encontrar las 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 realmente dice que cada "página" puede no tener 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 acomodar este caso extremo, debe "recordar" dónde lo dejó. La sugerencia de jandjorgensen es casi acertada, pero usaría un campo garantizado para ser único como la clave principal. Es posible que deba usar más de un campo.
Siguiendo el flujo de Facebook, puede (y debería) 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.
fuente
La paginación es generalmente una operación de "usuario" y para evitar la sobrecarga tanto en las computadoras como en el cerebro humano, generalmente se le asigna un subconjunto. Sin embargo, en lugar de pensar que no obtenemos la lista completa, puede ser mejor preguntar ¿importa?
Si se necesita una vista de desplazamiento en vivo precisa, las API REST que son de naturaleza solicitud / respuesta no son adecuadas para este propósito. Para esto, debe considerar WebSockets o Eventos enviados por el servidor HTML5 para informar a su front end cuando se trata de cambios.
Ahora, si es necesario obtener una instantánea de los datos, solo proporcionaría una llamada API que proporciona todos los datos en una solicitud sin paginación. Eso sí, necesitaría algo que hiciera la transmisión de la salida sin cargarla temporalmente en la memoria si tiene un conjunto de datos grande.
Para mi caso, implícitamente designo algunas llamadas API para permitir obtener toda la información (principalmente datos de la tabla de referencia). También puede proteger estas API para que no dañe su sistema.
fuente
Opción A: paginación de conjunto de claves con una marca de tiempo
Para evitar los inconvenientes de la paginación de desplazamiento que ha mencionado, puede usar la paginación basada en el conjunto de claves. Por lo general, las entidades tienen una marca de tiempo que indica su hora de creación o modificación. Esta marca de tiempo se puede usar para paginación: simplemente pase la marca de tiempo del último elemento como parámetro de consulta para la próxima solicitud. El servidor, a su vez, usa la marca de tiempo como criterio de filtro (por ejemplo
WHERE modificationDate >= receivedTimestampParameter
)De esta manera, no te perderás ningún elemento. Este enfoque debería ser lo suficientemente bueno para muchos casos de uso. Sin embargo, tenga en cuenta lo siguiente:
Puede hacer que esos inconvenientes sean menos probables aumentando el tamaño de la página y utilizando marcas de tiempo con una precisión de milisegundos.
Opción B: paginación de conjunto de claves extendida con un token de continuación
Para manejar los inconvenientes mencionados de la paginación normal del conjunto de claves, puede agregar un desplazamiento a la marca de tiempo y usar el llamado "Token de Continuación" o "Cursor". El desplazamiento es la posición del elemento con respecto al primer elemento con la misma marca de tiempo. Por lo general, el token tiene un formato como
Timestamp_Offset
. Se pasa al cliente en la respuesta y se puede volver a enviar al servidor para recuperar la página siguiente.El token "1512757072_2" apunta al último elemento de la página y dice "el cliente ya obtuvo el segundo elemento con la marca de tiempo 1512757072". De esta manera, el servidor sabe dónde continuar.
Tenga en cuenta que debe manejar los casos en que los elementos se cambiaron entre dos solicitudes. Esto generalmente se hace agregando una suma de verificación al token. Esta suma de comprobación se calcula sobre los ID de todos los elementos con esta marca de tiempo. Así que terminamos con un formato de token de la siguiente manera:
Timestamp_Offset_Checksum
.Para obtener más información sobre este enfoque, consulte la publicación del blog " Paginación de API web con tokens de continuación ". Un inconveniente de este enfoque es la implementación complicada ya que hay muchos casos de esquina que deben tenerse en cuenta. Es por eso que las bibliotecas como el token de continuación pueden ser útiles (si está utilizando lenguaje Java / a JVM). Descargo de responsabilidad: soy el autor de la publicación y coautor de la biblioteca.
fuente
Creo que actualmente tu API está respondiendo realmente como debería. Los primeros 100 registros de la página en el orden general de los objetos que está manteniendo. Su explicación le dice que está utilizando algún tipo de identificación de pedido para definir el orden de sus objetos para la paginación.
Ahora, en caso de que desee que la página 2 siempre comience desde 101 y termine en 200, debe hacer que el número de entradas en la página sea variable, ya que están sujetas a eliminación.
Debe hacer algo como el pseudocódigo siguiente:
fuente
Solo para agregar a esta respuesta por Kamilk: https://www.stackoverflow.com/a/13905589
fuente
He pensado mucho sobre esto y finalmente terminé con la solución que describiré a continuación. Es un gran avance en la complejidad, pero si haces este paso, terminarás con lo que realmente buscas, que son resultados deterministas para futuras solicitudes.
Su ejemplo de un elemento que se está eliminando es solo la punta del iceberg. ¿Qué sucede si está filtrando
color=blue
pero alguien cambia los colores de los elementos entre las solicitudes? Es imposible recuperar todos los elementos de forma confiable y paginada ... a menos que ... implementemos el historial de revisiones .Lo he implementado y en realidad es menos difícil de lo que esperaba. Esto es lo que hice:
changelogs
con una columna de ID de incremento automáticoid
campo, pero esta no es la clave principalchangeId
campo que es tanto la clave principal como una clave externa para los registros de cambios.changelogs
, toma el id y lo asigna a una nueva versión de la entidad, que luego inserta en el DBchangeId
representa una instantánea única de los datos subyacentes en el momento en que se creó el cambio.changeId
en ellos para siempre. Los resultados nunca caducarán porque nunca cambiarán.fuente
Otra opción para la paginación en las API RESTFul es utilizar el encabezado de enlace que se presenta aquí . Por ejemplo, Github lo usa de la siguiente manera:
Los valores posibles para
rel
son: primero, último, siguiente, anterior . Pero al usar elLink
encabezado, puede que no sea posible especificar total_count (número total de elementos).fuente