|
10 Min. Lesezeit
|

FHIR Package Management: Pinning, Tree-Shaking und warum die Laufzeitauflösung abgeschafft werden musste

Zusammenfassung

Aidbox löst nun alle kanonischen Versionen zur Konfigurationszeit auf – nicht zur Laufzeit. Pinning fixiert jede Referenz auf eine exakte Version, Tree-Shaking entfernt nicht verwendete Kanonicals aus Abhängigkeiten, und das Ergebnis ist ein flaches, vollständig aufgelöstes Set, bei dem jede Suche einfach über URL+Version erfolgt.

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Das Problem mit der Laufzeitauflösung

Vor dem Release 2511 löste Aidbox alle kanonischen Referenzen zur Laufzeit auf. Wenn eine StructureDefinition auf http://hl7.org/fhir/us/core/StructureDefinition/us-core-race ohne Versionsangabe verweist, musste Aidbox bestimmen, welche Version verwendet werden soll. Selbst wenn das Ergebnis gecacht war, blieb der Cache fragil. Die Installation eines neuen Pakets konnte neue Kanonicals einführen, Caches zurücksetzen und dazu führen, dass dieselbe Referenz zu einer anderen Version aufgelöst wurde.

So werden die meisten FHIR-Pakete veröffentlicht. Kanonicals enthalten ausgehende Referenzen auf andere Kanonicals – ValueSets verweisen auf CodeSystems, Profile auf Basis-StructureDefinitions – und die große Mehrheit dieser Referenzen enthält keine Version. Die Annahme ist, dass der Konsument die Version aus dem Kontext ableitet.

Diese Annahme überträgt die Verantwortung der Versionsauflösung auf den Konsumenten.

Das eigentliche Problem war nicht die Performance – es war der fehlende Kontext. Zur Laufzeit verfügt man nur über eine flache Menge installierter Pakete und deren Kanonicals. Aber welche Pakete hat der Benutzer explizit installiert? Welche sind transitive Abhängigkeiten? Welche hängen wovon ab? Diese Information ging nach der Installation faktisch verloren. Jedes Paket war lediglich ein Bündel von Kanonicals, das in denselben Pool geworfen wurde. Da das Auflösungsergebnis davon abhing, was sich zufällig in diesem Pool befand, konnte das Installieren oder Entfernen eines Pakets stillschweigend ändern, zu welcher Version eine Referenz aufgelöst wurde. Teile des Abhängigkeitsgraphen konnten auftauchen oder verschwinden, ohne dass der Resolver dies erkennen konnte.

Dadurch waren komplexe Abhängigkeitsszenarien schwierig korrekt zu behandeln. Nehmen Sie hl7.fhir.us.davinci-hrex – es zieht drei verschiedene Versionen von hl7.fhir.us.core ein. Wenn ein Profil aus HRex auf http://hl7.org/fhir/us/core/StructureDefinition/us-core-provenance ohne Versionsangabe verweist, welche der drei US Core-Versionen soll Aidbox wählen? Das lässt sich zur Laufzeit nicht zuverlässig ableiten – alle drei sind gleichzeitig vorhanden, und es gibt keine Information mehr zur Disambiguierung. Diese Entscheidung muss getroffen werden, bevor das System startet, nicht während eines Validierungsaufrufs.

Wir benötigten eine Möglichkeit, all diese Mehrdeutigkeiten einmalig im Voraus aufzulösen, wenn der vollständige Abhängigkeitsgraph und die Benutzerintention noch verfügbar sind – nicht zur Laufzeit, wenn beides nicht mehr vorhanden ist.

Die FHIR-Community bewegte sich bereits in diese Richtung. Die FHIR IG Guidance on Pinning legte das Fundament – die Spezifikation beschreibt den beabsichtigten Auflösungsalgorithmus:

  1. Das Paket finden, das die Ressource mit der Referenz enthält
  2. Die vollständige Liste der Abhängigkeiten dieses Pakets (transitiv) zusammenstellen
  3. Alle Ressourcen mit der kanonischen URL innerhalb dieser Pakete finden
  4. Die neueste Version auswählen

