Implementar paginação e totais de pesquisa com a pesquisa FHIR

A implementação da pesquisa FHIR da Cloud Healthcare API é altamente escalável e com bom desempenho, ao mesmo tempo que segue as diretrizes e as limitações do REST e da especificação FHIR. Para ajudar a alcançar este objetivo, a pesquisa FHIR tem as seguintes propriedades:

  • O total de pesquisas é uma estimativa até ser devolvida a última página. Os resultados da pesquisa devolvidos pelo método fhir.search incluem o total da pesquisa (a propriedade Bundle.total), que é o número total de correspondências na pesquisa. O total da pesquisa é uma estimativa até ser devolvida a última página de resultados da pesquisa. O total de pesquisas devolvido com a última página de resultados é uma soma precisa de todas as correspondências na pesquisa.

  • Os resultados da pesquisa oferecem paginação sequencial para a frente. Quando existem mais resultados da pesquisa a devolver, a resposta inclui um URL de paginação (Bundle.link.url) para obter a página seguinte de resultados.

Exemplos de utilização básicos

A pesquisa FHIR oferece soluções para os seguintes exemplos de utilização:

Consulte as secções seguintes para ver possíveis soluções para estes exemplos de utilização.

Navegue sequencialmente

Pode criar uma aplicação de baixa latência que permita a um utilizador navegar sequencialmente pelas páginas de resultados até encontrar a correspondência pretendida. Esta solução é viável se o número de correspondências for suficientemente pequeno para que o utilizador possa encontrar a correspondência desejada navegando página a página sem ignorar resultados. Se o seu exemplo de utilização exigir que os utilizadores naveguem para a frente várias páginas de cada vez ou naveguem para trás, consulte Navegue para uma página próxima.

Esta solução não pode fornecer um total de pesquisas preciso até ser devolvida a última página de resultados. No entanto, pode fornecer um total de pesquisas aproximado em cada página de resultados. Embora um total de pesquisas preciso possa ser um requisito para um processo em segundo plano, um total de pesquisas aproximado é normalmente adequado para um utilizador humano.

Fluxo de trabalho

Segue-se um exemplo de fluxo de trabalho para esta solução:

  1. Uma app chama o método fhir.search, que devolve a primeira página de resultados da pesquisa. A resposta inclui um URL de paginação (Bundle.link.url) se houver mais resultados a devolver. A resposta também inclui o total da pesquisa (Bundle.total). Se houver mais resultados a devolver do que na resposta inicial, o total da pesquisa é apenas uma estimativa. Para mais informações, consulte o artigo Paginação e ordenação.

  2. A app apresenta a página de resultados da pesquisa, um link para a página seguinte de resultados (se existir) e o total da pesquisa.

  3. Se o utilizador quiser ver a página seguinte de resultados, clica no link, o que resulta numa chamada ao URL de paginação. A resposta inclui um novo URL de paginação se houver mais resultados a devolver. A resposta também inclui o total de pesquisas. Esta é uma estimativa atualizada se existirem mais resultados a devolver.

  4. A app apresenta a nova página de resultados da pesquisa, um link para a página seguinte de resultados (se existirem) e o total da pesquisa.

  5. Os dois passos anteriores são repetidos até o utilizador parar de pesquisar ou ser devolvida a última página de resultados.

Prática recomendada

Escolha um tamanho da página adequado para uma pessoa ler sem dificuldade. Consoante o seu exemplo de utilização, podem ser 10 a 20 correspondências por página. As páginas mais pequenas são carregadas mais rapidamente, e demasiados links numa página podem ser difíceis de gerir para um utilizador. Controla o tamanho da página com o parâmetro _count.

Processar um conjunto de resultados da pesquisa

Pode obter um conjunto de resultados da pesquisa fazendo chamadas sucessivas ao método fhir.search através do URL de paginação. Se o número de resultados da pesquisa for suficientemente pequeno, pode obter um conjunto completo de resultados continuando até não haver mais páginas a devolver. É incluído um total de pesquisas preciso na última página de resultados. Depois de receber os resultados da pesquisa, a sua app pode lê-los e realizar qualquer processamento, análise ou agregação que necessite.

