|
10 min de lectura
|

Gestión de paquetes FHIR: fijación de versiones, tree-shaking y por qué la resolución en tiempo de ejecución tuvo que desaparecer

Resumen del artículo

Aidbox resuelve ahora todas las versiones de canonicals en el momento de la configuración, no en tiempo de ejecución. La fijación de versiones bloquea cada referencia a una versión exacta, el tree-shaking elimina los canonicals no utilizados de las dependencias, y el resultado es un conjunto plano y completamente resuelto donde cada búsqueda es simplemente URL+versión.

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

El problema de la resolución en tiempo de ejecución

Antes de la versión 2511, Aidbox resolvía todas las referencias a canonicals en tiempo de ejecución. Cuando una StructureDefinition hace referencia a http://hl7.org/fhir/us/core/StructureDefinition/us-core-race sin versión, Aidbox tenía que determinar qué versión utilizar. E incluso cuando el resultado se almacenaba en caché, dicha caché era frágil. Instalar un nuevo paquete podía introducir nuevos canonicals, invalidar las cachés y hacer que la misma referencia se resolviera a una versión diferente.

Así es como se publican la mayoría de los paquetes FHIR. Los canonicals contienen referencias salientes hacia otros canonicals — ValueSets que apuntan a CodeSystems, perfiles que apuntan a StructureDefinitions base — y la gran mayoría de estas referencias no llevan versión. La suposición es que el consumidor deducirá la versión correcta a partir del contexto.

Esa suposición traslada la responsabilidad de la resolución de versiones al consumidor.

El problema real no era el rendimiento — era la falta de contexto. En tiempo de ejecución, lo único disponible es un conjunto plano de paquetes instalados y sus canonicals. Pero ¿qué paquetes instaló el usuario de forma explícita? ¿Cuáles son dependencias transitivas? ¿Qué depende de qué? Esa información se perdía efectivamente tras la instalación. Cada paquete era simplemente una bolsa de canonicals vertida en el mismo repositorio común. Como el resultado de la resolución dependía de lo que hubiera en ese repositorio, instalar o eliminar un paquete podía cambiar silenciosamente la versión a la que se resolvía una referencia. Partes del grafo de dependencias podían aparecer o desaparecer sin que el resolutor pudiera detectarlo.

Esto hacía muy difícil manejar correctamente escenarios de dependencias complejas. Consideremos hl7.fhir.us.davinci-hrex — incorpora tres versiones diferentes de hl7.fhir.us.core. Cuando un perfil de HRex hace referencia a http://hl7.org/fhir/us/core/StructureDefinition/us-core-provenance sin versión, ¿cuál de las tres versiones de US Core debería elegir Aidbox? Esto no puede deducirse de forma fiable en tiempo de ejecución — las tres están presentes simultáneamente y no queda información para desambiguar. Esta decisión debe tomarse antes de que el sistema arranque, no durante una llamada de validación.

Necesitábamos una forma de resolver todas estas ambigüedades de una sola vez, desde el principio, cuando el grafo completo de dependencias y la intención del usuario siguen disponibles — no en tiempo de ejecución, cuando ya han desaparecido.

La comunidad FHIR ya se venía moviendo en esta dirección. La guía de FHIR IG sobre fijación de versiones sentó las bases — la especificación describe el algoritmo de resolución previsto:

  1. Encontrar el paquete que contiene el recurso con la referencia
  2. Compilar la lista completa de dependencias de ese paquete (transitivas)
  3. Encontrar todos los recursos que coincidan con la URL canonical dentro de esos paquetes
  4. Seleccionar la versión más reciente

Esto parece sencillo, pero las complicaciones se acumulan rápidamente. «Más reciente» es ambiguo cuando se tienen cadenas de versión basadas en semver, en fechas y arbitrarias. Múltiples recursos pueden tener la misma URL y versión. Y en un contexto de API RESTful, los canonicals llegan sin metadatos de paquete — el servidor puede no saber qué paquete «posee» un canonical determinado en absoluto.

La especificación es deliberadamente escueta en estos detalles de implementación — deja mucho en manos de los implementadores. Por ello, realizamos ingeniería inversa de lo que pudimos a partir de las herramientas existentes que intentan la fijación de versiones — el IG Publisher y UploadFIG — y finalizamos la estrategia de resolución en el FHIR Camp 2025 en colaboración con Grahame Grieve, Gino Canessa y Lloyd McKenzie.

