|
10 min de lecture
|

Type Schema : une approche pragmatique pour créer un SDK FHIR

Résumer cet article avec :
ChatGPTPerplexityClaudeGrok

FHIR est devenu la norme principale pour l'échange de données de santé. Pour commencer avec FHIR, les développeurs doivent lire la spécification FHIR et les guides d'implémentation (IG, tels que US Core, MCODE, etc.), puis les mettre en œuvre dans leur langage de programmation. Les SDK FHIR simplifient ce processus en offrant des outils bien documentés qui s'intègrent naturellement au langage de programmation du développeur.

Dans cet article, nous expliquerons ce qu'est un SDK FHIR, pourquoi créer son propre SDK peut être préférable à l'utilisation d'un SDK universel, et comment Type Schema, un outil à code source ouvert, vous aide à générer un SDK personnalisé adapté à vos ressources FHIR spécifiques.

Que contient un SDK FHIR?

Pour créer un SDK FHIR utile, il est important de comprendre ses responsabilités fondamentales — comment il communique avec le serveur FHIR et comment il gère les données structurées. Voici les composantes typiques d'un SDK :

  • Les opérations, comme les opérations CRUD, la recherche et d'autres façons d'interagir avec les données.
  • Les ressources et les types de données, comme Patient, Encounter, Marital Status Code, etc.

Du côté des opérations, le SDK fournit des méthodes pour communiquer avec un serveur FHIR. Il comprend des fonctions pour construire des URL, sérialiser les données, gérer la pagination, l'ouverture de session, l'authentification, et plus encore. Par exemple :

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);

Note d'implémentation : Ces parties fonctionnent étroitement avec les outils de votre langage de programmation, comme les bibliothèques HTTP et la façon dont il gère les tâches asynchrones. Idéalement, le SDK devrait être natif à la pile technologique de votre projet.

Du côté des ressources, un SDK fournit des types ou des classes qui correspondent aux définitions des ressources FHIR, incluant leurs champs et contraintes (plus de 150 dans la spécification FHIR de base). Ils devraient être naturels à utiliser dans votre langage :

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

Note d'implémentation : Étant donné le nombre de ressources, le code est généralement généré automatiquement. Les types de ressources peuvent également s'intégrer à la partie opérationnelle du SDK (p. ex., le patron active record).

Pourquoi générer un SDK plutôt que d'en utiliser un universel?

Un SDK FHIR universel (qui fonctionne avec toutes les fonctionnalités et versions de FHIR) présente plusieurs défis :

  • Il n'est pas réaliste de combiner tous les profils, car chaque IG a des exigences structurelles et de validation qui lui sont propres.
  • Les ressources et opérations personnalisées ne sont souvent pas couvertes par le FHIR standard.
  • Les projets réels ont des piles technologiques spécifiques. L'ajout d'un cadriciel volumineux peut créer des conflits.
  • L'inclusion de toutes les fonctionnalités FHIR rendrait le SDK trop complexe pour la plupart des projets réels.

En même temps, créer un SDK FHIR à partir de zéro est difficile pour quelqu'un qui débute avec FHIR (comme essayer de créer un ORM après avoir tout juste appris le SQL). Les principaux défis sont :

  • La correspondance des structures de données complexes et des profils avec un langage de programmation, incluant des fonctionnalités inhabituelles comme les types choix ou les extensions
  • La gestion des paquets FHIR et la résolution des conflits de versions
  • La représentation des ValueSets (c.-à-d. les listes de valeurs autorisées)

La plupart des défis appartiennent à la couche ressources/types du SDK. Pour simplifier le développement de SDK FHIR, nous avons créé Type Schema. FHIR SDK

Qu'est-ce que FHIR Type Schema?

Type Schema est un outil communautaire qui facilite le développement de SDK FHIR.

La spécification FHIR Schema est un format JSON qui représente les données FHIR d'une façon facile à apprendre et à partir de laquelle il est aisé de générer du code. Voici pourquoi :

  • Unification. Type Schema représente tous les éléments FHIR (ressources, types, ValueSets, etc.) de façon cohérente, ce qui facilite leur conversion en code.
  • Aplatissement. Type Schema vous donne un accès direct aux champs et à leurs types, en évitant les chemins complexes. Tous les schémas peuvent être stockés simplement dans un dictionnaire pour une génération de code en un seul passage.
  • Enrichissement. Il comprend des informations supplémentaires nécessaires à la génération de code, telles que les valeurs possibles provenant des ValueSets et tous les types dépendants.