Das klingt unkompliziert, aber die Komplikationen häufen sich schnell. „Neueste" ist mehrdeutig, wenn Sie Semver-, datumsbasierte und beliebige Versionszeichenketten haben. Mehrere Ressourcen können dieselbe URL und Version besitzen. Und in einem RESTful-API-Kontext kommen Kanonicals ohne Paketmetadaten an – der Server weiß möglicherweise gar nicht, welches Paket ein bestimmtes Kanonical „besitzt".

Die Spezifikation ist bei diesen Implementierungsdetails bewusst knapp gehalten – sie überlässt vieles den Implementierern. Deshalb haben wir aus bestehenden Tools, die Pinning versuchen, so viel wie möglich nachvollzogen – dem IG Publisher und UploadFIG – und die Auflösungsstrategie beim FHIR Camp 2025 in Zusammenarbeit mit Grahame Grieve, Gino Canessa und Lloyd McKenzie finalisiert.

In Aidbox resultiert dies in einem zweiphasigen Ansatz zur Paketverarbeitung: Die gesamte aufwändige Arbeit findet vor der eigentlichen Serverlaufzeit statt, und die Laufzeit erhält ein flaches, vollständig aufgelöstes Kanonical-Set.

Zweiphasiger Ansatz: Konfiguration und Laufzeit

FHIR Packages Configuration Phasepinning + tree-shaking Runtime Phaseflat canonical lookup

Konfigurationsphase. Wenn Sie ein Paket installieren – über die API, die Benutzeroberfläche oder die BOX_BOOTSTRAP_FHIR_PACKAGES-Konfiguration – lädt Aidbox das Paket und alle seine Abhängigkeiten herunter und durchläuft dann den gesamten Abhängigkeitsgraphen. Jede unversionierte kanonische Referenz wird mit einem expliziten |version-Suffix neu geschrieben:

before: http://hl7.org/fhir/ValueSet/observation-status
after:  http://hl7.org/fhir/ValueSet/observation-status|4.0.1

Gleichzeitig werden Kanonicals aus Abhängigkeitspaketen, auf die niemand tatsächlich verweist, entfernt – das ist Tree-Shaking.

Laufzeitphase. Alle Kanonicals sind bereits aufgelöst. Jede ausgehende Referenz trägt eine explizite Version. Für Aidbox ist die Auflösung eines Kanonicals nun eine einfache Datenbankabfrage nach <url>|<version> – kein Graphdurchlauf, keine kontextabhängige Auflösung, keine Mehrdeutigkeit. Pakete als Konzept existieren zur Laufzeit nicht mehr; es verbleiben nur vollständig aufgelöste Kanonicals.

Wie Pinning in Aidbox funktioniert

Der Pinning-Prozess durchläuft jedes Kanonical in den installierten Paketen und schreibt seine ausgehenden Referenzen mit exakten Versionen neu.

Ausgehende Referenzen sammeln

Für jede kanonische Ressource sammelt Aidbox alle ausgehenden kanonischen Referenzen – URLs, die auf andere Kanonicals zeigen. Dies geschieht für alle fünf kanonischen Ressourcentypen, die ausgehende Referenzen enthalten können:

  • StructureDefinition (ausgenommen snapshot – nur differential ist relevant)
  • CodeSystem
  • ValueSet
  • SearchParameter
  • CapabilityStatement

Eine StructureDefinition kann beispielsweise für eine Bindung auf ein ValueSet, auf eine Basis-StructureDefinition, von der sie abgeleitet ist, und auf verwendete Extension-Definitionen verweisen. Jede dieser Referenzen benötigt eine gepinnte Version.

Die Kandidatenliste aufbauen