Tenha em atenção que, se tiver um número muito elevado de resultados da pesquisa, pode não ser possível obter a última página de resultados da pesquisa (e o total da pesquisa preciso) num período de tempo prático.

Fluxo de trabalho

Segue-se um exemplo de fluxo de trabalho para esta solução:

  1. A app chama o método fhir.search, que devolve a primeira página dos resultados da pesquisa. A resposta inclui um URL de paginação (Bundle.link.url) se houver mais resultados a devolver.

  2. Se existirem mais resultados a devolver, a app chama o URL de paginação do passo anterior para obter a página seguinte de resultados da pesquisa.

  3. A app repete o passo anterior até não haver mais resultados a devolver ou até ser atingido outro limite predefinido. Se chegar à última página de resultados da pesquisa, o total da pesquisa é preciso.

  4. A app processa os resultados da pesquisa.

Consoante o seu exemplo de utilização, a app pode fazer qualquer uma das seguintes ações:

  • Aguarde até receber todos os resultados da pesquisa antes de processar os dados.
  • Processar os dados à medida que são recebidos com cada chamada sucessiva para fhir.search.
  • Definir algum tipo de limite, como o número de correspondências devolvidas ou a quantidade de tempo decorrido. Quando o limite é atingido, pode processar os dados, não processar os dados ou realizar outra ação.

Opções de design

Seguem-se algumas opções de design que podem diminuir a latência de pesquisa:

  • Defina um tamanho de página grande. Use o parâmetro _count para definir um tamanho da página grande, talvez de 500 a 1000, consoante o seu exemplo de utilização. A utilização de um tamanho de página maior aumenta a latência para cada obtenção de páginas, mas pode acelerar o processo geral, uma vez que são necessárias menos obtenções de páginas para obter o conjunto completo de resultados da pesquisa.

  • Limitar os resultados da pesquisa. Se só precisar de um total de pesquisa preciso (não precisa de devolver conteúdo de recursos), defina o parâmetro _elements do método fhir.search como identifier. Isto pode diminuir a latência da sua consulta de pesquisa, em comparação com o pedido de devolução de recursos FHIR completos. Para mais informações, consulte o artigo Limitar os campos devolvidos nos resultados da pesquisa.

Exemplos de utilização que requerem obtenção prévia e colocação em cache

Pode precisar de funcionalidades além do que é possível usando o mecanismo simples de chamar sucessivamente o método fhir.search usando URLs de paginação. Uma possibilidade é criar uma camada de colocação em cache entre a sua app e o FHIR store que mantém o estado da sessão enquanto pré-busca e coloca em cache os resultados da pesquisa. A app pode agrupar os resultados da pesquisa em pequenas "páginas de apps" de 10 ou 20 correspondências. Em seguida, a app pode publicar rapidamente estas pequenas páginas da app para o utilizador de forma direta e não sequencial, com base nas seleções do utilizador. Consulte o artigo Navegue para uma página próxima para ver um exemplo deste tipo de fluxo de trabalho.

Pode criar uma solução de baixa latência que permita a um utilizador percorrer um grande número de resultados da pesquisa até encontrar a correspondência que procura. Pode ser dimensionada para um número praticamente ilimitado de correspondências, mantendo a latência baixa e incorrendo num aumento relativamente pequeno no consumo de recursos. Um utilizador pode navegar diretamente para uma página de resultados da pesquisa, até um número predeterminado de páginas para a frente ou para trás a partir da página atual. Pode fornecer um total de pesquisas estimado com cada página de resultados. Para referência, este design é semelhante ao design da Pesquisa Google.

Fluxo de trabalho

A Figura 1 mostra um exemplo de fluxo de trabalho para esta solução. Com este fluxo de trabalho, sempre que o utilizador seleciona uma página de resultados para ver, a app fornece links para páginas próximas. Neste caso, a app fornece links para páginas até cinco páginas anteriores à página selecionada e até quatro páginas posteriores à página selecionada. Para disponibilizar as quatro páginas seguintes para visualização, a app pré-busca 40 correspondências adicionais quando o utilizador seleciona uma página de resultados.

obter previamente e colocar em cache

Figura 1. A app agrupa os resultados da pesquisa em "páginas de apps" que são colocadas em cache e disponibilizadas ao utilizador.

