Implementa la paginación y los totales de búsqueda con la búsqueda de FHIR

La implementación de búsqueda de FHIR de la API de Cloud Healthcare es muy escalable y eficaz, a la vez que sigue los lineamientos y limitaciones de REST y la especificación de FHIR. Para lograr esto, la búsqueda de FHIR tiene las siguientes propiedades:

  • El total de búsqueda es una estimación hasta que se muestra la última página. Los resultados de la búsqueda que muestra el método fhir.search incluyen el total de la búsqueda (la propiedad Bundle.total), que es la cantidad total de coincidencias en la búsqueda. El total de la búsqueda es una estimación hasta que se muestra la última página de resultados de la búsqueda. El total de la búsqueda que se muestra en la última página de resultados es una suma precisa de todas las coincidencias en la búsqueda.

  • Los resultados de la búsqueda proporcionan una paginación secuencial y directa. Cuando hay más resultados de la búsqueda para mostrar, la respuesta incluye una URL de paginación (Bundle.link.url) a fin de obtener la siguiente página de resultados.

Casos de uso básicos

La búsqueda de FHIR proporciona soluciones para los siguientes casos de uso:

Consulta las siguientes secciones a fin de obtener posibles soluciones para estos casos de uso.

Navega de forma secuencial

Puedes compilar una aplicación de baja latencia que le permita al usuario navegar hacia adelante de manera secuencial por las páginas de resultados hasta que encuentre la coincidencia que desea. Esta solución es posible si la cantidad de coincidencias es lo suficientemente pequeña como para que el usuario pueda encontrar la coincidencia deseada navegando página por página sin omitir resultados. Si tu caso de uso requiere que los usuarios naveguen hacia adelante varias páginas a la vez o retrocedan, consulta Cómo navegar a una página cercana.

Esta solución no puede proporcionar un total de búsqueda exacto hasta que se muestre la última página de resultados. Sin embargo, puede proporcionar un total aproximado de búsqueda con cada página de resultados. Si bien un total preciso de la búsqueda podría ser un requisito para los procesos en segundo plano, un total aproximado de la búsqueda suele ser adecuado para un usuario humano.

Flujo de trabajo

A continuación, se muestra un flujo de trabajo de ejemplo para esta solución:

  1. Una app llama al método fhir.search, que muestra la primera página de los resultados de la búsqueda. La respuesta incluye una URL de paginación (Bundle.link.url) si hay más resultados para mostrar. La respuesta también incluye el total de la búsqueda (Bundle.total). Si hay más resultados para mostrar que en la respuesta inicial, el total de la búsqueda es solo una estimación. Para obtener más información, consulta Paginación y orden.

  2. La app muestra la página de resultados de la búsqueda, un vínculo a la siguiente página de resultados (si corresponde) y el total de la búsqueda.

  3. Si el usuario desea ver la siguiente página de resultados, hace clic en el vínculo, lo que genera una llamada a la URL de paginación. La respuesta incluye una URL de paginación nueva si hay más resultados para mostrar. La respuesta también incluye el total de la búsqueda. Esta es una estimación actualizada si hay más resultados para mostrar.

  4. La app muestra la nueva página de resultados de la búsqueda, un vínculo a la siguiente página de resultados (si corresponde) y el total de la búsqueda.

  5. Los dos pasos anteriores se repiten hasta que el usuario deje de buscar o se muestre la última página de resultados.

Práctica recomendada

Elige un tamaño de página adecuado para que una persona pueda leer sin dificultad. Según tu caso de uso, podrían tener entre 10 y 20 coincidencias por página. Las páginas más pequeñas se cargan más rápido, y el exceso de vínculos en una página puede resultar difícil de administrar para el usuario. Puedes controlar el tamaño de la página con el parámetro _count.

Cómo procesar un conjunto de resultados de la búsqueda

Puedes obtener un conjunto de resultados de la búsqueda si realizas llamadas sucesivas al método fhir.search mediante la URL de paginación. Si la cantidad de resultados de la búsqueda es lo suficientemente pequeña, puedes obtener un conjunto completo de resultados si continúas hasta que no queden más páginas para mostrar. En la última página de resultados, se incluye un total preciso de la búsqueda. Después de obtener los resultados de la búsqueda, tu app puede leerlos y realizar el procesamiento, el análisis o la agregación que necesites.

Recuerda que, si tienes una gran cantidad de resultados de la búsqueda, es posible que no puedas obtener la última página de estos (ni el total exacto) en un período práctico.

Flujo de trabajo