Für jede ausgehende Referenz erstellt Aidbox eine Kandidatenliste: alle Kanonicals mit der übereinstimmenden URL, die im Abhängigkeitsbaum des Pakets verfügbar sind (einschließlich transitiver Abhängigkeiten). Wenn ein Paket von hl7.fhir.us.core@5.0.0 abhängt und US Core von hl7.terminology.r4 abhängt, sind Terminology-Kanonicals gültige Kandidaten.

Der Algorithmus zur Kandidatenauswahl

Wenn mehrere Kandidaten existieren – verschiedene Versionen, verschiedene Pakete, dieselbe kanonische URL – wählt Aidbox den besten mithilfe einer deterministischen, mehrstufigen Vergleichskette aus:

PrioritätKriteriumRegel
1Statusactive > draft > retired > unknown
2Terminology vor CoreKanonicals aus hl7.terminology-Paketen haben Vorrang vor gleichnamigen Kanonicals aus Core-Paketen
3VersionVerglichen mit dem erkannten Algorithmus: Semver, Integer, Datum oder alphabetisch
4lastUpdatedAbschließendes Tiebreaker-Kriterium mit meta.lastUpdated

Vor dem Vergleich werden Kandidaten gefiltert. Aidbox schließt Kanonicals aus Paketen aus, die bekannten Rauschmustern entsprechen – Expansions, Beispiele, suchbezogene, Elements, Corexml – sowie CodeSystem-Ressourcen, deren content nicht "complete" ist.

Die Erkennung des Versionsalgorithmus verdient einen Hinweis. Aidbox prüft zunächst versionAlgorithmString oder versionAlgorithmCoding in der Ressource (ein R5+-Feature). Wenn beides nicht vorhanden ist – was bei R4-Inhalten meistens der Fall ist – leitet Aidbox das Schema durch Inspektion der Versionszeichenkette ab: Sieht sie wie Semver aus (1.2.3)? Wie ein Integer (4)? Wie ein Datum (2024-01-15)? Der Fallback ist alphabetischer Vergleich. Dies entspricht dem praktischen Ansatz, der in der FHIR IG Guidance empfohlen wird.

Rekursives Pinning

Pinning ist rekursiv. Sobald eine Referenz auf ein bestimmtes Kanonical gepinnt ist, werden auch die ausgehenden Referenzen dieses Kanonicals gepinnt – und so weiter, entlang der gesamten Abhängigkeitskette. Jedes <url>|<version>-Paar wird nur einmal verarbeitet (memoisiert), sodass der Prozess auch bei zirkulären Referenzen terminiert.

Dieser rekursive Durchlauf treibt auch das Tree-Shaking an.

Tree-Shaking: Nur was benötigt wird

Ein typisches FHIR-Paket enthält Hunderte oder Tausende von Kanonicals. Wenn Sie jedoch ein Paket installieren, benötigen Sie nicht alle Kanonicals aus seinen Abhängigkeiten – nur diejenigen, auf die tatsächlich verwiesen wird.

Betrachten Sie die Installation von hl7.fhir.us.core@5.0.0. Es hängt von hl7.terminology.r4 (4.158 Kanonicals) und us.nlm.vsac (8.918 Kanonicals) ab. Aber US Core referenziert nur eine kleine Teilmenge von jedem. Warum den Rest laden?

Tree-Shaking ist die Antwort. Während des rekursiven Pinning-Durchlaufs verfolgt Aidbox, welche Abhängigkeits-Kanonicals tatsächlich erreicht werden, indem es ausgehenden Referenzen aus dem Zielpaket folgt. Nur diese erreichbaren Kanonicals – und ihre eigenen rekursiven Abhängigkeiten – gelangen in das endgültige Kanonical-Set.