A Figura 1 ilustra estes passos:

  1. O utilizador introduz uma consulta de pesquisa no frontend da app, o que inicia as seguintes ações:

    1. A app chama o método fhir.search para pré-obter a primeira página dos resultados da pesquisa.

      O parâmetro _count está definido como 100 para manter o tamanho da página da resposta relativamente pequeno, o que resulta em tempos de resposta relativamente rápidos. A resposta inclui um URL de paginação (Bundle.link.url) se existirem mais resultados a devolver. A resposta também inclui o total da pesquisa (Bundle.total). O total da pesquisa é uma estimativa se houver mais resultados a devolver. Para mais informações, consulte o artigo Paginação e ordenação.

    2. A app envia a resposta para a camada de colocação em cache.

  2. Na camada de colocação em cache, a app agrupa as 100 correspondências da resposta em 10 páginas de apps com 10 correspondências cada. Uma página de app é um pequeno agrupamento de correspondências que a app pode apresentar ao utilizador.

  3. A app apresenta a página 1 da app ao utilizador. A página da app 1 inclui links para as páginas da app 2 a 10 e o total de pesquisas estimado.

  4. O utilizador clica num link para uma página de app diferente (página de app 10 neste exemplo), o que inicia as seguintes ações:

    1. A app chama o método fhir.search, usando o URL de paginação que foi devolvido com a obtenção prévia anterior, para obter previamente a página seguinte de resultados da pesquisa.

      O parâmetro _count está definido como 40 para pré-obter as 40 correspondências seguintes da consulta de pesquisa do utilizador. As 40 correspondências destinam-se às quatro páginas da app seguintes que a app disponibiliza ao utilizador.

    2. A app envia a resposta para a camada de colocação em cache.

  5. Na camada de colocação em cache, a app agrupa as 40 correspondências da resposta em quatro páginas de apps de 10 correspondências cada.

  6. A app apresenta a página 10 da app ao utilizador. A página da app 10 inclui links para as páginas da app 5 a 9 (as cinco páginas da app anteriores à página da app 10) e links para as páginas da app 11 a 14 (as quatro páginas da app posteriores à página da app 10). A página da app 10 também inclui o total de pesquisas estimado.

Isto pode continuar enquanto o utilizador quiser continuar a clicar em links para páginas de apps. Tenha em atenção que, se o utilizador navegar para trás a partir da página da app atual e já tiver todas as páginas da app próximas em cache, pode optar por não fazer uma nova obtenção prévia, consoante o seu exemplo de utilização.

Esta solução é rápida e eficiente pelos seguintes motivos:

  • As pré-obtenções pequenas e as páginas de apps ainda mais pequenas são processadas rapidamente.
  • Os resultados da pesquisa em cache reduzem a necessidade de fazer várias chamadas para os mesmos resultados.
  • O mecanismo permanece rápido, independentemente do aumento do número de resultados da pesquisa.

Opções de design

Seguem-se algumas opções de design a considerar, consoante o seu exemplo de utilização:

  • Tamanho da página da app. As páginas da sua app podem conter mais de 10 correspondências se se adequarem ao seu exemplo de utilização. Tenha em atenção que as páginas mais pequenas são carregadas mais rapidamente e que demasiados links numa página podem ser difíceis de gerir para um utilizador.

  • Número de links da página da app. No fluxo de trabalho sugerido aqui, cada página da app devolve nove links para outras páginas da app: cinco links para as páginas da app diretamente anteriores à página da app atual e quatro links para as páginas diretamente posteriores à página da app atual. Pode ajustar estes números de acordo com o seu exemplo de utilização.

