|
8 Min. Lesezeit
|

FHIR-Validierung: Rekursion oder keine Rekursion

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Wie alles begann

WÀhrend der Arbeit am Aidbox FHIR-Validator erhielten wir eine Frage von einem Kunden: Warum schlÀgt die Validierung bei verschachtelten Questionnaire.item-Elementen fehl, wenn die EinschrÀnkung nur auf dem obersten item definiert ist?

Wir wendeten ProfilbeschrĂ€nkungen rekursiv an – wenn eine EinschrĂ€nkung auf Questionnaire.item definiert war, erzwang unser Validator diese auch fĂŒr item.item, item.item.item und so weiter. Das schien das Richtige zu sein. Schließlich heißt es in der R5-Spezifikation:

Wenn ein Profil EinschrĂ€nkungen fĂŒr solche Elemente definiert, gelten diese EinschrĂ€nkungen auch fĂŒr die rekursiven Verweise auf diese Elemente.

Der Fall des Kunden veranlasste uns jedoch, genauer hinzusehen. Und was wir fanden, war ein echter Widerspruch in der Spezifikation.

Der SDC-Fall

Das SDC-Questionnaire-Profil definiert die Invariante sdc-1 auf Questionnaire.item:

Ein Element darf keine answerExpression haben, wenn bereits answerOption oder answerValueSet vorhanden ist.

Betrachten Sie diesen Questionnaire, bei dem sowohl ein Element der obersten Ebene als auch ein verschachteltes Element gegen sdc-1 verstoßen:

{
  "resourceType": "Questionnaire",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire"
    ]
  },
  "url": "http://example.org/test",
  "status": "active",
  "item": [
    {
      "linkId": "1",
      "type": "decimal",
      "item": [
        {
          "linkId": "1.1",
          "type": "decimal",
          "answerValueSet": "http://example.org/vs",
          "extension": [
            {
              "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "1 + 1"
              }
            }
          ]
        }
      ]
    },
    {
      "linkId": "2",
      "type": "decimal",
      "answerValueSet": "http://example.org/vs",
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
          "valueExpression": {
            "language": "text/fhirpath",
            "expression": "1 + 1"
          }
        }
      ]
    }
  ]
}

Unser Aidbox-Validator meldete Fehler bei beiden Elementen. Der HL7-Referenzvalidator auf validator.fhir.org meldete nur das Element der obersten Ebene. Wer hat recht?

Analyse des Profils

Ein Blick auf die StructureDefinition des SDC-Profils macht die Antwort weniger eindeutig. Das Differential erwĂ€hnt Questionnaire.item.item explizit, setzt dort jedoch nur mustSupport: true – keine EinschrĂ€nkungen, keine Invarianten:

{
  "id": "Questionnaire.item.item",
  "path": "Questionnaire.item.item",
  "mustSupport": true
}

Ob der Profilautor beabsichtigte, dass EinschrĂ€nkungen nur auf der obersten Ebene gelten, oder davon ausging, dass sdc-1 automatisch auf verschachtelte Elemente angewendet wird, ist unklar – es ist durchaus möglich, dass er annahm, sdc-1 wĂŒrde automatisch auf verschachtelte Elemente kaskadieren. In jedem Fall schrĂ€nkt das Differential Questionnaire.item.item nicht explizit ein, und der HL7-Validator erzwingt nur das, was das Differential vorgibt.

Aus Sicht der Spezifikation war unser Verhalten jedoch ebenfalls korrekt – EinschrĂ€nkungen sollten rekursiv angewendet werden.

Warum automatische Rekursion reale Profile bricht

Ich habe dies mit Grahame Grieve auf chat.fhir.org besprochen, und seine Reaktion war unmittelbar.

Betrachten Sie das IPS-Composition-Profil. Es schrÀnkt Composition.section mit einer Mindest-KardinalitÀt von 3..* ein. Wenn wir dies rekursiv anwenden:

  • Composition.section – muss 3 oder mehr Abschnitte haben
  • Composition.section.section – muss ebenfalls 3 oder mehr Abschnitte haben
  • Composition.section.section.section – und so weiter, unendlich

Kein gĂŒltiges IPS-Dokument könnte jemals existieren. Grahames Reaktion auf den Spezifikationstext:

„Das ist völlig falsch, wenn es um Composition-Profile geht. Damit ist es absolut unmöglich zu arbeiten. Ich weiß nicht, was wir uns dabei gedacht haben, als wir das geschrieben haben."

Slicing macht es noch schlimmer