Das Ergebnis:

  • Zielpaket: Alle Kanonicals werden aufgenommen (das ist es, was der Benutzer angefordert hat)
  • Abhängigkeitspakete: Nur referenzierte Kanonicals werden aufgenommen (tree-shaken)

Die Reduzierung ist dramatisch. Vergleichen Sie die installedCanonicals-Zahlen aus einer realen Installation:

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

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

Die Antwort zeigt, wie drastisch Tree-Shaking die Anzahl der Kanonicals reduziert. Von 4.158 Kanonicals in hl7.terminology.r4 überleben nur 7. Von 8.918 in us.nlm.vsac nur 20:

PaketInstallierte KanonicalsIntention
hl7.fhir.us.core@5.0.0194direkt
us.nlm.vsac@0.3.020transitiv
hl7.fhir.uv.sdc@3.0.011transitiv
hl7.terminology.r4@3.1.07transitiv

Jedes Kanonical im System ist eindeutig durch <url>|<version>. Keine Duplikate, keine Konflikte, keine Mehrdeutigkeiten.

Paketquellen

Aidbox unterstützt drei Möglichkeiten, Pakete zu beziehen:

npm file url NPM Registryfs.get-ig.org/pkgs, Simplifier, Verdaccio Local Filesystem/srv/aidbox-fhir-packages Direct URLshttps:// or file:// Aidbox Artifact Registry

NPM-Registries. Die Standard-Registry ist https://fs.get-ig.org/pkgs, die packages2.fhir.org mit täglichen Synchronisierungen spiegelt. Sie können Aidbox auf jede NPM-kompatible Registry verweisen – Simplifier, eine private Verdaccio-Instanz oder einen beliebigen benutzerdefinierten Spiegel.

Lokales Dateisystem. Binden Sie ein Verzeichnis mit .tgz-Paketen unter /srv/aidbox-fhir-packages im Aidbox-Container ein. Pakete müssen der Namenskonvention {name}#{version}.tgz folgen – zum Beispiel hl7.fhir.r4.core#4.0.1.tgz. Aidbox prüft lokale Pakete zuerst, bevor es die Remote-Registry abfragt. Dies ist unerlässlich für luftgespaltene Umgebungen und beschleunigt außerdem den Start erheblich.

Direkte URLs. Sie können eine https://-URL übergeben, die auf ein .tgz-Tarball verweist, oder einen file://-Pfad zu einer lokalen Datei. Nützlich für die Installation von Paketen, die in keiner Registry veröffentlicht sind – wie benutzerdefinierte IGs, die lokal mit SUSHI erstellt wurden.

Abhängigkeits-Overrides

Erinnern Sie sich an das hl7.fhir.us.davinci-hrex-Problem von zuvor – drei Versionen von US Core, zur Laufzeit nicht disambiguierbar? Mit Overrides trifft der Benutzer diese Entscheidung explizit zur Konfigurationszeit.

HRex zieht hl7.fhir.us.core@7.0.0, hl7.fhir.us.core.v610 und hl7.fhir.us.core.v311 ein. Wenn Sie nur die Version 7.0.0 benötigen, überspringen Sie die anderen beiden:

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 }
      ]
    }
  ]
}

Drei Arten von Overrides:

OverrideSyntaxAnwendungsfall
Abhängigkeit überspringen"to": false (valueBoolean)Eine Abhängigkeit ausschließen – etwa unerwünschte US Core-Versionen entfernen
Version pinnen"to": "0.17.0"Eine bestimmte Version einer Abhängigkeit erzwingen
Paket ersetzen"to": "npm:alt.package@1.0.0"Ein Paket vollständig durch ein anderes ersetzen

Das from-Feld unterstützt sowohl die Übereinstimmung nur nach Name (hl7.fhir.us.core – entspricht jeder Version) als auch die versionsspezifische Übereinstimmung (hl7.fhir.us.core@6.1.0 – entspricht nur genau dieser Version). Versionsspezifische Overrides haben Vorrang.