A continuación, se muestra un flujo de trabajo de ejemplo para esta solución:

  1. La app llama al método fhir.search, que muestra la primera página de los resultados de la búsqueda. La respuesta incluye una URL de paginación (Bundle.link.url) si hay más resultados para mostrar.

  2. Si hay más resultados para mostrar, la app llama a la URL de paginación del paso anterior a fin de obtener la siguiente página de los resultados de la búsqueda.

  3. La app repite el paso anterior hasta que no haya más resultados para mostrar o hasta que se alcance algún otro límite predefinido. Si llegas a la última página de los resultados de la búsqueda, el total de la búsqueda es preciso.

  4. La app procesa los resultados de la búsqueda.

Según el caso de uso, la app puede realizar cualquiera de las siguientes acciones:

  • Espera hasta que se reciban todos los resultados de la búsqueda antes de procesar los datos.
  • Procesa los datos a medida que se reciben con cada llamada sucesiva a fhir.search.
  • Establece algún tipo de límite, como la cantidad de coincidencias que se muestran o la cantidad de tiempo transcurrido. Cuando se alcanza el límite, puedes procesar los datos, no procesarlos o realizar alguna otra acción.

Opciones de diseño

Estas son algunas opciones de diseño que podrían disminuir la latencia de búsqueda:

  • Configura un tamaño de página grande. Usa el parámetro _count para establecer un tamaño de página grande, quizás de 500 a 1,000, según tu caso de uso. Usar un tamaño de página más grande aumenta la latencia de cada recuperación de página, pero puede acelerar el proceso general, ya que se requieren menos recuperaciones de páginas para obtener todo el conjunto de resultados de la búsqueda.

  • Limita los resultados de la búsqueda. Si todo lo que necesitas es un total de búsqueda exacto (no es necesario que muestres el contenido del recurso), establece el parámetro _elements del método fhir.search en identifier. Esto puede disminuir la latencia de la consulta de búsqueda, en comparación con solicitar la devolución de los recursos de FHIR completos. Para obtener más información, consulta Limita los campos que se muestran en los resultados de la búsqueda.

Casos de uso que requieren carga previa y almacenamiento en caché

Es posible que necesites funciones más allá de las posibles si usas el mecanismo simple de llamar de forma sucesiva al método fhir.search mediante URLs de paginación. Una posibilidad es compilar una capa de almacenamiento en caché entre tu app y el almacén de FHIR que mantenga el estado de la sesión mientras se carga previamente y almacenan en caché los resultados de la búsqueda. La app puede agrupar los resultados de la búsqueda en pequeñas "páginas de la app" de 10 o 20 coincidencias. Luego, la app puede entregar rápidamente esas pequeñas páginas de la app al usuario de manera directa y no secuencial, según las selecciones que haga el usuario. Consulta Cómo navegar a una página cercana para ver un ejemplo de este tipo de flujo de trabajo.

Puedes compilar una solución de latencia baja que le permita al usuario recorrer una gran cantidad de resultados de la búsqueda hasta que encuentre la coincidencia que busca. Puede escalar a una cantidad prácticamente ilimitada de coincidencias mientras mantiene baja la latencia y genera un aumento relativamente pequeño en el consumo de recursos. Un usuario puede navegar directamente a una página de resultados de la búsqueda, hasta una cantidad predeterminada de páginas hacia adelante o hacia atrás desde la página actual. Puedes proporcionar un total estimado de la búsqueda con cada página de resultados. A modo de referencia, este diseño es similar al diseño de la Búsqueda de Google.

Flujo de trabajo

En la Figura 1, se muestra un flujo de trabajo de ejemplo para esta solución. Con este flujo de trabajo, cada vez que el usuario selecciona una página de resultados para ver, la app proporciona vínculos a páginas cercanas. En este caso, la app proporciona vínculos a páginas de hasta cinco páginas hacia atrás desde la página seleccionada y hasta cuatro páginas hacia delante desde la página seleccionada. A fin de que las cuatro páginas futuras estén disponibles para visualizarse, la app carga previamente 40 coincidencias adicionales cuando el usuario selecciona una página de resultados.

carga previa y caché

Figura 1. La app agrupa los resultados de la búsqueda en "páginas de la app" que se almacenan en caché y se ponen a disposición del usuario.