Das Subscriptions-Backport-Profil definiert Slices auf Parameters.parameter:

  • parameter:subscription (erforderlich)
  • parameter:topic (erforderlich)
  • parameter:type (erforderlich)

Die Parameters-Ressource ist rekursiv – parameter enthĂ€lt part, das ĂŒber contentReference dieselbe Struktur teilt. Wenn wir Slicing rekursiv anwenden, wĂŒrde jedes .part seine eigenen subscription-, topic- und type-Slices benötigen. Das ist offensichtlich nicht die Absicht der Autoren.

Diese Profile sind weit verbreitet implementiert. Die Erzwingung rekursiver EinschrĂ€nkungen wĂŒrde sie alle brechen.

Rekursion vs. Typwiederverwendung

Die Diskussion offenbarte ein tieferliegendes Problem. FHIR verwendet contentReference fĂŒr zwei unterschiedliche Zwecke:

Echte Rekursion – ein Element, das auf sich selbst verweist:

  • Questionnaire.item -> Questionnaire.item.item
  • Composition.section -> Composition.section.section
  • Consent.provision -> Consent.provision.provision

Typwiederverwendung – verschiedene Elemente, die eine Struktur teilen:

  • ValueSet.compose.include und ValueSet.compose.exclude
  • Parameters.parameter und Parameters.parameter.part

Beide verwenden intern contentReference, aber die Semantik ist grundlegend verschieden. Wie Gino Canessa darauf hinwies:

„Beide werden ĂŒber den contentReference-Mechanismus realisiert, und die anfĂ€ngliche Rekursion ist nicht dasselbe Element. In diesem Fall haben sie zufĂ€llig denselben Elementnamen, aber das ist nicht das, was es rekursiv macht."

Jede Lösung muss diese FĂ€lle unterscheiden – und weder die Spezifikation noch die aktuellen Tools tun dies.

Das ist nicht das erste Mal

Grahame verwies auf einen frĂŒheren Thread auf chat.fhir.org, in dem genau dieses Problem bereits 2020 diskutiert wurde. Chris Moesel von MITRE fragte, ob EinschrĂ€nkungen auf Questionnaire.item ĂŒber contentReference auf item.item ĂŒbertragen werden sollten. Die Community kam zu dem Schluss, dass dies nicht der Fall sein sollte – und Grahame verwies auf eine bestehende Extension, elementdefinition-profile-element, als Mechanismus fĂŒr explizite rekursive Wiederverwendung. Die Extension wird auf das untergeordnete Element gesetzt und verweist zurĂŒck auf das ĂŒbergeordnete Profil, sodass Autoren explizit in die Vererbung von EinschrĂ€nkungen einwilligen können.

Dann wurde 2023 R5 mit einem neuen Abschnitt zu Rekursiven Elementen veröffentlicht, der das Gegenteil besagte: EinschrĂ€nkungen werden standardmĂ€ĂŸig rekursiv angewendet. Chris Moesel machte sofort auf den Widerspruch aufmerksam. Wie Grahame es formulierte: „Ich weiß nicht, was wir uns dabei gedacht haben, als wir das geschrieben haben."

Die Community hatte das Problem also bereits identifiziert, eine Extension zur Lösung entwickelt – und dann ging die Spezifikation in die entgegengesetzte Richtung.

Ein möglicher Lösungsansatz: eine Extension auf ElementDefinition

Im Laufe der Diskussion schlug ich eine Lösung vor: eine Extension, die explizit steuert, ob EinschrĂ€nkungen rekursiv vererbt werden. Die Idee ist einfach – ein Flag auf ElementDefinition, das Validatoren mitteilt: „Wende EinschrĂ€nkungen des ĂŒbergeordneten Elements auf diesen rekursiven Verweis an (oder nicht)."

Gino Canessa entwickelte dies weiter zu einem flexibleren Ansatz: eine Extension auf dem untergeordneten Element, die auf das Element verweist, von dem es EinschrĂ€nkungen erben soll. Beispielsweise könnte Questionnaire.item.item explizit angeben: „Übernimm EinschrĂ€nkungen von Questionnaire.item":

Questionnaire.item          – EinschrĂ€nkungen hier definiert
Questionnaire.item.item     – Extension: "inherit-constraints-from: Questionnaire.item"