En Aidbox, esto da lugar a un enfoque en dos fases para el manejo de paquetes: todo el trabajo pesado ocurre antes del tiempo de ejecución real del servidor, y el tiempo de ejecución recibe un conjunto de canonicals plano y completamente resuelto.

Enfoque en dos fases: configuración y tiempo de ejecución

Paquetes FHIR Fase de configuraciónfijación + tree-shaking Fase de ejecuciónbúsqueda plana de canonicals

Fase de configuración. Cuando se instala un paquete — a través de la API, la interfaz de usuario o la configuración BOX_BOOTSTRAP_FHIR_PACKAGES — Aidbox descarga el paquete y todas sus dependencias, y luego recorre el grafo de dependencias completo. Cada referencia canonical sin versión se reescribe con un sufijo explícito |versión:

antes: http://hl7.org/fhir/ValueSet/observation-status
después: http://hl7.org/fhir/ValueSet/observation-status|4.0.1

Al mismo tiempo, los canonicals de los paquetes de dependencias a los que nadie hace referencia realmente se eliminan — esto es el tree-shaking.

Fase de ejecución. Todos los canonicals ya están resueltos. Cada referencia saliente lleva una versión explícita. Para Aidbox, resolver un canonical es ahora una simple consulta a la base de datos por <url>|<versión> — sin recorrido del grafo, sin resolución dependiente del contexto, sin ambigüedad. Los paquetes como concepto ya no existen en tiempo de ejecución; solo permanecen los canonicals completamente resueltos.

Cómo funciona la fijación de versiones en Aidbox

El proceso de fijación recorre cada canonical de los paquetes instalados y reescribe sus referencias salientes con versiones exactas.

Recopilación de referencias salientes

Para cada recurso canonical, Aidbox recopila todas las referencias canonicals salientes — URLs que apuntan a otros canonicals. Lo hace para los cinco tipos de recursos canonical que pueden contener referencias salientes:

  • StructureDefinition (excluyendo snapshot — solo importa differential)
  • CodeSystem
  • ValueSet
  • SearchParameter
  • CapabilityStatement

Por ejemplo, una StructureDefinition puede hacer referencia a un ValueSet para un binding, a una StructureDefinition base de la que deriva y a las definiciones de extensión que utiliza. Cada una de estas referencias necesita una versión fijada.

Construcción de la lista de candidatos

Para cada referencia saliente, Aidbox construye una lista de candidatos: todos los canonicals con la URL coincidente disponibles en el árbol de dependencias del paquete (incluidas las dependencias transitivas). Si un paquete depende de hl7.fhir.us.core@5.0.0 y US Core depende de hl7.terminology.r4, entonces los canonicals de terminología son candidatos válidos.

El algoritmo de selección de candidatos

Cuando existen múltiples candidatos — versiones diferentes, paquetes diferentes, misma URL canonical — Aidbox selecciona el mejor mediante una cadena de comparación determinista y de múltiples etapas:

PrioridadCriterioRegla
1Estadoactive > draft > retired > unknown
2Terminología sobre CoreLos canonicals de paquetes hl7.terminology tienen precedencia sobre los canonicals con el mismo nombre de los paquetes core
3VersiónComparada mediante el algoritmo detectado: semver, entero, fecha o alfabético
4lastUpdatedDesempate final usando meta.lastUpdated

Antes de la comparación, los candidatos se filtran. Aidbox excluye los canonicals de paquetes que coincidan con patrones de ruido conocidos — expansions, examples, search-related, elements, corexml — y los recursos CodeSystem cuyo content no sea "complete".

La detección del algoritmo de versión merece una nota. Aidbox comprueba primero versionAlgorithmString o versionAlgorithmCoding en el recurso (una característica de R5+). Si ninguno está presente — que es la mayoría de las veces con contenido R4 — Aidbox infiere el esquema inspeccionando la cadena de versión: ¿parece semver (1.2.3)? ¿Un entero (4)? ¿Una fecha (2024-01-15)? El método de reserva es la comparación alfabética. Esto coincide con el enfoque práctico recomendado en la guía de FHIR IG.

Fijación recursiva