En la Figura 1, se ilustran estos pasos:

  1. El usuario ingresa una búsqueda en el frontend de la app y, luego, inicia las siguientes acciones:

    1. La app llama al método fhir.search para cargar previamente la primera página de los resultados de la búsqueda.

      El parámetro _count se establece en 100 para mantener relativamente pequeño el tamaño de la página de la respuesta, lo que da como resultado tiempos de respuesta relativamente rápidos. La respuesta incluye una URL de paginación (Bundle.link.url) si hay más resultados para mostrar. La respuesta también incluye el total de la búsqueda (Bundle.total). El total de la búsqueda es una estimación si hay más resultados para mostrar. Para obtener más información, consulta Paginación y ordenación.

    2. La app envía la respuesta a la capa de almacenamiento en caché.

  2. En la capa de almacenamiento en caché, la app agrupa las 100 coincidencias de la respuesta en 10 páginas de la app de 10 coincidencias cada una. La página de una app es una pequeña agrupación de coincidencias que la app puede mostrar al usuario.

  3. La app le muestra la página 1 de la app al usuario. La página 1 de la app incluye vínculos a las páginas 2 a 10 de la app y el total estimado de la búsqueda.

  4. El usuario hace clic en un vínculo a una página de app diferente (en este ejemplo, la página 10 de la app) inicia las siguientes acciones:

    1. La app llama al método fhir.search, mediante la URL de paginación que se mostró con la carga previa anterior, para cargar previamente la siguiente página de resultados de la búsqueda.

      El parámetro _count se establece en 40 para cargar previamente las siguientes 40 coincidencias de la búsqueda del usuario. Las 40 coincidencias corresponden a las siguientes cuatro páginas de la app que esta pone a disposición del usuario.

    2. La app envía la respuesta a la capa de almacenamiento en caché.

  5. En la capa de almacenamiento en caché, la app agrupa las 40 coincidencias de la respuesta en cuatro páginas de 10 coincidencias cada una.

  6. La app le muestra la página 10 al usuario. La página 10 de la app incluye vínculos a las páginas 5 a 9 de la app (las cinco páginas de la app anteriores a la 10) y vínculos a las páginas 11 a 14 de la app (las cuatro páginas de la app que siguen desde la página 10). La página 10 de la app también incluye el total estimado de la búsqueda.

Esta acción puede continuar mientras el usuario quiera seguir haciendo clic en vínculos a las páginas de la app. Ten en cuenta que, si el usuario retrocede desde la página actual de la app y ya tienes todas las páginas cercanas almacenadas en caché, puedes elegir no realizar una carga previa nueva, según tu caso de uso.

Esta solución es rápida y eficiente por los siguientes motivos:

  • Las cargas previas pequeñas y las páginas de apps aún más pequeñas se procesan rápidamente.
  • Los resultados de la búsqueda almacenados en caché reducen la necesidad de realizar varias llamadas para los mismos resultados.
  • El mecanismo permanece rápido, independientemente de la escala de la cantidad de resultados de la búsqueda.

Opciones de diseño

Estas son algunas opciones de diseño que debes considerar según tu caso de uso:

  • Tamaño de la página de la app. Las páginas de tu app pueden contener más de 10 coincidencias si se adapta a tu caso de uso. Ten en cuenta que las páginas más pequeñas se cargan más rápido, y que tener demasiados vínculos en una página puede ser difícil de administrar para el usuario.

  • Cantidad de vínculos a la página de la app. En el flujo de trabajo sugerido aquí, cada página de la app muestra nueve vínculos a otras páginas de la app: cinco vínculos a las páginas de la app que se dirigen directamente hacia atrás desde la página actual de la app y cuatro vínculos a las páginas directamente desde la página actual de la app. Puedes ajustar estos números para que se adapten a tu caso de uso.

prácticas recomendadas

  • Usa la capa de almacenamiento en caché solo cuando sea necesario. Si configuras una capa de almacenamiento en caché, úsala solo cuando tu caso de uso la requiera. Las búsquedas que no requieren la capa de almacenamiento en caché deben omitirla.

  • Reduce el tamaño de la caché. Para conservar recursos, puedes reducir el tamaño de la caché si borras definitivamente los resultados de la búsqueda anteriores y conservas las URLs de las páginas que usaste para obtenerlos. Luego, puedes reconstruir la caché según sea necesario llamando a las URLs de la página. Ten en cuenta que los resultados de varias llamadas a la misma URL de paginación pueden cambiar con el tiempo, ya que los recursos del almacén de FHIR se crean, actualizan y borran en segundo plano. Las decisiones de diseño que dependen de tu caso de uso son decidir si depurar definitivamente, cómo hacerlo y con qué frecuencia hacerlo.

  • Borra definitivamente la caché para una búsqueda determinada. Para conservar recursos, puedes quitar completamente de la caché los resultados de las búsquedas inactivas. Considera quitar primero las búsquedas inactivas más largas. Ten en cuenta que si una búsqueda borrada se vuelve a activar, es posible que se produzca un estado de error que obligue a la capa de almacenamiento en caché a reiniciar la búsqueda.

