Eu adoraria alguma ajuda para lidar com um caso estranho com uma API paginada que estou construindo.
Como muitas APIs, esta pagina grandes resultados. Se você consultar / foos, obterá 100 resultados (por exemplo, foo # 1-100) e um link para / foos? Page = 2, que deve retornar foo # 101-200.
Infelizmente, se o foo # 10 for excluído do conjunto de dados antes que o consumidor da API faça a próxima consulta, / foos? Page = 2 será compensado em 100 e retornará o foos # 102-201.
Este é um problema para os consumidores de API que estão tentando obter todos os foos - eles não receberão o foo 101.
Qual é a melhor prática para lidar com isso? Gostaríamos de torná-lo o mais leve possível (ou seja, evitando sessões de manipulação para solicitações de API). Exemplos de outras APIs seriam muito apreciados!
fonte
Respostas:
Não tenho certeza de como seus dados são manipulados, portanto, isso pode ou não funcionar, mas você já pensou em paginar com um campo de carimbo de data / hora?
Quando você consulta / foos, obtém 100 resultados. Sua API deve retornar algo assim (assumindo JSON, mas se precisar de XML, os mesmos princípios podem ser seguidos):
Apenas uma observação: o uso de apenas um carimbo de data e hora depende de um 'limite' implícito nos seus resultados. Você pode adicionar um limite explícito ou também usar uma
until
propriedade.O registro de data e hora pode ser determinado dinamicamente usando o último item de dados na lista. Parece ser mais ou menos como o Facebook pagina em sua API do Graph (role para baixo até o final para ver os links de paginação no formato que eu dei acima).
Um problema pode ser se você adicionar um item de dados, mas com base na sua descrição, parece que eles seriam adicionados ao final (se não, avise-me e verei se posso melhorar isso).
fonte
Você tem vários problemas.
Primeiro, você tem o exemplo que você citou.
Você também tem um problema semelhante se as linhas forem inseridas, mas, neste caso, o usuário obtém dados duplicados (sem dúvida, mais fáceis de gerenciar do que com dados ausentes, mas ainda assim um problema).
Se você não estiver capturando instantaneamente o conjunto de dados original, isso é apenas um fato da vida.
Você pode fazer com que o usuário faça um instantâneo explícito:
Quais resultados:
Em seguida, você pode paginar isso o dia inteiro, pois agora está estático. Isso pode ser razoavelmente leve, já que você pode capturar as chaves do documento real em vez das linhas inteiras.
Se o caso de uso for simplesmente o de que seus usuários desejam (e precisam) todos os dados, você pode simplesmente fornecer a eles:
e apenas envie o kit inteiro.
fonte
Se você tiver paginação, também classifique os dados por alguma chave. Por que não permitir que os clientes da API incluam a chave do último elemento da coleção retornada anteriormente na URL e adicionem uma
WHERE
cláusula à sua consulta SQL (ou algo equivalente, se você não estiver usando o SQL), para que ela retorne apenas os elementos para os quais a chave é maior que esse valor?fonte
Pode haver duas abordagens, dependendo da lógica do servidor.
Abordagem 1: quando o servidor não é inteligente o suficiente para lidar com estados de objetos.
Você pode enviar todos os IDs exclusivos de registro em cache para o servidor, por exemplo ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] e um parâmetro booleano para saber se você está solicitando novos registros (puxe para atualizar) ou registros antigos (carregue mais).
Seu servidor deve ser responsável por retornar novos registros (carregar mais registros ou novos registros por meio de puxar para atualizar), bem como IDs de registros excluídos de ["id1", "id2", "id3", "id4", "id5", " id6 "," id7 "," id8 "," id9 "," id10 "].
Exemplo: - Se você está solicitando carregar mais, sua solicitação deve ser algo como isto: -
Agora, suponha que você esteja solicitando registros antigos (carregue mais) e suponha que o registro "id2" seja atualizado por alguém e os registros "id5" e "id8" sejam excluídos do servidor, e a resposta do servidor deverá ser algo como:
Mas, neste caso, se você tiver muitos registros em cache locais, suponha 500, sua sequência de solicitação será muito longa assim: -
Abordagem 2: Quando o servidor é inteligente o suficiente para lidar com os estados dos objetos de acordo com a data.
Você pode enviar o ID do primeiro registro e o último registro e o tempo da época da solicitação anterior. Dessa forma, sua solicitação é sempre pequena, mesmo se você tiver uma grande quantidade de registros em cache
Exemplo: - Se você está solicitando carregar mais, sua solicitação deve ser algo como isto: -
Seu servidor é responsável por retornar os IDs dos registros excluídos que são excluídos após o last_request_time, bem como retornar o registro atualizado após last_request_time entre "id1" e "id10".
Puxe para atualizar: -
Carregue mais
fonte
Pode ser difícil encontrar práticas recomendadas, já que a maioria dos sistemas com APIs não se adapta a esse cenário, porque é uma vantagem extrema ou eles geralmente não excluem registros (Facebook, Twitter). O Facebook realmente diz que cada "página" pode não ter o número de resultados solicitados devido à filtragem feita após a paginação. https://developers.facebook.com/blog/post/478/
Se você realmente precisar acomodar esse gabinete de borda, precisará "lembrar" de onde parou. A sugestão de jandjorgensen é quase imediata, mas eu usaria um campo garantido como único como a chave primária. Pode ser necessário usar mais de um campo.
Seguindo o fluxo do Facebook, você pode (e deve) armazenar em cache as páginas já solicitadas e apenas retornar aquelas com linhas excluídas filtradas se solicitarem uma página que já haviam solicitado.
fonte
A paginação é geralmente uma operação de "usuário" e, para evitar sobrecarga nos computadores e no cérebro humano, você geralmente atribui um subconjunto. No entanto, em vez de pensar que não recebemos a lista inteira, talvez seja melhor perguntar , isso importa?
Se uma visualização precisa de rolagem ao vivo for necessária, as APIs REST que são de natureza de solicitação / resposta não são adequadas para esse propósito. Para isso, considere WebSockets ou Eventos enviados pelo servidor HTML5 para informar seu front-end ao lidar com alterações.
Agora, se houver necessidade obter uma captura instantânea dos dados, eu apenas forneceria uma chamada de API que forneça todos os dados em uma solicitação sem paginação. Lembre-se de que você precisaria de algo que faria o streaming da saída sem carregá-la temporariamente na memória se você tiver um conjunto de dados grande.
Para o meu caso, designo implicitamente algumas chamadas de API para permitir a obtenção de todas as informações (principalmente os dados da tabela de referência). Você também pode proteger essas APIs para que não danifiquem seu sistema.
fonte
Opção A: Paginação do conjunto de chaves com um carimbo de data e hora
Para evitar os inconvenientes da paginação deslocada que você mencionou, você pode usar a paginação baseada em conjunto de chaves. Geralmente, as entidades têm um registro de data e hora que indica seu horário de criação ou modificação. Esse registro de data e hora pode ser usado para paginação: basta passar o registro de data e hora do último elemento como parâmetro de consulta para a próxima solicitação. O servidor, por sua vez, usa o registro de data e hora como critério de filtro (por exemplo
WHERE modificationDate >= receivedTimestampParameter
)Dessa forma, você não perderá nenhum elemento. Essa abordagem deve ser boa o suficiente para muitos casos de uso. No entanto, lembre-se do seguinte:
Você pode tornar essas desvantagens menos prováveis, aumentando o tamanho da página e usando registros de data e hora com precisão de milissegundos.
Opção B: Paginação estendida do conjunto de chaves com um token de continuação
Para lidar com as desvantagens mencionadas da paginação normal do conjunto de teclas, você pode adicionar um deslocamento ao carimbo de data e hora e usar o chamado "Continuation Token" ou "Cursor". O deslocamento é a posição do elemento em relação ao primeiro elemento com o mesmo registro de data e hora. Geralmente, o token tem um formato parecido
Timestamp_Offset
. Ele é passado para o cliente na resposta e pode ser enviado de volta ao servidor para recuperar a próxima página.O token "1512757072_2" aponta para o último elemento da página e declara "o cliente já obteve o segundo elemento com o carimbo de data / hora 1512757072". Dessa forma, o servidor sabe para onde continuar.
Lembre-se de que você deve lidar com casos em que os elementos foram alterados entre duas solicitações. Isso geralmente é feito adicionando uma soma de verificação ao token. Essa soma de verificação é calculada sobre os IDs de todos os elementos com esse registro de data e hora. Então, acabamos com um formato de token como este:
Timestamp_Offset_Checksum
.Para obter mais informações sobre essa abordagem, consulte a postagem do blog " Paginação da API da Web com tokens de continuação ". Uma desvantagem dessa abordagem é a implementação complicada, pois há muitos casos extremos que precisam ser levados em consideração. É por isso que bibliotecas como o token de continuação podem ser úteis (se você estiver usando a linguagem Java / a JVM). Isenção de responsabilidade: sou o autor da postagem e co-autor da biblioteca.
fonte
Eu acho que atualmente sua API está realmente respondendo da maneira que deveria. Os 100 primeiros registros da página na ordem geral de objetos que você está mantendo. Sua explicação diz que você está usando algum tipo de ID de pedido para definir a ordem dos seus objetos para paginação.
Agora, se você quiser que a página 2 sempre comece de 101 e termine em 200, faça o número de entradas na página como variável, pois elas estão sujeitas a exclusão.
Você deve fazer algo como o pseudocódigo abaixo:
fonte
Apenas para adicionar a esta resposta de Kamilk: https://www.stackoverflow.com/a/13905589
fonte
Eu pensei muito sobre isso e finalmente terminei com a solução que descreverei abaixo. É um grande avanço na complexidade, mas se você fizer esse passo, terá o que realmente procura, que são resultados determinísticos para solicitações futuras.
Seu exemplo de um item sendo excluído é apenas a ponta do iceberg. E se você estiver filtrando,
color=blue
mas alguém alterar as cores dos itens entre as solicitações? Buscar todos os itens de maneira paginável de forma confiável é impossível ... a menos que ... implementemos o histórico de revisões .Eu o implementei e é realmente menos difícil do que eu esperava. Aqui está o que eu fiz:
changelogs
com uma coluna de ID de incremento automáticoid
campo, mas essa não é a chave primáriachangeId
campo que é a chave primária e também uma chave estrangeira para os registros de alterações.changelogs
, pega o ID e o atribui a uma nova versão da entidade, que depois insere no banco de dadoschangeId
representa uma captura instantânea exclusiva dos dados subjacentes no momento em que a alteração foi criada.changeId
nelas para sempre. Os resultados nunca expiram porque nunca mudam.fonte
Outra opção para Paginação em APIs RESTFul, é usar o cabeçalho Link apresentado aqui . Por exemplo, o Github usa- o da seguinte forma:
Os valores possíveis para
rel
são: primeiro, último, próximo, anterior . Mas, usando oLink
cabeçalho, pode não ser possível especificar total_count (número total de elementos).fonte