Overrides lösen auch Registry-Lücken. hl7.fhir.us.core@8.0.0 hängt von us.nlm.vsac@0.23.0 ab, aber Simplifier hat VSAC-Versionen nach 0.17.0 nicht mehr gehostet. Pinnen Sie es auf 0.17.0, überspringen Sie es vollständig oder ersetzen Sie es durch eine Alternative – ohne die Quellpakete zu modifizieren.

Was ist mit der Laufzeitinstallation?

Bedeutet das, dass Sie die Möglichkeit verlieren, Pakete und Kanonicals zur Laufzeit zu installieren? Nein – aber mit einigen Klarstellungen.

Pakete, die zur Laufzeit installiert werden, werden weiterhin automatisch gepinnt. Der Unterschied besteht darin, dass Pinning in einem isolierten Kontext stattfindet: Nur der eigene Abhängigkeitsbaum des Pakets ist beteiligt. Bereits installierte Inhalte sind während dieses Pinning-Durchlaufs nicht erreichbar, sodass das neue Paket ein in sich konsistentes Kanonical-Set erhält, ohne das bereits Geladene zu beeinflussen.

Einzelne Kanonicals, die Sie direkt hochladen – außerhalb eines Pakets – liegen in Ihrer Verantwortung zu pinnen. Wenn Sie ein Kanonical mit unversionierten ausgehenden Referenzen bereitstellen, wird Aidbox nicht versuchen, diese für Sie zu pinnen. Es behält jedoch einen Fallback-Modus für ungepinnte Referenzen bei: Es verwendet denselben oben beschriebenen Kandidatenauswahlalgorithmus – Status, Terminology-Priorität, Versionsvergleich – um zur bestmöglichen verfügbaren Übereinstimmung aufzulösen. Es bricht also nichts – Sie erhalten lediglich nicht die Determinismusgarantien, die Pinning auf Paketebene bietet.

Was das in der Praxis bedeutet

Das bringt Ihnen Aidbox damit:

Vorhersagbarkeit. Dieselbe Paketinstallationseingabe erzeugt immer dasselbe Kanonical-Set. Es gibt keine Laufzeitmehrdeutigkeit, kein reihenfolgeabhängiges Verhalten, keinen versteckten Zustand, der die Auflösung beeinflusst.

Performance. Kanonical-Lookups sind einfache Datenbankabfragen nach <url>|<version>. Kein Graphdurchlauf, kein Abhängigkeitsdurchlauf, keine Caching-Heuristiken.

Debuggbarkeit. Jedes Kanonical im System hat eine explizite Version. Wenn die Validierung fehlschlägt, sehen Sie genau, welche Version welcher StructureDefinition verwendet wurde – nicht „was auch immer zu diesem Zeitpunkt aufgelöst wurde".

Kleinerer Footprint. Tree-Shaking bedeutet, dass transitive Abhängigkeiten nur die Kanonicals beisteuern, die tatsächlich benötigt werden. Die Installation von US Core zieht 7 Kanonicals aus hl7.terminology.r4 ein statt 4.158.

Der Kompromiss besteht darin, dass die Installation etwas länger dauert – Aidbox muss den vollständigen Abhängigkeitsgraphen durchlaufen, Kandidatenlisten erstellen und Referenzen rekursiv pinnen. Für den initialen Paketsatz geschieht dies jedoch nur einmal, beim ersten Start.

Referenzen

Spezifikation:

Aidbox-Dokumentation:

Tools:

  • IG Publisher – HL7's offizieller Implementation Guide Publisher
  • UploadFIG – Brian Postlethwaites Tool zum Hochladen von Kanonicals
  • SUSHI – FHIR Shorthand-Compiler zum Erstellen benutzerdefinierter IGs
  • Verdaccio – leichtgewichtiger NPM-Proxy/Registry für privates Hosting

Veranstaltungen:

Diesen Artikel teilen
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

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