La fijación es recursiva. Una vez que una referencia se fija a un canonical específico, las propias referencias salientes de ese canonical también se fijan — y así sucesivamente, a lo largo de toda la cadena de dependencias. Cada par <url>|<versión> se procesa solo una vez (con memoización), por lo que el proceso termina incluso con referencias circulares.

Este recorrido recursivo es también lo que impulsa el tree-shaking.

Tree-shaking: solo lo que se necesita

Un paquete FHIR típico contiene cientos o miles de canonicals. Pero cuando se instala un paquete, no se necesitan todos los canonicals de sus dependencias — solo los que se referencian realmente.

Consideremos la instalación de hl7.fhir.us.core@5.0.0. Depende de hl7.terminology.r4 (4.158 canonicals) y us.nlm.vsac (8.918 canonicals). Pero US Core solo referencia un subconjunto pequeño de cada uno. ¿Por qué cargar el resto?

El tree-shaking es la respuesta. Durante el recorrido recursivo de fijación, Aidbox rastrea qué canonicals de dependencias se alcanzan realmente siguiendo las referencias salientes del paquete objetivo. Solo estos canonicals alcanzables — y sus propias dependencias recursivas — forman parte del conjunto final de canonicals.

El resultado:

  • Paquete objetivo: todos los canonicals se incluyen (esto es lo que el usuario solicitó)
  • Paquetes de dependencias: solo se incluyen los canonicals referenciados (con tree-shaking aplicado)

La reducción es dramática. Compare los recuentos de installedCanonicals de una instalación real:

POST /fhir/$fhir-package-install
Content-Type: application/json

{
  "resourceType": "Parameters",
  "parameter": [
    { "name": "package", "valueString": "hl7.fhir.us.core@5.0.0" }
  ]
}

La respuesta muestra cuánto reduce el tree-shaking el recuento de canonicals. De los 4.158 canonicals de hl7.terminology.r4, solo 7 sobreviven. De los 8.918 de us.nlm.vsac, solo 20:

PaqueteCanonicals instaladosIntención
hl7.fhir.us.core@5.0.0194directo
us.nlm.vsac@0.3.020transitivo
hl7.fhir.uv.sdc@3.0.011transitivo
hl7.terminology.r4@3.1.07transitivo

Cada canonical en el sistema es único por <url>|<versión>. Sin duplicados, sin conflictos, sin ambigüedad.

Fuentes de paquetes

Aidbox admite tres formas de obtener paquetes:

npm file url Registro NPMfs.get-ig.org/pkgs, Simplifier, Verdaccio Sistema de archivos local/srv/aidbox-fhir-packages URLs directashttps:// o file:// Registro de artefactos de Aidbox

Registros NPM. El registro predeterminado es https://fs.get-ig.org/pkgs, que replica packages2.fhir.org con sincronizaciones diarias. Puede apuntar Aidbox a cualquier registro compatible con NPM — Simplifier, una instancia privada de Verdaccio o cualquier réplica personalizada.

Sistema de archivos local. Monte un directorio con paquetes .tgz en /srv/aidbox-fhir-packages en el contenedor de Aidbox. Los paquetes deben seguir la convención de nomenclatura {nombre}#{versión}.tgz — por ejemplo, hl7.fhir.r4.core#4.0.1.tgz. Aidbox comprueba los paquetes locales primero antes de acceder al registro remoto. Esto es esencial para entornos sin acceso a internet y también acelera significativamente el arranque.

URLs directas. Puede pasar una URL https:// que apunte a un tarball .tgz o una ruta file:// a un archivo local. Útil para instalar paquetes que no están publicados en ningún registro — como IGs personalizados compilados localmente con SUSHI.

Anulaciones de dependencias

¿Recuerda el problema de hl7.fhir.us.davinci-hrex mencionado anteriormente — tres versiones de US Core, imposibles de desambiguar en tiempo de ejecución? Con las anulaciones, el usuario toma esta decisión de forma explícita en el momento de la configuración.

HRex incorpora hl7.fhir.us.core@7.0.0, hl7.fhir.us.core.v610 y hl7.fhir.us.core.v311. Si solo necesita la versión 7.0.0, omita las otras dos:

POST /fhir/$fhir-package-install
Content-Type: application/json

