---
{
  "title": "FHIR Package Management: Pinning, Tree-Shaking und warum die Laufzeitauflösung abgeschafft werden musste",
  "description": "Wie Aidbox die kanonische Auflösung von der Laufzeit in die Konfigurationszeit verlagert hat – mit Pinning, Tree-Shaking und einem deterministischen Algorithmus zur Kandidatenauswahl.",
  "date": "2026-04-03",
  "author": "Evgeny Mukha",
  "reading-time": "10 min read",
  "tags": ["FHIR Standard", "Aidbox", "Infrastructure"],
  "tldr": "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."
}
---
## 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](https://build.fhir.org/ig/FHIR/ig-guidance/pinning.html) 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](https://github.com/HL7/fhir-ig-publisher) und [UploadFIG](https://github.com/brianpos/UploadFIG) – und die Auflösungsstrategie beim **[FHIR Camp 2025](https://www.health-samurai.io/events/fhir-camp-2025)** in Zusammenarbeit mit [Grahame Grieve](https://www.linkedin.com/in/grahame-grieve-952637), [Gino Canessa](https://www.linkedin.com/in/gino-canessa/) und [Lloyd McKenzie](https://www.linkedin.com/in/lloyd-mckenzie-6b6681/) 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

```mermaid
flowchart LR
    A(FHIR Packages):::blue2 --> B(Configuration Phase<br/>pinning + tree-shaking):::green2 --> C(Runtime Phase<br/>flat canonical lookup):::violet2
```

**Konfigurationsphase.** Wenn Sie ein Paket installieren – über die API, die Benutzeroberfläche oder die [`BOX_BOOTSTRAP_FHIR_PACKAGES`](https://docs.aidbox.app/reference/all-settings#bootstrap-fhir-package-list)-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ät | Kriterium | Regel |
|-----------|-----------|-------|
| 1 | **Status** | `active` > `draft` > `retired` > `unknown` |
| 2 | **Terminology vor Core** | Kanonicals aus `hl7.terminology`-Paketen haben Vorrang vor gleichnamigen Kanonicals aus Core-Paketen |
| 3 | **Version** | Verglichen mit dem erkannten Algorithmus: Semver, Integer, Datum oder alphabetisch |
| 4 | **lastUpdated** | Abschließ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](/articles/how-to-build-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:

```http
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:

| Paket | Installierte Kanonicals | Intention |
|-------|------------------------|-----------|
| `hl7.fhir.us.core@5.0.0` | 194 | direkt |
| `us.nlm.vsac@0.3.0` | 20 | transitiv |
| `hl7.fhir.uv.sdc@3.0.0` | 11 | transitiv |
| `hl7.terminology.r4@3.1.0` | 7 | transitiv |

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:

```mermaid
flowchart RL
    A(NPM Registry<br/>fs.get-ig.org/pkgs, Simplifier, Verdaccio):::violet2
    B(Local Filesystem<br/>/srv/aidbox-fhir-packages):::green2
    C(Direct URLs<br/>https:// or file://):::yellow1

    E(Aidbox Artifact Registry):::blue2

    A -->|npm| E
    B -->|file| E
    C -->|url| E
```

**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](https://packages.simplifier.net), eine private [Verdaccio](https://verdaccio.org/)-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](https://fshschool.org/docs/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:

```http
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:

| Override | Syntax | Anwendungsfall |
|----------|--------|----------------|
| **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:**
- [FHIR IG Guidance: Managing Canonical Versions (Pinning)](https://build.fhir.org/ig/FHIR/ig-guidance/pinning.html)
- [FHIR NPM Package Specification](https://build.fhir.org/packages.html)

**Aidbox-Dokumentation:**
- [Artifact Registry Overview](https://docs.aidbox.app/modules-1/profiling-and-validation/fhir-schema-validator)
- [FAR Package Management API](https://docs.aidbox.app/reference/package-registry-api)

**Tools:**
- [IG Publisher](https://github.com/HL7/fhir-ig-publisher) – HL7's offizieller Implementation Guide Publisher
- [UploadFIG](https://github.com/brianpos/UploadFIG) – Brian Postlethwaites Tool zum Hochladen von Kanonicals
- [SUSHI](https://fshschool.org/docs/sushi/) – FHIR Shorthand-Compiler zum Erstellen benutzerdefinierter IGs
- [Verdaccio](https://verdaccio.org/) – leichtgewichtiger NPM-Proxy/Registry für privates Hosting

**Veranstaltungen:**
- [FHIR Camp 2025](https://www.health-samurai.io/events/fhir-camp-2025) – wo die Auflösungsstrategie finalisiert wurde