Estou desenvolvendo um serviço de API REST para um grande site de rede social em que estou envolvido. Até agora, está funcionando muito bem. Eu posso emitir GET
, POST
, PUT
e DELETE
solicitações para URLs objeto e afetar meus dados. No entanto, esses dados são paginados (limitados a 30 resultados por vez).
No entanto, qual seria a melhor maneira RESTful de obter o número total de digamos, membros, por meio da minha API?
Atualmente, emito solicitações para uma estrutura de URL como a seguinte:
- / api / members - retorna uma lista de membros (30 por vez, conforme mencionado acima)
- / api / members / 1 - afeta um único membro, dependendo do método de solicitação usado
Minha pergunta é: como eu usaria uma estrutura de URL semelhante para obter o número total de membros no meu aplicativo? Obviamente, solicitar apenas o id
campo (semelhante à API Graph do Facebook) e contar os resultados seria ineficaz, pois apenas uma fatia de 30 resultados seria retornada.
fonte
Respostas:
Embora a resposta a / API / users seja paginada e retorne apenas 30 registros, nada impede que você inclua na resposta também o número total de registros e outras informações relevantes, como o tamanho da página, o número da página / deslocamento, etc. .
A API StackOverflow é um bom exemplo desse mesmo design. Aqui está a documentação para o método Users - https://api.stackexchange.com/docs/users
fonte
Eu prefiro usar cabeçalhos HTTP para esse tipo de informação contextual.
Para o número total de elementos eu uso o
X-total-count
cabeçalho.Para obter links para a página seguinte, anterior etc., eu uso o
Link
cabeçalho http :http://www.w3.org/wiki/LinkHeader
O Github faz o mesmo: https://developer.github.com/v3/#pagination
Na minha opinião, é mais limpo, pois também pode ser usado quando você devolve conteúdo que não suporta hiperlinks (por exemplo, binários, imagens).
fonte
X-
.Ultimamente, tenho pesquisado extensivamente essa e outras perguntas relacionadas à paginação REST e achei construtivo adicionar algumas das minhas descobertas aqui. Estou expandindo um pouco a questão para incluir pensamentos sobre paginação, bem como a contagem, pois eles estão intimamente relacionados.
Cabeçalhos
Os metadados de paginação são incluídos na resposta na forma de cabeçalhos de resposta. O grande benefício dessa abordagem é que a carga útil da resposta é exatamente o que o solicitante de dados real estava solicitando. Facilitando o processamento da resposta para clientes que não estão interessados nas informações de paginação.
Existem vários cabeçalhos (padrão e personalizados) usados em caráter selvagem para retornar informações relacionadas à paginação, incluindo a contagem total.
Contagem X total
Isso é usado em algumas APIs que encontrei na natureza. Existem também pacotes NPM para adicionar suporte a este cabeçalho, por exemplo, Loopback. Alguns artigos recomendam a configuração desse cabeçalho também.
Geralmente é usado em combinação com o
Link
cabeçalho, que é uma solução muito boa para paginação, mas não possui as informações totais de contagem.Ligação
Sinto-me, de ler muito sobre este assunto, que o consenso geral é usar o
Link
cabeçalho para fornecer paginação links para clientes usandorel=next
,rel=previous
etc. O problema com isto é que ela não tem a informação de quantos registros totais existem, o que é por que muitas APIs combinam isso com oX-Total-Count
cabeçalho.Como alternativa, algumas APIs e, por exemplo, o padrão JsonApi , usam o
Link
formato, mas adicionam as informações em um envelope de resposta em vez de em um cabeçalho. Isso simplifica o acesso aos metadados (e cria um local para adicionar as informações totais da contagem) às custas do aumento da complexidade do acesso aos próprios dados reais (adicionando um envelope).Intervalo de conteúdo
Promovido por um artigo de blog chamado Range header, eu escolho você (para paginação)! . O autor defende fortemente o uso dos cabeçalhos
Range
eContent-Range
para paginação. Quando lemos atentamente a RFC nesses cabeçalhos, descobrimos que estender seu significado além dos intervalos de bytes foi realmente antecipado pela RFC e é explicitamente permitido. Quando usado no contexto de emitems
vez debytes
, o cabeçalho Range realmente nos permite solicitar um certo intervalo de itens e indicar a qual intervalo do resultado total os itens de resposta se referem. Esse cabeçalho também oferece uma ótima maneira de mostrar a contagem total. E é um verdadeiro padrão que mapeia principalmente a paginação individual. Também é usado na natureza .Envelope
Muitas APIs, incluindo a do nosso site de perguntas e respostas favorito, usam um envelope , um invólucro em torno dos dados que são usados para adicionar meta informações sobre os dados. Além disso, os padrões OData e JsonApi usam um envelope de resposta.
A grande desvantagem disso (imho) é que o processamento dos dados de resposta se torna mais complexo, pois os dados reais precisam ser encontrados em algum lugar do envelope. Além disso, existem muitos formatos diferentes para esse envelope e você precisa usar o correto. É revelador que os envelopes de resposta do OData e JsonApi são totalmente diferentes, com o OData misturando metadados em vários pontos da resposta.
Ponto final separado
Eu acho que isso foi abordado o suficiente nas outras respostas. Não investiguei muito isso, porque concordo com os comentários de que isso é confuso, pois agora você tem vários tipos de pontos de extremidade. Eu acho que é melhor se cada endpoint representar um (conjunto de) recursos.
Pensamentos adicionais
Não precisamos apenas comunicar as meta informações de paginação relacionadas à resposta, mas também permitir que o cliente solicite páginas / intervalos específicos. É interessante observar também esse aspecto para obter uma solução coerente. Aqui também podemos usar cabeçalhos (o
Range
cabeçalho parece muito adequado) ou outros mecanismos, como parâmetros de consulta. Algumas pessoas defendem o tratamento de páginas de resultados como recursos separados, o que pode fazer sentido em alguns casos de uso (por exemplo/books/231/pages/52
, acabei selecionando uma variedade de parâmetros de solicitação usados com frequência, comopagesize
,page[size]
elimit
etc, além de oferecer suporte aoRange
cabeçalho (e como parâmetro de solicitação também).fonte
Range
cabeçalho, mas não consegui encontrar evidências suficientes de que o uso de algo além debytes
um tipo de intervalo seja válido.acceptable-ranges = 1#range-unit | "none"
Eu acho que essa formulação deixa explicitamente espaço para outras unidades de alcance quebytes
, embora as especificações em si só definambytes
.Alternativa quando você não precisa de itens reais
A resposta de Franci Penov é certamente o melhor caminho a percorrer, para que você sempre retorne itens, juntamente com todos os metadados adicionais sobre as solicitações de suas entidades. É assim que deve ser feito.
mas, às vezes, retornar todos os dados não faz sentido, porque talvez você não precise deles. Talvez tudo o que você precise é de metadados sobre o recurso solicitado. Como contagem total ou número de páginas ou outra coisa. Nesse caso, você sempre pode fazer com que a consulta de URL informe ao seu serviço para não retornar itens, mas apenas metadados como:
ou algo parecido ...
fonte
metaonly
ouincludeitems
não é.Você pode retornar a contagem como um cabeçalho HTTP personalizado em resposta a uma solicitação HEAD. Dessa forma, se um cliente deseja apenas a contagem, você não precisa retornar a lista real e não há necessidade de um URL adicional.
(Ou, se você estiver em um ambiente controlado de um ponto a outro, poderá usar um verbo HTTP personalizado, como COUNT.)
fonte
Eu recomendaria adicionar cabeçalhos para o mesmo, como:
Para detalhes, consulte:
https://github.com/adnan-kamili/rest-api-response-format
Para arquivo swagger:
https://github.com/adnan-kamili/swagger-response-template
fonte
A partir de "X -" - o prefixo foi descontinuado. (consulte: https://tools.ietf.org/html/rfc6648 )
Nós achamos que "Accept-Ranges" é a melhor opção para mapear o intervalo de paginação: https://tools.ietf.org/html/rfc7233#section-2.3 Como as "Range Units" podem ser "bytes" ou " símbolo". Ambos não representam um tipo de dados personalizado. (consulte: https://tools.ietf.org/html/rfc7233#section-4.2 ) Ainda assim, afirma-se que
O que indica: o uso de unidades de alcance personalizadas não é contra o protocolo, mas PODE ser ignorado.
Dessa forma, teríamos que definir os intervalos de aceitação para "membros" ou qualquer tipo de unidade à distância, esperávamos. Além disso, também defina o intervalo de conteúdo para o intervalo atual. (consulte: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )
De qualquer forma, gostaria de seguir a recomendação do RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) para enviar um 206 em vez de 200:
Portanto, como resultado, teríamos os seguintes campos de cabeçalho HTTP:
Para conteúdo parcial:
Para conteúdo completo:
fonte
Parece mais fácil adicionar apenas um
e retorne a contagem total de membros
fonte
Que tal um novo ponto final> / api / members / count que apenas chama Members.Count () e retorna o resultado
fonte
members
coleção puder ser criada por uma solicitação POST para/api
, também/api/members/count
será criada como efeito colateral, ou eu tenho que fazer uma solicitação POST explícita para criá-la antes de solicitá-la? :-)Às vezes, estruturas (como $ resource / AngularJS) exigem uma matriz como resultado da consulta, e você realmente não pode ter uma resposta, como
{count:10,items:[...]}
neste caso eu armazeno "count" em responseHeaders.PS Na verdade, você pode fazer isso com $ resource / AngularJS, mas ele precisa de alguns ajustes.
fonte
isArray: false|true
Você pode considerar
counts
como um recurso. O URL seria então:fonte
Ao solicitar dados paginados, você conhece (por valor explícito do parâmetro do tamanho da página ou valor padrão do tamanho da página) o tamanho da página, para saber se obteve todos os dados em resposta ou não. Quando há menos dados em resposta do que o tamanho da página, você obtém dados inteiros. Quando uma página inteira é retornada, você deve solicitar outra página.
Prefiro ter um endpoint separado para count (ou o mesmo endpoint com o parâmetro countOnly). Porque você pode preparar o usuário final para um processo demorado / demorado, mostrando a barra de progresso iniciada corretamente.
Se você deseja retornar o tamanho dos dados em cada resposta, deve haver pageSize, deslocamento mencionado também. Para ser honesto, a melhor maneira é repetir também os filtros de solicitação. Mas a resposta se tornou muito complexa. Portanto, prefiro o endpoint dedicado para retornar a contagem.
Em relação à minha, prefira um parâmetro countOnly ao ponto final existente. Portanto, quando especificada, a resposta contém apenas metadados.
ponto final? filter = value
ponto final? filter = value & countOnly = true
fonte