{
  "resourceType": "Parameters",
  "parameter": [
    { "name": "package", "valueString": "hl7.fhir.us.davinci-hrex@1.1.0" },
    {
      "name": "override",
      "part": [
        { "name": "from", "valueString": "hl7.fhir.us.core.v610" },
        { "name": "to", "valueBoolean": false }
      ]
    },
    {
      "name": "override",
      "part": [
        { "name": "from", "valueString": "hl7.fhir.us.core.v311" },
        { "name": "to", "valueBoolean": false }
      ]
    }
  ]
}

Existen tres tipos de anulaciones:

AnulaciónSintaxisCaso de uso
Omitir dependencia"to": false (valueBoolean)Excluir una dependencia — como eliminar versiones de US Core no deseadas
Fijar versión"to": "0.17.0"Forzar una versión específica de una dependencia
Reemplazar paquete"to": "npm:alt.package@1.0.0"Sustituir un paquete por otro completamente

El campo from admite tanto coincidencia por nombre (hl7.fhir.us.core — coincide con cualquier versión) como coincidencia con versión calificada (hl7.fhir.us.core@6.1.0 — coincide solo con esa versión exacta). Las anulaciones con versión calificada tienen prioridad.

Las anulaciones también resuelven las brechas de los registros. hl7.fhir.us.core@8.0.0 depende de us.nlm.vsac@0.23.0, pero Simplifier dejó de alojar versiones de VSAC a partir de la 0.17.0. Fíjela a 0.17.0, omítala por completo o reemplácela con una alternativa — sin modificar los paquetes fuente.

¿Qué ocurre con la instalación en tiempo de ejecución?

¿Significa esto que se pierde la capacidad de instalar paquetes y canonicals en tiempo de ejecución? No — pero con algunas aclaraciones.

Los paquetes instalados en tiempo de ejecución siguen siendo fijados automáticamente. La diferencia es que la fijación ocurre en un contexto aislado: solo interviene el propio árbol de dependencias del paquete. El contenido ya instalado no es accesible durante este proceso de fijación, por lo que el nuevo paquete obtiene un conjunto de canonicals autoconsistente sin interferir con lo que ya está cargado.

Los canonicals individuales que se cargan directamente — fuera de cualquier paquete — son responsabilidad del usuario. Si se proporciona un canonical con referencias salientes sin versionar, Aidbox no intentará fijarlas por usted. Sin embargo, mantiene un modo de reserva para referencias no fijadas: utilizará el mismo algoritmo de selección de candidatos descrito anteriormente — estado, prioridad de terminología, comparación de versiones — para resolver al mejor candidato disponible. Por tanto, nada se rompe — simplemente no se obtienen las garantías de determinismo que proporciona la fijación a nivel de paquete.

Qué significa esto en la práctica

Esto es lo que esto le aporta en Aidbox:

Previsibilidad. La misma entrada de instalación de paquetes produce siempre el mismo conjunto de canonicals. No hay ambigüedad en tiempo de ejecución, ni comportamiento dependiente del orden, ni estado oculto que afecte a la resolución.

Rendimiento. Las búsquedas de canonicals son simples consultas a la base de datos por <url>|<versión>. Sin recorrido del grafo, sin recorrido de dependencias, sin heurísticas de caché.

Capacidad de depuración. Cada canonical en el sistema tiene una versión explícita. Cuando falla la validación, se ve exactamente qué versión de qué StructureDefinition se utilizó — no «lo que resultó resolverse en ese momento».

Huella menor. El tree-shaking significa que las dependencias transitivas solo contribuyen con los canonicals que se necesitan realmente. Instalar US Core incorpora 7 canonicals de hl7.terminology.r4 en lugar de 4.158.

La contrapartida es que la instalación tarda un poco más — Aidbox necesita recorrer el grafo completo de dependencias, construir listas de candidatos y fijar referencias recursivamente. Pero para el conjunto inicial de paquetes, esto ocurre solo una vez, en el primer arranque.

Referencias

Especificación:

Documentación de Aidbox:

Herramientas:

  • IG Publisher — editor oficial de guías de implementación de HL7
  • UploadFIG — herramienta de carga de canonicals de Brian Postlethwaite
  • SUSHI — compilador FHIR Shorthand para construir IGs personalizados
  • Verdaccio — proxy/registro NPM ligero para alojamiento privado

Eventos:

Compartir este artículo
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.