|
10 Min. Lesezeit
|

Type Schema: Ein pragmatischer Ansatz zur Erstellung eines FHIR SDK

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

FHIR hat sich zum primären Standard für den Austausch von Gesundheitsdaten entwickelt. Um mit FHIR zu beginnen, müssen Entwickler die FHIR-Spezifikation und Implementierungsleitfäden (IGs, wie US Core, MCODE usw.) lesen und diese dann in ihrer Programmiersprache umsetzen. FHIR SDKs vereinfachen diesen Prozess, indem sie gut dokumentierte Werkzeuge bieten, die sich natürlich in die Programmiersprache eines Entwicklers einfügen.

In diesem Beitrag erläutern wir, was ein FHIR SDK ist, warum die Erstellung eines eigenen SDK besser sein kann als die Verwendung eines universellen, und wie Type Schema, ein Open-Source-Werkzeug, Ihnen hilft, ein maßgeschneidertes SDK für Ihre spezifischen FHIR-Ressourcen zu generieren.

Was steckt in einem FHIR SDK?

Um ein nützliches FHIR SDK zu entwickeln, ist es wichtig, seine Kernaufgaben zu verstehen – wie es mit dem FHIR-Server kommuniziert und wie es strukturierte Daten verarbeitet. Schauen wir uns an, was ein typisches SDK umfasst:

  • Operationen wie CRUD, Suche und andere Möglichkeiten zur Interaktion mit Daten.
  • Ressourcen und Datentypen, wie Patient, Encounter, Marital Status Code usw.

Auf der Operationsseite stellt das SDK Methoden zur Kommunikation mit einem FHIR-Server bereit. Es enthält Funktionen zum Erstellen von URLs, zum Marshaling von Daten, zur Behandlung von Paginierung, zur Anmeldung, zur Authentifizierung und mehr. Zum Beispiel:

const patient = await fhirClient.read('Patient', '123');

const observations = await fhirClient.search('Observation', {
  patient: 'Patient/123',
  code: 'http://loinc.org|8867-4',
  date: 'gt2022-01-01'
});

const result = await fhirClient.create(newPatientResource);

Implementierungshinweis: Diese Teile arbeiten eng mit den Werkzeugen Ihrer Programmiersprache zusammen, wie HTTP-Bibliotheken und der Art, wie asynchrone Aufgaben behandelt werden. Idealerweise sollte das SDK nativ zu Ihrem Projekt-Stack passen.

Auf der Ressourcenseite bietet ein SDK Typen oder Klassen, die FHIR-Ressourcendefinitionen einschließlich ihrer Felder und Einschränkungen entsprechen (über 150 in der grundlegenden FHIR-Spezifikation). Sie sollten in Ihrer Sprache natürlich verwendbar sein:

const patient = new Patient({
  name: [
    new HumanName({
      family: "Smith",
      given: ["John"]
    })
  ],
  birthDate: "1970-01-01"
});

Implementierungshinweis: Angesichts der Anzahl der Ressourcen wird Code in der Regel automatisch generiert. Ressourcentypen können auch mit dem Operationsteil des SDK integriert werden (z. B. Active-Record-Muster).

Warum ein SDK generieren statt ein universelles zu verwenden?

Ein universelles FHIR SDK (eines, das mit allen FHIR-Funktionen und -Versionen funktioniert) bringt mehrere Herausforderungen mit sich:

  • Es ist nicht praktikabel, alle Profile zu kombinieren, da jedes IG einzigartige strukturelle und Validierungsanforderungen hat.
  • Benutzerdefinierte Ressourcen und Operationen werden häufig nicht durch Standard-FHIR abgedeckt.
  • Reale Projekte haben spezifische Technologie-Stacks. Das Hinzufügen eines umfangreichen Frameworks kann Konflikte verursachen.
  • Die Aufnahme aller FHIR-Funktionen würde das SDK für die meisten realen Projekte zu komplex machen.

Gleichzeitig ist die Entwicklung eines FHIR SDK von Grund auf schwierig für jemanden, der neu bei FHIR ist (ähnlich wie der Versuch, ein ORM zu erstellen, nachdem man gerade SQL gelernt hat). Die größten Herausforderungen sind:

  • Das Abbilden komplexer Datenstrukturen und Profile auf eine Programmiersprache, einschließlich ungewöhnlicher Funktionen wie Choice Types oder Extensions
  • Das Verwalten von FHIR-Paketen und das Auflösen von Versionskonflikten
  • Das Darstellen von ValueSets (d. h. Listen zulässiger Werte)

Die meisten Herausforderungen gehören zur Ressourcen-/Typen-Schicht des SDK. Um die FHIR SDK-Entwicklung zu vereinfachen, haben wir Type Schema entwickelt. FHIR SDK

Was ist FHIR Type Schema?