Si quieres que un usuario pueda navegar a cualquier página de los resultados de la búsqueda, no solo a las páginas cercanas a la página actual, puedes usar una capa de almacenamiento en caché similar a la que se describe en Cómo navegar a una página cercana. Sin embargo, para permitir que un usuario navegue a cualquier página de los resultados de la búsqueda de una app, deberás precargar y almacenar en caché todos los resultados de la búsqueda. Con un número relativamente pequeño de resultados de búsqueda, esto es posible. Con una gran cantidad de resultados de la búsqueda, puede ser poco práctico o imposible cargarlos todos. Incluso con una cantidad menor de resultados de la búsqueda, el tiempo que se demora en cargarlos previamente puede ser más largo de lo que espera un usuario.

Flujo de trabajo

Configura un flujo de trabajo similar a Cómo navegar a una página cercana, con esta diferencia clave: la app continúa precargando los resultados de la búsqueda en segundo plano hasta que se muestren todas las coincidencias o se alcance algún otro límite predefinido.

A continuación, se muestra un flujo de trabajo de ejemplo para esta solución:

  1. La app llama al método fhir.search para cargar previamente la primera página de resultados de la búsqueda del usuario. La respuesta incluye una URL de paginación (Bundle.link.url) si hay más resultados para mostrar. La respuesta también incluye el total de la búsqueda (Bundle.total). Esta es una estimación si hay más resultados para mostrar.

  2. La app agrupa las coincidencias de la respuesta en páginas de 20 coincidencias cada una y las almacena en la caché. La página de una app es una pequeña agrupación de coincidencias que la app puede mostrar al usuario.

  3. La app le muestra al usuario la primera página de la app. La página de la app incluye vínculos a las páginas almacenadas en caché de la app y el total estimado de la búsqueda.

  4. Si hay más resultados para mostrar, la app hace lo siguiente:

    • Llama a la URL de paginación que se mostró en la carga previa anterior para obtener la siguiente página de resultados de la búsqueda.
    • Agrupa las coincidencias de la respuesta en páginas de la app de 20 coincidencias cada una y las almacena en la caché.
    • Actualiza la página de la app que el usuario está viendo en ese momento con vínculos nuevos a las páginas de la app recién cargadas y almacenadas en caché.
  5. La app repite el paso anterior hasta que no haya más resultados para mostrar o hasta que se alcance algún otro límite predefinido. Se muestra un total preciso con la última página de resultados de la búsqueda.

Mientras la app realiza la carga previa y almacena en caché las coincidencias en segundo plano, el usuario puede hacer clic en vínculos que redirigen a páginas almacenadas en caché.

Opciones de diseño

Estas son algunas opciones de diseño que debes considerar según tu caso de uso:

  • Tamaño de la página de la app. Las páginas de tu app pueden contener más o menos de 20 coincidencias si se adapta a tu caso de uso. Ten en cuenta que las páginas más pequeñas se cargan más rápido y que tener demasiados vínculos en una página puede ser difícil de administrar para el usuario.

  • Actualiza el total de la búsqueda. Mientras tu app realiza una carga previa y almacena en caché los resultados de la búsqueda en segundo plano, puedes mostrarle al usuario totales de búsqueda cada vez más precisos. Para ello, configura tu app de modo que haga lo siguiente:

    • En un intervalo establecido, obtén el total de búsqueda (la propiedad Bundle.total) de la carga previa más reciente en la capa de almacenamiento en caché. Esta es la mejor estimación actual del total de la búsqueda. Muestra el total de búsqueda al usuario para indicar que es una estimación. Determina la frecuencia de esta actualización según tu caso de uso.

    • Reconoce cuándo el total de búsqueda de la capa de almacenamiento en caché es preciso. Es decir, el total de la búsqueda proviene de la última página de resultados de la búsqueda. Cuando se llega a la última página de los resultados de la búsqueda, la app muestra el total de la búsqueda y le indica al usuario que ese total es correcto. Luego, la app deja de obtener los totales de búsqueda desde la capa de almacenamiento en caché.

    Ten en cuenta que, con una gran cantidad de coincidencias, es posible que la carga previa en segundo plano y el almacenamiento en caché no lleguen a la última página de resultados de la búsqueda (ni al total de la búsqueda exacto) antes de que el usuario complete su sesión de búsqueda.