Les outils FHIR Schema sont des utilitaires à code source ouvert (licence MIT) qui créent des Type Schemas pour FHIR, les IG et vos ressources personnalisées.

L'outil principal est Type Schema, qui prend des paquets et des définitions de ressources personnalisées et génère des Type Schemas prêts pour la génération de code.

Vous pouvez également en savoir plus ici :

Comment générer des types avec Type Schema?

Voyons un exemple. Nous commencerons avec la ressource Patient de hl7.fhir.r4.core en TypeScript et montrerons comment générer ce code à partir de Type Schema, étape par étape.

Des exemples de code complets sont disponibles ici :

Importer les dépendances de types

Puisque hl7.fhir.r4.core comprend de nombreuses ressources et de nombreux types, les regrouper dans un seul fichier n'est pas pratique. On les importe plutôt :

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

Vous pouvez générer les importations avec :

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');

Tous les fichiers peuvent être générés de la même façon que pour la ressource Patient.

Définition des types imbriqués

Les ressources FHIR ont des structures imbriquées complexes. Puisque de nombreux langages ne prennent pas en charge les définitions de types imbriqués, nous générons des types locaux :

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

Vous pouvez générer ceci à partir du champ .nested, avec toutes les dépendances déjà importées :

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

generateType est une fonction qui reçoit un schéma de type et produit une définition de type. Les détails se trouvent dans la section suivante.

Génération de types

Où en sommes-nous à cette étape?

  • Tous les types externes ont été importés
  • Tous les fichiers imbriqués ont été définis

Examinons quelques cas (en omettant les extensions et la plupart des champs) :

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

La première ligne de la définition de type est simple : nous prenons le nom du type dans .identifier.name et le type de base dans .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" }
}

Les champs sont générés en analysant .fields et en les associant selon leur type :

  • Les types primitifs sont associés directement aux types natifs du langage cible. Par exemple : const typeMap = { boolean: 'boolean'... }
  • Les types complexes/imbriqués utilisent leurs noms définis, puisqu'ils sont déjà importés ou définis.
  • Les tableaux sont suffixés d'un [] à la fin du nom.
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
  }
}

Pour les champs avec une liaison ValueSet (listes de valeurs autorisées), Type Schema fournit les valeurs possibles afin que nous puissions les ajouter directement à notre type :

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

Pour les types choix (champs pouvant prendre différents types), cet exemple utilise une approche simple en créant des champs séparés sans validation supplémentaire. Pour d'autres options, voir : 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
  }
}

Il ne s'agit là que d'un aperçu de la génération de code de SDK FHIR avec Type Schema, mais comme vous pouvez le constater, c'est simple si vous savez ce que vous cherchez à produire.

Essayez par vous-même

Pour générer des types de SDK :

  1. Installez le générateur de code :
npm install -g @fhirschema/codegen
  1. Générez des types pour

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

Pour plus de détails, veuillez consulter : fhir-schema-codegen.

Conclusion

Dans cet article, nous avons présenté Type Schema comme outil pour simplifier le développement de SDK FHIR. En fournissant un format normalisé pour les entités de données FHIR, les développeurs peuvent générer des SDK personnalisés adaptés à leurs besoins sans repartir de zéro.

Plans futurs

Type Schema est encore en développement précoce. Les améliorations à venir comprennent :

  • Un meilleur soutien pour les profils, les ValueSets, les extensions, les opérations, etc.
  • Un « livre de recettes SDK FHIR » avec des exemples montrant comment associer FHIR à différents langages de programmation.
  • Des outils améliorés pour gérer plusieurs paquets et résoudre les conflits.

Rejoignez notre communauté

Aidez-nous à améliorer Type Schema :

  • Lancez une discussion ou signalez des problèmes : GitHub Issues
  • Ajoutez vos propres exemples, fonctionnalités ou correctifs : GitHub Repo
  • Discutez avec nous : Zulip

Voir aussi : Type Schema : SDK Python pour FHIR et Implémenter FHIR dans des langages dynamiques.

Partager cet article
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

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