---
{
  "title": "Ein FHIR-Store, zwei Versionen: R4 und R6 mit FML bereitstellen",
  "description": "Wir haben einen FHIR-Server entwickelt, der R4 einmalig speichert und sowohl R4 als auch R6 über die FHIR Mapping Language bereitstellt. Die Werkzeuge haben funktioniert. Der schwierige Teil war das Modell.",
  "date": "2026-11-08",
  "author": "Aleksandr Kislitsyn",
  "reading-time": "17 min read",
  "tags": ["FHIR Standard", "FHIR Tools", "System Design", "Health Samurai Lab"],
  "tldr": "R4 und R6 werden noch jahrelang nebeneinander betrieben werden, und beim FHIR Camp 2026 war den Betroffenen klar: Sie wollen einen einzigen Datensatz, keine Kopie pro Version. Daher haben wir einen Server entwickelt, der FHIR R4 einmalig speichert und R6 über FML-Maps in beide Richtungen bereitstellt. Patient wurde unverändert konvertiert. Encounter erforderte Umbenennungen, Status-Übersetzungen und versionsübergreifende Extensions. Die Suche benötigte einen zweiten Parameter. BodyStructure musste in mehrere R4-Ressourcen aufgeteilt werden, was Referenzen brach, bis wir das Problem behoben hatten. Die Werkzeuge haben ihre Aufgabe erfüllt. Jede schwierige Entscheidung betraf das Modell.",
  "utm-campaign": "fhir_expert",
  "utm-content": "r4-r6-fml",
  "hero-image": "architecture.svg",
  "hero-image-alt": "R4 clients read and write a PostgreSQL store of FHIR R4 directly. R6 clients go through an FML converter that maps R4 to R6 on reads and R6 to R4 on writes.",
  "hero-image-caption": "Ein R4-Store, zwei APIs. R6 wird bei jeder Anfrage aus dem gespeicherten R4 berechnet.",
  "hero-image-position": "before-tldr"
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

## Warum ein Server für zwei Versionen

R6 befindet sich in seinen abschließenden Abstimmungsrunden, und die Frage hat sich verändert. Es geht nicht mehr darum, „was ist neu in R6", sondern „wie lange werden wir es neben R4 betreiben". Alles, was wir wissen, deutet darauf hin: jahrelang.

- **Regulatorische Vorgaben fixieren Versionen.** Die US-amerikanische Zertifizierung (US Core) sowie die CMS-Interoperabilitätsregeln für Patient Access, Provider Access, Payer-to-Payer und Prior Authorization basieren alle auf R4. Neue Regeln werden mit Übergangszeiträumen kommen, nicht mit einem festen Umstellungsdatum. Andere Jurisdiktionen folgen ihren eigenen Zeitplänen.
- **Partner rüsten nicht gemeinsam auf.** Wenn Sie eine Plattform, ein HIE oder ein Kostenträger sind, erhalten Sie die Vereinigung aller Versionen Ihrer Partner.
- **Lange Ausläufer sind normal.** Epic stellt DSTU2, STU3 und R4 jeweils unter einer eigenen Basis-URL bereit. Oracle Health soll DSTU2 etwa sechs Jahre lang neben R4 betrieben haben, bevor es 2025 eingestellt wurde.
- **R5 wurde größtenteils übersprungen.** Produktivsysteme blieben auf R4 und übernahmen die benötigten R5-Features – etwa themenbasierte Subscriptions – über R4-Backport-IGs. Der realistische Sprung ist also R4 direkt zu R6, mit allen Änderungen von R4 → R5 und R5 → R6 auf einmal.
- **Daten überdauern Versionen.** Heute geschriebene Datensätze werden in zehn oder zwanzig Jahren gelesen. Archive und Prüfpfade werden nicht neu geschrieben.

Die Änderungen sind auch nicht kosmetischer Natur. Einige, auf die wir gestoßen sind:

| R4 | R6 | Warum es problematisch ist |
|---|---|---|
| `Encounter.class`: ein `Coding` | `Encounter.class`: eine Liste von `CodeableConcept` | Mehrere Klassen passen nicht in einen Wert |
| `Encounter.status` `finished`, `onleave`, `arrived` | `completed`, `on-hold`, `in-progress` + `subjectStatus` | Unterschiedliche Codes, keine Eins-zu-eins-Entsprechung |
| `Encounter.period`, `hospitalization`, `participant.individual` | `actualPeriod`, `admission`, `participant.actor` | Umbenennungen |
| `BodyStructure.location` und `morphology`: je einer | `BodyStructure.includedStructure`: einer oder mehr | Eine R6-Ressource kann beschreiben, wofür R4 mehrere Ressourcen benötigt |
| `Observation.bodySite`: ein Code | ergänzt `Observation.bodyStructure`: eine Referenz | R4 hat kein Element, das den Link aufnehmen kann |

Die Koexistenz ist also gegeben. Die eigentliche Frage lautet, wie man sie **innerhalb eines einzigen Systems** umsetzt.

## Was die Runde beim FHIR Camp sagte

Beim [HL7 FHIR Camp 2026](/events/fhir-camp-2026) in Cascais, Portugal, leitete ich eine Birds-of-a-Feather-Session mit dem Titel *Koexistenz mehrerer FHIR-Versionen im selben System*. Die Frage: Soll ein System mehrere Versionen aus einem einzigen Datensatz mit Konvertern bereitstellen, eine separate Kopie pro Version vorhalten oder jede Ressource in der Version speichern, in der sie eingegangen ist?

Die Antwort der Runde war eindeutig: **Die Teilnehmer wollen einen einzigen Datensatz.** Die Gründe sind pragmatisch:

- **Eine einzige Quelle der Wahrheit.** Ein Patient existiert genau einmal. Mit einer Kopie pro Version laufen die Kopien auseinander, und jemand muss entscheiden, welche die richtige ist.
- **Keine Konflikte zwischen Versionen.** Wenn eine R4-Kopie und eine R6-Kopie beide Schreibzugriffe akzeptieren, können zwei Clients denselben Datensatz gleichzeitig über verschiedene Versionen ändern. Die Rücksynchronisierung führt zu Konflikten, Reihenfolge­problemen und Merge-Regeln. Mit einem einzigen Store geht jeder Schreibvorgang in dieselbe Zeile, und `If-Match` funktioniert unabhängig davon, welche Version der Client verwendet.
- **Ein Verlauf und ein Prüfpfad.** Versionen, Löschungen und Provenance werden einmal erfasst, nicht einmal pro Kopie.
- **Ein Ort für Suche und Analyse.** Abfragen müssen nicht wissen, in welcher Version eine Ressource eingegangen ist.

Das lässt nur ein Design übrig: einen einzigen Store, aus dem jede Version als Projektion davon bereitgestellt wird. Es konzentriert auch alle Schwierigkeiten an einem einzigen Ort: der Konvertierung. Sie muss in beide Richtungen funktionieren, für echte Daten, bei jeder Anfrage.

## Ein Hackathon-Projekt

Wir haben daraus ein Hackathon-Projekt gemacht, mit zwei Zielen: zu sehen, wie weit das Single-Dataset-Design trägt, und zu bewerten, ob die FHIR Mapping Language (FML) eine geeignete Mapping-Engine für diese Architektur ist.

Das Team: [Brian Postlethwaite](https://www.linkedin.com/in/brianpos/), [Artur Kurvits](https://www.linkedin.com/in/artur-kurvits-8a2467141/), [Patrick Werner](https://www.linkedin.com/in/wernerpatrick/), [Francisco Novillo Perez](https://www.linkedin.com/in/fnovillo/), [Vadim Peretokin](https://www.linkedin.com/in/vadimperetokin/), [Ralf Pohl](https://www.linkedin.com/in/ralf-pohl-6aa90b3/) und [Aleksandr Kislitsyn](https://www.linkedin.com/in/aleksandrkislitsyn/).

Ein besonderer Dank gilt [Gino Canessa](https://www.linkedin.com/in/gino-canessa/). Er war nicht Teil des Teams, teilte jedoch einige der schwierigsten Fälle – solche, die grundsätzlich schwer korrekt zu lösen sind.

## Das Setup

Der Server ist bewusst klein gehalten:

- **Ein Store, nur FHIR R4.** PostgreSQL 18, eine Tabelle pro Ressourcentyp plus eine Verlaufstabelle. Wir haben R4 gewählt, weil dort die heutigen Daten und Clients sind. Mit R6 als Store würden dieselben Konvertierungen einfach in die andere Richtung laufen.
- **Zwei APIs.** `/R4` liest und schreibt den gespeicherten JSON ohne Konvertierung. `/R6` liest, sucht, erstellt, aktualisiert und löscht über die FHIR Mapping Language (FML).
- **Die FML-Engine** ist Brian Postlethwaites FML-Executor aus dem [FHIRPath Lab](https://github.com/brianpos/fhirpath-lab). Er führt seine generierten R4 → R6-Maps unverändert aus, einschließlich aller Importe: 164 Maps. Die Richtung R6 → R4 umfasst 63 Maps: Datentypen umgekehrt aus den generierten Maps, plus handgeschriebene Maps für Patient, Encounter, BodyStructure und Observation.
- **Validierung** verwendet R4 4.0.1 und R6 6.0.0-ballot5, geladen mit `@atomic-ehr/fhir-canonical-manager` und geprüft mit `@atomic-ehr/fhirschema`.

Eine Regel hält alles zusammen: **Jeder Schreibvorgang, aus beiden Versionen, muss in beiden Versionen gültig sein und den Round-Trip R6 → R4 → R6 überstehen.**

Jede R6-Antwort trägt dieselbe `id` und `versionId` wie die gespeicherte R4-Ressource, sodass beide Versionen stets übereinstimmen, welchen Datensatz und welche Version sie betrachten.

## Patient: der einfache Fall

Patient ist seit R4 normativ und hat sich kaum verändert. Die generierte Map ist eine Feld-für-Feld-Kopie und funktioniert unverändert. R6 fügt drei Dinge zu `Patient.contact` hinzu: `role`, `additionalName` und `additionalAddress`. Auf dem Weg nach R4 werden sie in versionsübergreifende Extensions gespeichert und auf dem Weg nach R6 wieder herausgelesen.

Dieses Muster taucht im gesamten Projekt immer wieder auf, daher lohnt es sich, es einmal festzuhalten. FHIR definiert ein URL-Schema für **versionsübergreifende Extensions**, `http://hl7.org/fhir/[version]/StructureDefinition/extension-[element]`, für Daten, die in der anderen Version keinen Platz haben. `http://hl7.org/fhir/6.0/StructureDefinition/extension-Patient.contact.role` ist R6s `Patient.contact.role`, gespeichert in R4.

## Encounter: wo sich Versionen wirklich unterscheiden

Ein R6-Client erstellt einen Encounter mit zwei Klassen und dem Status „completed":

```json
POST /R6/Encounter

{
  "resourceType": "Encounter",
  "status": "completed",
  "class": [
    { "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
        "code": "AMB" }] },
    { "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
        "code": "VR" }] }
  ],
  "participant": [{ "actor": { "reference": "Practitioner/dr-smith" } }],
  "actualPeriod": { "start": "2026-11-06T09:00:00Z", "end": "2026-11-06T09:30:00Z" },
  "admission": { "admitSource": { "text": "Referral" } }
}
```

Die handgeschriebene `Encounter_6to4.fml` wandelt ihn in R4 um. Status-Codes folgen HL7s R5 → R4-ConceptMap, da R5 und R6 dieselben teilen. Die erste Klasse wird zum R4-`class`, jede weitere Klasse wird zu einer versionsübergreifenden Extension, und die umbenannten Elemente erhalten ihre R4-Namen zurück:

```fml
conceptmap "EncounterStatus" {
  s:"planned"   - t:"planned"
  s:"on-hold"   - t:"onleave"
  s:"completed" - t:"finished"
  …
}

group Encounter(source src : EncounterR6, target tgt : EncounterR4) extends DomainResource <<type+>> {
  src.status as v where ($this != 'in-progress' and $this != 'discharged')
    -> tgt.status = translate(v, '#EncounterStatus', 'code');
  src.class first as c -> tgt.class as k then EncounterFirstClass(c, k);
  src.class not_first as v -> tgt.extension as x,
    x.url = 'http://hl7.org/fhir/6.0/StructureDefinition/extension-Encounter.class',
    x.valueCodeableConcept as t then CodeableConcept(v, t);
  src.actualPeriod -> tgt.period;                       // renamed
  …
}
```

Das wird gespeichert und von einem R4-Client gelesen:

```json
{
  "resourceType": "Encounter",
  "status": "finished",
  "class": {
    "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
    "code": "AMB"
  },
  "extension": [{
    "url": "http://hl7.org/fhir/6.0/StructureDefinition/extension-Encounter.class",
    "valueCodeableConcept": {
      "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
        "code": "VR"
      }]
    }
  }],
  "participant": [{ "individual": { "reference": "Practitioner/dr-smith" } }],
  "period": { "start": "2026-11-06T09:00:00Z", "end": "2026-11-06T09:30:00Z" },
  "hospitalization": { "admitSource": { "text": "Referral" } }
}
```

Das Zurücklesen über `/R6` liefert exakt das, was der Client gesendet hat, zuzüglich `id` und `meta`. Die Round-Trip-Prüfung stellt das sicher, bevor der Schreibvorgang akzeptiert wird.

### Ist die Status-Übersetzung verlustbehaftet?

`translate()` allein ist nur dort sicher, wo die Codes eins zu eins übereinstimmen. R6 hat mehr Encounter-Status-Codes als R4, sodass einige davon zusammenfallen:

| R6-Status | R4-Status | Verlustbehaftet mit `translate()` allein? |
|---|---|---|
| `completed` | `finished` | Nein |
| `on-hold` | `onleave` | Nein |
| `discharged` | `finished` | Ja: `finished` wird als `completed` zurückgelesen |
| `discontinued` | kein R4-Code | Ja: nichts, wohin übersetzt werden könnte |

Bei einem Viele-zu-eins-Fall wie `discharged` speichert die Map den nächstgelegenen R4-Code **und bewahrt den ursprünglichen R6-Code in einer versionsübergreifenden Extension am Element selbst**. Das ist HL7s Regel für Required-Bindings mit zusätzlichen Codes:

```json
{
  "status": "finished",
  "_status": {
    "extension": [{
      "url": "http://hl7.org/fhir/6.0/StructureDefinition/extension-Encounter.status",
      "valueCode": "discharged"
    }]
  }
}
```

Ein R4-Client sieht einen gültigen R4-Status. Die R6-Richtung prüft zuerst die Extension, sodass `discharged` als `discharged` zurückkommt, nicht als `completed`. Das allgemeine Prinzip lautet also: Übersetzen, wo die Codes übereinstimmen, und wo nicht, den Originalwert neben dem übersetzten bewahren.

`discontinued` ist noch nicht gelöst. Es gibt keinen R4-Code dafür, daher wird ein Schreibvorgang, der ihn verwendet, vorerst abgelehnt, anstatt mit einem geraten Status gespeichert zu werden.

## Suche bedeutet in jeder Version etwas anderes

Der gespeicherte Encounter hat nun nativ die Klasse `AMB` und `VR` in einer Extension. Welche Suchen sollten ihn über `VR` finden?

![An R6 client sends class AMB and VR. R4 stores AMB natively and VR in the cross-version extension. R4 class matches only AMB; R4 class-r6 and R6 class match both.](encounter-class-search.svg "Dieselbe gespeicherte Zeile beantwortet drei Suchen unterschiedlich.")

Für R6 ist die Antwort einfach: `class` ist eine Liste, `VR` ist darin enthalten, also trifft `class=VR` zu. Die Frage ist, was R4-`class` tun soll.

**Option 1: R4-`class` die R6-Bedeutung geben.** Wir hätten den R4-Parameter auch in die versionsübergreifende Extension schauen lassen können. Dann würden beide Versionen für dieselbe Abfrage dieselben Encounters zurückgeben. Aber schauen Sie, was ein R4-Client zurückbekommt: Er fragt nach `class=VR` und erhält einen Encounter, dessen `class` `AMB` ist. Der gesuchte Wert befindet sich nicht in dem Feld, nach dem der Parameter benannt ist. Er steckt in einer Extension, die der Client möglicherweise gar nicht liest. Die Suche würde der Datenform widersprechen, die der Client sieht.

**Option 2: Die Standardbedeutung beibehalten.** R4-`class` trifft nur auf das, was in R4-`class` steht, findet den Encounter also über `AMB`, nicht über `VR`. Die Ergebnisse stimmen mit der Ressource überein, die der R4-Client erhält, und Standardparameter behalten ihre veröffentlichte Definition. Der Preis: Dieselben gespeicherten Daten beantworten dieselbe Frage unterschiedlich, je nachdem, über welche Version man fragt.

Wir haben Option 2 gewählt und einen **expliziten** Parameter für R4-Clients hinzugefügt, die das R6-Verhalten wünschen:

- **R6-`class`** trifft auf `AMB` und `VR`: die Standard-R6-Bedeutung.
- **R4-`class`** trifft nur auf `AMB`: die Standard-R4-Bedeutung.
- **R4-`class-r6`** trifft auf `AMB` und `VR`: die R6-Bedeutung, namentlich opt-in, gibt R4-Ressourcen zurück.

Ist das die beste Antwort? Ich bin nicht sicher, ob es eine gibt. Eine R4-App, die anzeigt, was in `class` steht, möchte Option 2. Ein Bericht, der alle virtuellen Besuche zählen muss, unabhängig davon, in welcher Version sie erfasst wurden, möchte die R6-Bedeutung. Jeder Implementierer muss das für seinen eigenen Anwendungsfall entscheiden. Wichtig ist, bewusst zu wählen und die Entscheidung für Clients sichtbar zu machen, anstatt sie in einem Standardparameter zu verstecken.

Wir haben `class-r6` als ordnungsgemäßen R4-SearchParameter in einem kleinen lokalen FHIR-Paket definiert. Der Canonical Manager lädt ihn neben R4-Core, der Server stellt ihn unter `/R4/SearchParameter/Encounter-class-r6` bereit, und das CapabilityStatement verweist darauf:

```json
{
  "resourceType": "SearchParameter",
  "url": "http://example.org/fhir/SearchParameter/Encounter-class-r6",
  "derivedFrom": "http://hl7.org/fhir/SearchParameter/Encounter-class",
  "code": "class-r6",
  "base": ["Encounter"],
  "type": "token",
  "expression": "Encounter.class | Encounter.class.extension('http://hl7.org/fhir/6.0/StructureDefinition/extension-Encounter.class').value | Encounter.extension('http://hl7.org/fhir/6.0/StructureDefinition/extension-Encounter.class').value"
}
```

Die Suche selbst läuft als SQL auf dem gespeicherten R4. FML konvertiert nur die Ergebnisse für `/R6`.

## BodyStructure: eine Ressource, mehrere Zeilen

BodyStructure ist der Punkt, an dem die beiden Modelle aufhören, aufeinander zu passen. In R6 ist `includedStructure` eine Liste: Eine BodyStructure kann eine Läsion über den Unterarm, den Oberarm und die Schulter beschreiben. R4 hat ein `location` und ein `morphology`.

Unsere Lösung – ein eigenes Design, nicht HL7s – besteht darin, eine R6-BodyStructure als **eine R4-Ressource pro Struktur** zu speichern, mit den IDs `abc`, `abc--2`, `abc--3`. Eine vom Server verwaltete Extension verknüpft sie: Die erste Zeile listet die anderen auf, und jede weitere Zeile verweist auf die erste zurück.

```json
GET /R4/BodyStructure/abc

{
  "resourceType": "BodyStructure",
  "id": "abc",
  "location": { "text": "Forearm" },
  "extension": [{
    "url": "http://example.org/fhir/StructureDefinition/bodystructure-row-group",
    "extension": [
      { "url": "position", "valuePositiveInt": 1 },
      { "url": "member", "valueReference": { "reference": "BodyStructure/abc--2" } },
      { "url": "member", "valueReference": { "reference": "BodyStructure/abc--3" } }
    ]
  }]
}
```

Der Rest ergibt sich daraus:

- **Ein Schreibvorgang schreibt die gesamte Zeilengruppe in einer Transaktion neu**, aus beiden Versionen.
- **Nur in R6 vorhandene Werte gehen in versionsübergreifende Extensions**, beispielsweise zusätzliche Morphologien, Lateralität und Landmarks.
- **Zeilen-IDs bleiben stabil**, und die `includedStructure.id` jeder Struktur in R6 ist ihre Zeilennummer.
- **R6 zeigt immer nur `abc`.** `GET /R6/BodyStructure/abc--2` ergibt einen `404`.

Es gibt eine andere Aufteilungsmöglichkeit, die möglicherweise die klarere ist. Jede Struktur nummerieren – `abc--1`, `abc--2`, `abc--3` – und gar keine R4-Ressource namens `abc` speichern. Ein Lesezugriff auf `GET /R4/BodyStructure/abc` gibt dann ein OperationOutcome zurück, das die echten IDs auflistet:

```json
GET /R4/BodyStructure/abc

{
  "resourceType": "OperationOutcome",
  "issue": [{
    "severity": "information",
    "code": "informational",
    "diagnostics": "BodyStructure/abc is stored in R4 as three resources: BodyStructure/abc--1, BodyStructure/abc--2, BodyStructure/abc--3"
  }]
}
```

Die Abwägungen verlaufen in beide Richtungen:

| | `abc`, `abc--2`, `abc--3` (was wir gebaut haben) | `abc--1`, `abc--2`, `abc--3` + OperationOutcome |
|---|---|---|
| Lesen von `abc` über R4 | Gibt die erste Struktur zurück, die wie das Ganze wirkt | Sagt klar, dass es aufgeteilt ist, und wo die Teile sind |
| Bestehende R4-Referenzen auf `abc` | Funktionieren weiter und zeigen auf die erste Struktur | Zeigen auf etwas, das nicht als BodyStructure lesbar ist |
| Entfernen der ersten Struktur | Die nächste rückt in `abc` nach, Referenzen müssen neu geschrieben werden | Nichts verschiebt sich: jede Struktur behält ihre ID |
| R4-Clients | Können die Aufteilung ignorieren, bis sie relevant wird | Müssen das OperationOutcome von Anfang an verstehen |

Wir haben uns dafür entschieden, `abc` als echte Ressource beizubehalten, hauptsächlich damit bestehende R4-Referenzen und Clients weiterhin funktionieren. Wie bei den Such-Semantiken bin ich auch hier nicht überzeugt, dass es eine einzig richtige Antwort gibt. Jedes System muss die Entscheidung anhand seiner eigenen Clients treffen.

## Referenzen auf eine aufgeteilte Ressource

Die Aufteilung einer Ressource gibt ihr zwei Identitäten: R4-Clients sehen Zeilen, R6-Clients sehen eine Ressource. Jede Ressource, die auf eine BodyStructure zeigt, muss nun zwischen beiden übersetzt werden.

In R6 verweist eine Observation über `bodyStructure` auf eine BodyStructure. R4 hat kein solches Element, daher trägt die Standard-Extension `http://hl7.org/fhir/StructureDefinition/bodySite` den Link. Da eine R6-BodyStructure mehrere R4-Zeilen sind, reicht ein einzelner Link nicht aus: In R4 ist `BodyStructure/abc` nur der Unterarm. Die R6 → R4-Map löst die Referenz auf und schreibt **einen Link pro Zeile**:

![Writing through R6, one bodyStructure reference is resolved and stored as one bodySite link per R4 row. Reading through R6, the links merge back into one reference. Rules: keep, repoint, refuse.](bodystructure-references.svg "Eine R6-Referenz wird zu einem R4-Link pro Zeile – und zurück.")

```fml
bs.reference as r where (reference.startsWith('BodyStructure/'))
  -> evaluate(r, resolve() as BodyStructure) as row
  then BodyStructureRows(row, t);

group BodyStructureRows(source row : BodyStructure, target t) {
  row.extension as g where (url = '…/bodystructure-row-group') then {
    g.extension as m where (url = 'member') then {
      m.value : Reference as member -> t.extension as x,
        x.url = 'http://hl7.org/fhir/StructureDefinition/bodySite',
        x.valueReference as v then Reference(member, v);
    };
  };
}
```

Auf dem Rückweg entfernt `Observation_4to6` das Suffix `--N`, und die Duplikate verschmelzen zur einzelnen Referenz, die der Client gesendet hat. Drei Server-Regeln sorgen für Konsistenz:

- **Beibehalten.** Hat ein R4-Client den Link auf eine Zeile eingeschränkt, bewahrt ein R6-Update diese Einschränkung, weil R6 sie nicht ausdrücken kann.
- **Neu ausrichten.** Wird die erste Struktur entfernt, rückt die nächste in die erste Zeile nach, und jede Observation, die auf die alte Zeile zeigte, wird in derselben Transaktion neu geschrieben.
- **Ablehnen.** Alles, was den Round-Trip R6 → R4 → R6 nicht übersteht – beispielsweise ein als Konzept angegebenes `bodyStructure` – wird mit `422` abgelehnt.

## Was das für bestehende R4-Clients bedeutet

Ein einziger Datensatz bedeutet nicht, dass R4-Clients unberührt bleiben. Sie werden wahrscheinlich einige Änderungen benötigen:

- **Ein Lesezugriff kann unvollständig sein.** `GET /R4/BodyStructure/abc` gibt nur die erste Struktur zurück. Die übrigen befinden sich in Zeilen, die ein Client nur über die row-group-Extension findet.
- **R6-Daten kommen als Extensions an.** Zusätzliche Encounter-Klassen und `statusReason` bei Observations zum Beispiel sind versionsübergreifende Extensions. Ein Client, der sie ignoriert, verpasst Daten; einer, der sie bei einem Update entfernt, verlässt sich darauf, dass der Server sie vorwärts trägt (unser Server tut das).
- **Suchen finden weniger.** R4-`class` trifft nicht auf eine Klasse, die nur in R6 existiert. Der Client muss von `class-r6` wissen.
- **Daten ändern sich unter ihnen.** Referenzen zeigen auf Zeilen, die sich verschieben können, und der Server schreibt Observations neu, wenn das passiert. Versionen ändern sich ohne Schreibvorgang des Clients, sodass `If-Match`-Konflikte wahrscheinlicher werden.

## Erkenntnisse

### 1. Es ist eher eine Modellierungsaufgabe als eine technische

Die technische Seite hat gehalten. Eine FML-Engine führte die generierten R4 → R6-Maps unverändert aus, und handgeschriebene Maps deckten Umbenennungen, Code-Übersetzungen, in einen einzigen Wert gefaltete Listen plus Extensions sowie das Aufteilen einer Ressource in mehrere ab. FML ist eine geeignete Mapping-Engine für diese Architektur. Der Canonical Manager, fhirschema und PostgreSQL haben ihre Aufgaben erfüllt.

Die schwierigen Fragen betrafen alle das Modell, und die Spezifikation wird sie Ihnen nicht beantworten. R4 und R6 bieten unterschiedliche Formen für dieselben Daten: eine Klasse oder eine Liste von Klassen, eine Körperstruktur oder mehrere, ein codierter Körperstelle oder eine Referenz. Die Entscheidung, wie eine Form in die andere überführt wird, hängt davon ab, was die Daten in Ihrem System bedeuten – weshalb Ihr eigenes fachliches Modellieren letztlich in den Mappings landet. Keine Version der Spezifikation wird diese Entscheidungen für Sie treffen.

### 2. Das Modell muss änderbar bleiben

Diese Entscheidungen werden sich ändern. HL7s versionsübergreifende Arbeit wird Konventionen veröffentlichen, Ihre Clients werden Rückmeldung geben, und einige Entscheidungen werden sich als falsch herausstellen. Daher müssen die Regeln sichtbar und umkehrbar sein, nicht in Code vergraben: Maps statt handgeschriebener Konvertierungen, SearchParameter als veröffentlichte Definitionen und eine dokumentierte Liste, was wohin gehört.

Es gibt einen Haken: Da der Store R4 ist, **ist jede Modellierungsentscheidung auch ein Speicherformat**. Wir haben die URL der Encounter-class-Extension einmal während des Hackathons geändert, und bereits unter der alten URL gespeicherte Encounters verloren ihre zusätzliche Klasse in der R6-Ansicht stillschweigend. Eine Entscheidung rückgängig zu machen bedeutet, die darunter geschriebenen Daten zu migrieren – planen Sie das von Anfang an ein.

### 3. R4-Clients kommen nicht ohne Änderungen davon

Es ist verlockend, versionsübergreifende Extensions als zusätzliche Daten zu betrachten, die ein R4-Client getrost ignorieren kann. Das sind sie nicht. Was in ihnen landet, ist oft der Kern des Datensatzes: der Link von einer Observation zu den Körperstrukturen, die sie beschreibt, die zweite Klasse eines Encounters, die Zeilen einer BodyStructure jenseits der ersten. Ein Client, der sie ignoriert, verpasst nicht nur Details. Er bekommt eine falsche Antwort, und seine Suchen verhalten sich anders als die R6-Suchen. Bestehende R4-Clients werden zumindest einige Änderungen benötigen, um die Daten korrekt zu lesen.

## Ausprobieren

- **Code, Maps und Tests:** [github.com/aleksandrkislitsyn/fml-r4-r6-fhir-server](https://github.com/aleksandrkislitsyn/fml-r4-r6-fhir-server). Die README erklärt, wie man es ausführt, und `docs/mappings.html` dokumentiert jede Mapping-Entscheidung.
- **Mehr zu R6:** [FHIR R6-Veröffentlichungsdatum und Neuigkeiten](/articles/getting-ready-for-fhir-r6-what-you-need-to-know), [FHIR R4 vs. R5 vs. R6](/articles/fhir-r4-vs-fhir-r5-choosing-the-right-version-for-your-implementation), [R6: Subtraktion durch zusätzliche Ressourcen](/articles/fhir-additional-resources) und [R6: Was passiert mit einer Referenz, wenn ihr Ziel in einen Inkubator wandert](/articles/fhir-r6-dangling-references).