Práticas recomendadas

  • Use a camada de colocação em cache apenas quando necessário. Se configurar uma camada de cache, use-a apenas quando o seu exemplo de utilização o exigir. As pesquisas que não requerem a camada de colocação em cache devem ignorá-la.

  • Reduza o tamanho da cache. Para conservar recursos, pode reduzir o tamanho da cache purgando os resultados da pesquisa antigos, mantendo os URLs das páginas que usou para obter os resultados. Em seguida, pode reconstruir a cache conforme necessário chamando os URLs das páginas. Tenha em atenção que os resultados de várias chamadas ao mesmo URL de paginação podem mudar ao longo do tempo, à medida que os recursos no FHIR store são criados, atualizados e eliminados em segundo plano. A decisão de limpar, como limpar e com que frequência limpar a cache são decisões de design que dependem do seu exemplo de utilização.

  • Limpe a cache para uma determinada pesquisa. Para conservar recursos, pode remover completamente da cache os resultados de pesquisas inativas. Considere remover primeiro as pesquisas mais antigas. Tenha em atenção que, se uma pesquisa eliminada for ativada novamente, isto pode causar um estado de erro que força a camada de cache a reiniciar a pesquisa.

Se quiser que um utilizador possa navegar para qualquer página nos resultados da pesquisa e não apenas para as páginas próximas da página atual, pode usar uma camada de colocação em cache semelhante à descrita em Navegue para uma página próxima. No entanto, para permitir que um utilizador navegue para qualquer página de resultados da pesquisa da app, tem de pré-obter e colocar em cache todos os resultados da pesquisa. Com um número relativamente pequeno de resultados da pesquisa, isto é possível. Com um número muito grande de resultados da pesquisa, pode ser impraticável ou impossível pré-obtê-los todos. Mesmo com um número modesto de resultados da pesquisa, o tempo necessário para pré-obter os resultados pode ser superior ao tempo que é razoável esperar que um utilizador aguarde.

Fluxo de trabalho

Configure um fluxo de trabalho semelhante a Navegar para uma página próxima, com esta diferença fundamental: a app continua a obter previamente resultados da pesquisa em segundo plano até que todas as correspondências sejam devolvidas ou seja atingido outro limite predefinido.

Segue-se um exemplo de fluxo de trabalho para esta solução:

  1. A app chama o método fhir.search para pré-obter a primeira página de resultados da pesquisa a partir da consulta de pesquisa do utilizador. A resposta inclui um URL de paginação (Bundle.link.url) se houver mais resultados a devolver. A resposta também inclui o total da pesquisa (Bundle.total). Esta é uma estimativa se houver mais resultados a devolver.

  2. A app agrupa as correspondências da resposta em páginas de apps de 20 correspondências cada e armazena-as na cache. Uma página de apps é um pequeno agrupamento de correspondências que a app pode apresentar ao utilizador.

  3. A app apresenta a primeira página da app ao utilizador. A página da app inclui links para as páginas da app em cache e o total de pesquisas estimado.

  4. Se existirem mais resultados a devolver, a app faz o seguinte:

    • Chama o URL de paginação devolvido da obtenção prévia anterior para obter a página seguinte de resultados da pesquisa.
    • Agrupa as correspondências da resposta em páginas da app de 20 correspondências cada e armazena-as na cache.
    • Atualiza a página da app que o utilizador está a ver atualmente com novos links para as páginas da app pré-obtidas e colocadas em cache recentemente.
  5. A app repete o passo anterior até não haver mais resultados a devolver ou até ser atingido outro limite predefinido. É devolvido um total de pesquisa preciso com a última página de resultados da pesquisa.

Enquanto a app pré-obter e colocar em cache as correspondências em segundo plano, o utilizador pode continuar a clicar em links para páginas em cache.

Opções de design

Seguem-se algumas opções de design a considerar, consoante o seu exemplo de utilização:

  • Tamanho da página da app. As páginas da sua app podem conter mais ou menos de 20 correspondências se se adequarem ao seu exemplo de utilização. Tenha em atenção que as páginas mais pequenas são carregadas mais rapidamente e que demasiados links numa página podem ser difíceis de gerir para um utilizador.

  • Atualize o total de pesquisas. Enquanto a sua app pré-obter e colocar em cache os resultados da pesquisa em segundo plano, pode apresentar ao utilizador totais de pesquisa progressivamente mais precisos. Para o fazer, configure a sua app para fazer o seguinte:

    • A um intervalo definido, obtenha o total de pesquisas (a propriedade Bundle.total) da pré-obtenção mais recente na camada de colocação em cache. Esta é a melhor estimativa atual do total de pesquisas. Apresentar o total da pesquisa ao utilizador, indicando que se trata de uma estimativa. Determine a frequência desta atualização com base no seu exemplo de utilização.

    • Reconhecer quando o total de pesquisas da camada de colocação em cache está correto. Isto é, o total da pesquisa é da última página de resultados da pesquisa. Quando a última página de resultados da pesquisa é alcançada, a app apresenta o total da pesquisa e indica ao utilizador que o total da pesquisa está correto. Em seguida, a app deixa de receber os totais de pesquisa da camada de colocação em cache.

    Tenha em atenção que, com um grande número de correspondências, a obtenção prévia e o armazenamento em cache em segundo plano podem não alcançar a última página de resultados da pesquisa (e o total de pesquisas preciso) antes de o utilizador concluir a sessão de pesquisa.