Dieser Ansatz löst beide FÀlle:

  • Echte Rekursion (Questionnaire.item.item): Der Profilautor fĂŒgt die Extension hinzu, wenn er möchte, dass EinschrĂ€nkungen kaskadieren – Opt-in
  • Typwiederverwendung (ValueSet.compose.include / .exclude): Jedes Element bleibt standardmĂ€ĂŸig unabhĂ€ngig – keine versehentliche Übertragung von EinschrĂ€nkungen

Die zentrale Designfrage ist der Standard. Die Community tendiert zu nicht-rekursiv als Standard mit explizitem Opt-in:

PositionBefĂŒrworterBegrĂŒndung
Nicht-rekursiv als Standard, Opt-in ĂŒber ExtensionGino Canessa, Grahame GrieveSicherste Option fĂŒr bestehende Profile; vermeidet Breaking Changes
Rekursiv als Standard, Opt-out ĂŒber ExtensionBrian PostlethwaiteIntuitiver fĂŒr gĂ€ngige AnwendungsfĂ€lle
Bevorzugt rekursiv, akzeptiert nicht-rekursivLloyd McKenzieEine Änderung des Standards wĂ€re ein Breaking Change

Lloyd McKenzie fasste die Spannung treffend zusammen:

„Es muss eine Möglichkeit geben, EinschrĂ€nkungen zu definieren, die rekursiv gelten, denn das ist definitiv ein benötigter Anwendungsfall. Da das aktuelle Verhalten jedoch nicht so ist, könnte eine Änderung des Standards einen Breaking Change darstellen."

Was wir dagegen unternehmen

In FHIR Schema – dem baumförmigen Validierungsformat, das wir bei Health Samurai entwickeln – tritt dieses Problem als explizite architektonische Entscheidung auf. Wenn wir StructureDefinitions in einen Schema-Baum transpilieren, wird contentReference zu einem Zeiger auf einen anderen Knoten, und wir mĂŒssen entscheiden: Die EinschrĂ€nkungen des ĂŒbergeordneten Elements kopieren oder nicht?

Vorerst haben wir den Aidbox-Validator am HL7-Referenzvalidator ausgerichtet – EinschrĂ€nkungen werden nicht rekursiv angewendet, es sei denn, das Profil definiert sie explizit auf verschachtelten Elementen. Das entspricht der Art und Weise, wie Profile in der Praxis tatsĂ€chlich geschrieben werden.

Das Thema ist fĂŒr die Diskussion beim HL7 Working Group Meeting in Rotterdam vorgemerkt. Wir erwarten, dass dort konkrete VorschlĂ€ge fĂŒr einen Extension-Mechanismus und eine klarere Spezifikationssprache erarbeitet werden.

Was das fĂŒr Implementierer bedeutet

Wenn Sie mit rekursiven Elementen arbeiten (Questionnaire.item, Composition.section, Consent.provision, Parameters.parameter):

  • Aktuelles Verhalten verschiedener Validatoren: EinschrĂ€nkungen werden nicht rekursiv angewendet, unabhĂ€ngig davon, was die R5-Spezifikation besagt
  • Wenn Sie EinschrĂ€nkungen auf allen Verschachtelungsebenen benötigen: Definieren Sie diese explizit im Differential Ihres Profils fĂŒr jede Ebene, oder verwenden Sie FHIRPath-Invarianten, die den Baum traversieren
  • Wenn Sie einen Validator entwickeln: Wenden Sie EinschrĂ€nkungen nicht standardmĂ€ĂŸig rekursiv an – das wird weit verbreitete Profile brechen

Dies ist eine LĂŒcke in der Spezifikation, kein Fehler. Die Spezifikation sagt eine Sache, reale Profile benötigen eine andere, und Validatoren haben pragmatisch das Verhalten gewĂ€hlt, das bestehende Implementierungen nicht bricht.

NĂ€chster Schritt: WGM Rotterdam

Wir planen, dieses Thema beim HL7 Working Group Meeting in Rotterdam mit einem konkreten Vorschlag fĂŒr den oben beschriebenen Extension-Mechanismus einzubringen. Das Ziel ist es, eine Einigung mit der FHIR Infrastructure Working Group zu erzielen und dies in eine formelle SpezifikationsĂ€nderung zu ĂŒberfĂŒhren.

Wenn Sie dieses Problem in Ihren Profilen oder Ihrem Validator bereits erlebt haben – wir wĂŒrden Ihre AnwendungsfĂ€lle gerne vor dem Meeting hören. Beteiligen Sie sich an der Diskussion auf chat.fhir.org oder wenden Sie sich direkt an uns.

Siehe auch: So erstellen Sie einen FHIR Implementation Guide und FHIR Additional Bindings.

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

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