Type Schema ist ein community-getriebenes Werkzeug, das die FHIR SDK-Entwicklung erleichtert.

Die FHIR-Schema-Spezifikation ist ein JSON-Format, das FHIR-Daten auf eine Weise darstellt, die leicht erlernbar und für die Code-Generierung geeignet ist. Hier ist der Grund:

  • Vereinheitlichung. Type Schema stellt alle FHIR-Elemente (Ressourcen, Typen, ValueSets usw.) auf konsistente Weise dar, die leicht in Code umgewandelt werden kann.
  • Abflachung. Type Schema ermöglicht direkten Zugriff auf Felder und deren Typen und vermeidet komplexe Pfade. Alle Schemas können einfach in einem Dictionary für die einpfadige Code-Generierung gespeichert werden.
  • Anreicherung. Es enthält zusätzliche Informationen, die für die Code-Generierung benötigt werden, wie mögliche Werte aus ValueSets und alle abhängigen Typen.

FHIR-Schema-Werkzeuge sind Open-Source-Dienstprogramme (MIT-lizenziert), die Type Schemas für FHIR, IGs und Ihre benutzerdefinierten Ressourcen erstellen.

Das Hauptwerkzeug ist Type Schema, das Pakete und benutzerdefinierte Ressourcendefinitionen entgegennimmt und Type Schemas erstellt, die für die Code-Generierung bereit sind.

Weitere Informationen finden Sie hier:

Wie generiert man Typen mit Type Schema?

Betrachten wir ein Beispiel. Wir beginnen mit der Patient-Ressource aus hl7.fhir.r4.core in TypeScript und zeigen Schritt für Schritt, wie dieser Code aus Type Schema generiert wird.

Vollständige Code-Beispiele sind hier verfügbar:

Typabhängigkeiten importieren

Da hl7.fhir.r4.core viele Ressourcen und Typen enthält, ist es nicht praktikabel, sie in einer Datei zu platzieren. Stattdessen importieren wir sie:

import { Address } from './Address';
import { Attachment } from './Attachment';
// ...

Sie können Importe wie folgt generieren:

const deps = schema.dependencies
    // other types will be inlined or defined in this file
    .filter((dep) => ['complex-type', 'resource'].includes(dep.kind))
    .sort((a, b) => a.name.localeCompare(b.name))
    .map((dep) => `import { ${this.uppercaseFirstLetter(dep.name)} } from './${dep.name}'`)
    .join('\n');

Alle Dateien können auf dieselbe Weise wie für die Patient-Ressource generiert werden.

Definition verschachtelter Typen

FHIR-Ressourcen haben komplexe verschachtelte Strukturen. Da viele Sprachen keine verschachtelten Typdefinitionen unterstützen, generieren wir lokale Typen:

export interface PatientLink extends BackboneElement {
    other?: Reference<'Patient' | 'RelatedPerson'>;
    type?: 'replaced-by' | 'replaces' | 'refer' | 'seealso';
}

export interface PatientCommunication extends BackboneElement {
    language?: CodeableConcept;
    preferred?: boolean;
}

export interface PatientContact extends BackboneElement {
    address?: Address;
    gender?: 'male' | 'female' | 'other' | 'unknown';
    name?: HumanName;
    organization?: Reference<'Organization'>;
    period?: Period;
    relationship?: CodeableConcept[];
    telecom?: ContactPoint[];
}

Sie können dies aus dem .nested-Feld generieren, wobei alle Abhängigkeiten bereits importiert sind:

for (const subtype of schema.nested) {
    this.generateType(subtype);
}

wobei generateType eine Funktion ist, die ein Typschema empfängt und eine Typdefinition erstellt. Details finden Sie im nächsten Abschnitt.

Typgenerierung

Was haben wir an diesem Punkt?

  • Alle externen Typen importiert
  • Alle verschachtelten Dateien definiert

Betrachten wir einige Fälle (Extensions und die meisten Felder werden übersprungen):

export interface Patient extends DomainResource {
    active?: boolean;
    link?: PatientLink[];
    gender?: 'male' | 'female' | 'other' | 'unknown';
    multipleBirthBoolean?: boolean;
    multipleBirthInteger?: number;
    // ...
}

Die erste Zeile der Typdefinition ist einfach: Wir nehmen den Typnamen aus .identifier.name und den Basistyp aus .base.name.

export interface Patient extends DomainResource {
{
  "identifier": { "kind": "resource", "package": "hl7.fhir.r4.core", "version": "4.0.1",
                 "name": "Patient", "url": "http://hl7.org/fhir/StructureDefinition/Patient" },
  "base": { "kind": "resource", "package": "hl7.fhir.r4.core", "version": "4.0.1",
            "name": "DomainResource", "url": "http://hl7.org/fhir/StructureDefinition/DomainResource" }
}

Felder werden durch Analyse von .fields generiert und basierend auf dem Typ zugeordnet:

  • Primitive Typen werden direkt auf native Typen in der Zielsprache abgebildet. Zum Beispiel: const typeMap = { boolean: 'boolean'... }
  • Komplexe/verschachtelte Typen verwenden ihre definierten Namen, da wir sie bereits importieren oder definieren.
  • Arrays werden mit [] am Ende des Namens versehen.
active?: boolean;
    link?: PatientLink[];
{
  "active": {
      "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1",
                "name": "boolean", "url": "http://hl7.org/fhir/StructureDefinition/boolean" },
      "array": false,
      "required": false, "excluded": false
    },
  "link": {
    "type": { "kind": "nested", "package": "hl7.fhir.r4.core", "version": "4.0.1",
              "name": "link", "url": "http://hl7.org/fhir/StructureDefinition/Patient#link" },
    "array": true,
    "required": false, "excluded": false
  }
}

Für Felder mit ValueSet-Bindung (Listen zulässiger Werte) stellt Type Schema mögliche Werte bereit, sodass wir sie direkt in unserem Typ verwenden können:

gender?: 'male' | 'female' | 'other' | 'unknown';
{
  "gender": {
    "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1",
              "name": "code", "url": "http://hl7.org/fhir/StructureDefinition/code" },
    "array": false,
    "required": false, "excluded": false,
    "enum": [ "male", "female", "other", "unknown" ]
  }
}

Für Choice Types (Felder, die verschiedene Typen annehmen können) verwendet dieses Beispiel einen einfachen Ansatz, indem separate Felder ohne zusätzliche Validierung erstellt werden. Weitere Optionen finden Sie unter: Choice Type Representation.

multipleBirthBoolean?: boolean;
    multipleBirthInteger?: number;
{
  "multipleBirth": {
    "choices": [ "multipleBirthBoolean", "multipleBirthInteger" ],
    "array": false,
    "required": false, "excluded": false
  },
  "multipleBirthBoolean": {
    "choiceOf": "multipleBirth",
    "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1",
              "name": "boolean", "url": "http://hl7.org/fhir/StructureDefinition/boolean" },
    "array": false,
    "required": false, "excluded": false
  },
  "multipleBirthInteger": {
    "choiceOf": "multipleBirth",
    "type": { "kind": "primitive-type", "package": "hl7.fhir.r4.core", "version": "4.0.1",
              "name": "integer", "url": "http://hl7.org/fhir/StructureDefinition/integer" },
    "array": false,
    "required": false, "excluded": false
  }
}

Dies ist nur ein kurzer Einblick in die Generierung von FHIR SDK-Code mit Type Schema, aber wie Sie sehen können, ist es unkompliziert, wenn Sie wissen, was Sie erzeugen möchten.

Probieren Sie es selbst aus

So generieren Sie SDK-Typen:

  1. Code-Generator installieren:
npm install -g @fhirschema/codegen
  1. Typen generieren für

TypeScript:

npx @fhirschema/codegen generate -g typescript -p 'hl7.fhir.r4.core@4.0.1' -o out

Python:

npx @fhirschema/codegen generate -g python -p 'hl7.fhir.r4.core@4.0.1' -o out

C#:

npx @fhirschema/codegen generate -g csharp -p 'hl7.fhir.r4.core@4.0.1' -o out

Weitere Details finden Sie unter: fhir-schema-codegen.

Fazit

In diesem Beitrag haben wir Type Schema als Werkzeug vorgestellt, das die FHIR SDK-Entwicklung vereinfacht. Durch die Bereitstellung eines standardisierten Formats für FHIR-Datenentitäten können Entwickler maßgeschneiderte SDKs generieren, die auf ihre Bedürfnisse zugeschnitten sind, ohne von Grund auf neu beginnen zu müssen.

Zukunftspläne

Type Schema befindet sich noch in der frühen Entwicklung. Geplante Verbesserungen umfassen:

  • Bessere Unterstützung für Profile, ValueSets, Extensions, Operationen usw.
  • Ein „FHIR SDK Cookbook" mit Beispielen, die zeigen, wie FHIR auf verschiedene Programmiersprachen abgebildet werden kann.
  • Verbesserte Werkzeuge für den Umgang mit mehreren Paketen und die Auflösung von Konflikten.

Treten Sie unserer Community bei

Helfen Sie uns, Type Schema zu verbessern:

  • Starten Sie eine Diskussion oder melden Sie Probleme: GitHub Issues
  • Fügen Sie eigene Beispiele, Funktionen oder Korrekturen hinzu: GitHub Repo
  • Chatten Sie mit uns: Zulip

Siehe auch: Type Schema: Python SDK für FHIR und FHIR in dynamischen Sprachen implementieren.

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

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