Práticas recomendadas

  • Remova duplicados dos recursos incluídos. Se usar os parâmetros _include e _revinclude ao pré-obter e colocar em cache os resultados da pesquisa, recomendamos que remova as duplicidades dos recursos incluídos na cache após cada pré-obtenção. Isto ajuda a poupar memória, reduzindo o tamanho da cache. Quando agrupa correspondências em páginas de apps, adicione os recursos incluídos adequados a cada página de app. Para mais informações, consulte o artigo Incluir recursos adicionais nos resultados da pesquisa.

  • Defina um limite na obtenção prévia e no armazenamento em cache. Com um número muito grande de resultados da pesquisa, pode ser impraticável ou impossível pré-obtê-los todos. Recomendamos que defina um limite para o número de resultados da pesquisa a pré-obter. Isto mantém a cache num tamanho gerível e ajuda a poupar memória. Por exemplo, pode limitar o tamanho da cache a 10 000 ou 20 000 correspondências. Em alternativa, pode limitar o número de páginas a pré-obter ou definir um limite de tempo após o qual a pré-obtenção é interrompida. O tipo de limite que impõe e a forma como o impõe são decisões de design que dependem do seu exemplo de utilização. Se o limite for atingido antes de todos os resultados da pesquisa serem devolvidos, considere indicar isto ao utilizador, incluindo o facto de o total da pesquisa ser ainda uma estimativa.

Colocação em cache da interface

O frontend da aplicação, como um navegador de Internet ou uma app para dispositivos móveis, pode fornecer algum armazenamento em cache dos resultados da pesquisa como alternativa à introdução de uma camada de armazenamento em cache na arquitetura. Esta abordagem pode fornecer navegação para a página anterior ou para qualquer página no histórico de navegação, tirando partido das chamadas AJAX e armazenando os resultados da pesquisa e/ou os URLs de paginação. Seguem-se algumas vantagens desta abordagem:

  • Pode ser menos intensivo em recursos do que uma camada de colocação em cache.
  • É mais escalável, uma vez que distribui o trabalho de colocação em cache por muitos clientes.
  • É mais fácil determinar quando os recursos em cache já não são necessários, por exemplo, quando o utilizador fecha um separador ou sai da interface de pesquisa.

Práticas recomendadas gerais

Seguem-se algumas práticas recomendadas que se aplicam a todas as soluções neste documento.

Avalie os seus exemplos de utilização

A implementação de funcionalidades como navegar para qualquer página, obter totais de pesquisa precisos e atualizar os totais estimados aumenta a complexidade e os custos de desenvolvimento da sua app. Estas funcionalidades também podem aumentar a latência e os custos monetários de utilização dos Google Cloud recursos. Recomendamos que avalie cuidadosamente os seus exemplos de utilização para garantir que o valor destas funcionalidades justifica os custos. Seguem-se alguns aspetos a considerar:

  • Navegar para qualquer página. Normalmente, um utilizador não precisa de navegar para uma página específica, mas sim para muitas páginas a partir da página atual. Na maioria dos casos, navegar para uma página próxima é suficiente.

  • Totais de pesquisa precisos. Os totais da pesquisa podem mudar à medida que os recursos na loja FHIR são criados, atualizados e eliminados. Por este motivo, um total de pesquisa preciso é preciso no momento em que é devolvido (com a última página de resultados da pesquisa), mas pode não permanecer preciso ao longo do tempo. Por conseguinte, os totais de pesquisa precisos podem ter um valor limitado para a sua app, consoante o seu exemplo de utilização.