prácticas recomendadas

  • Anula el duplicado de los recursos incluidos. Si usas los parámetros _include y _revinclude cuando realizas una carga previa y almacenas en caché los resultados de la búsqueda, te recomendamos anular la duplicación de los recursos incluidos en la caché después de cada carga previa. Esto ayudará a ahorrar memoria al reducir el tamaño de la caché. Cuando agrupes las coincidencias en páginas de la app, agrega los recursos incluidos correspondientes a cada página de la app. Para obtener más información, consulta Cómo incluir recursos adicionales en los resultados de la búsqueda.

  • Establece un límite para la carga previa y el almacenamiento en caché. Con una gran cantidad de resultados de la búsqueda, puede ser poco práctico o imposible cargarlos todos. Te recomendamos que establezcas un límite en la cantidad de resultados de la búsqueda para la carga previa. Esto mantiene la caché en un tamaño administrable y ayuda a ahorrar memoria. Por ejemplo, podrías limitar el tamaño de la caché a 10,000 o 20,000 coincidencias. Como alternativa, puedes limitar la cantidad de páginas que se deben cargar previamente o establecer un límite de tiempo después del cual se detenga la carga previa. El tipo de límite y la forma en que lo impones son decisiones de diseño que dependen de tu caso de uso. Si se alcanza el límite antes de que se muestren todos los resultados de la búsqueda, considera indicárselo al usuario, incluso si el total de la búsqueda sigue siendo una estimación.

Almacenamiento en caché de frontend

El frontend de la aplicación, como un navegador web o una aplicación para dispositivos móviles, puede proporcionar almacenamiento en caché de los resultados de la búsqueda como alternativa a la introducción de una capa de almacenamiento en caché en la arquitectura. Este enfoque permite navegar a la página anterior o a cualquier página del historial de navegación, ya que aprovecha las llamadas AJAX y almacena los resultados de la búsqueda o las URLs de paginación. Estas son algunas de las ventajas de este enfoque:

  • Puede consumir menos recursos que una capa de almacenamiento en caché.
  • Es más escalable, ya que distribuye el trabajo de almacenamiento en caché entre muchos clientes.
  • Es más fácil determinar cuándo ya no se necesitan los recursos almacenados en caché, por ejemplo, cuando el usuario cierra una pestaña o sale de la interfaz de búsqueda.

Prácticas recomendadas generales

Estas son algunas de las prácticas recomendadas que se aplican a todas las soluciones de este documento.

  • Planifique para que las páginas sean inferiores al valor _count. En algunas circunstancias, una búsqueda puede mostrar páginas que contengan menos coincidencias que el valor _count que especificaste. Por ejemplo, esto puede suceder si especificas un tamaño de página particularmente grande. Si la búsqueda muestra una página menor que el valor _count y tu app usa una capa de almacenamiento en caché, es posible que debas decidir entre (1) mostrar menos resultados de los esperados en la página de una app o (2) recuperar algunos resultados más para obtener una página completa de la app. Para obtener más información, consulta Paginación y orden.

  • Reintenta los errores de solicitud HTTP que se pueden reintentar. Tu app debe esperar errores de solicitud HTTP que se pueden reintentar, como 429 o 500, y volver a intentarlo después de recibirlos.

Evalúa tus casos de uso

Implementar funciones como navegar a cualquier página, obtener totales de búsqueda precisos y actualizar los totales estimados aumenta la complejidad y los costos de desarrollo de la app. Estas funciones también pueden aumentar la latencia y los costos monetarios por el uso de los recursos de Google Cloud. Te recomendamos que evalúes con cuidado los casos de uso para asegurarte de que el valor de estas funciones justifique los costos. Estos son algunos aspectos que debes tener en cuenta:

  • Navegar a cualquier página. Por lo general, un usuario no necesita navegar a una página específica, a muchas páginas de la página actual. En la mayoría de los casos, es adecuado navegar a una página cercana.

  • Totales de búsqueda precisos. Los totales de búsqueda pueden cambiar a medida que se crean, actualizan y borran recursos en el almacén de FHIR. Por esta razón, un total preciso de la búsqueda es preciso en el momento en que se muestra (con la última página de los resultados de la búsqueda), pero es posible que no lo siga siendo con el tiempo. Por lo tanto, es posible que los totales de búsqueda precisos tengan un valor limitado para la app, según el caso de uso.