|
4 min de lecture
|

Implémenter FHIR dans les langages dynamiques

Résumer cet article avec :
ChatGPTPerplexityClaudeGrok

Publié le 3 février 2015 par Nicola Rizhikov

La plupart des implémentations de référence FHIR reposent sur la génération de code pour des langages fortement typés. Il existe donc un territoire inexploré du côté des langages fonctionnels et/ou dynamiquement typés comme JavaScript, Ruby, Python, Clojure, Groovy, etc.

Ce n'est un secret pour personne que le développement avec des langages dynamiques est parfois beaucoup plus rapide et plus agréable. On pourrait générer des SDK pour ces langages, mais on obtiendrait alors le pire des deux mondes : les langages dynamiques ne bénéficient pas des vérifications du compilateur ni d'un bon support d'EDI, et on n'exploite absolument pas la puissance du dynamisme.

Dans cet article, je souhaite aborder l'implémentation de FHIR dans les langages dynamiques et rassembler des idées et des lignes directrices à l'intention des développeurs.

Représentation interne

La plupart des implémentations FHIR génèrent des classes pour représenter les ressources en mémoire, mais les langages dynamiques disposent de tables de hachage (dicts, tables) et de tableaux (vecteurs) nativement, avec des littéraux pratiques. La représentation interne peut donc être très proche du format JSON :

//jsvar pt = {resourceType: "Patient", name:[{text: "Smith"}]};; clj(def pt {:resourceType "Patient" :name [{:text "Smith"}]})# rbpt = {resourceType: "Patient", name:[{text: "Smith"}]}# pypt = {'resourceType': 'Pateint', 'name': [{'text':'Smith'}]}

Les types primitifs FHIR (date, entier, etc.) peuvent être mappés aux types primitifs correspondants du langage cible.

Cette représentation est très générique et pratique pour la manipulation et l'interaction avec d'autres bibliothèques et cadriciels.

« Il vaut mieux avoir 100 fonctions opérant sur une seule structure de données que 10 fonctions sur 10 structures de données. »

Alan Perlis

API minimaliste

On peut créer une API simple et solide, composée de seulement quelques fonctions :

// Coerce primitive types from string to appropriate types.// profiles - collection of profiles with meta information // (ie types of elements, multiplicity etc)function coerce(profiles, resource){...}// walk recursively through resource and validate against profile// returns collection of errors and warnings // (OperationOutcome.issue)function validate(profiles, resource){}// parse json or xml into internal representation, optional function parse(str, format, profiles){}//serialize resource into xml or json stringfunction generate(str, format, profiles){}//convert from one format into anotherfunction convert(resource, toFormat, profiles)

Les fonctions **coerce** et **validate** sont couplées, car certaines validations (formats de date, d'URI, etc.) doivent être effectuées avant la coercition, et certaines d'entre elles nécessitent des données déjà converties (par exemple : invariants de date, expressions numériques). La fonction **convert** est dérivée de parse->generate.

Transformations pures

Presque toutes les fonctions nécessitent des métainformations (Profiles). En théorie, il serait possible de supprimer certaines dépendances aux métadonnées si la correspondance entre les formats JSON->XML et XML->JSON était non ambiguë. Or, la transformation pure de JSON->XML n'est pas possible, car le XML exige un ordre strict des éléments (contrainte fortuite), et la transformation XML->JSON est défaillante parce que le XML ne distingue pas les collections des attributs singuliers, contrairement au JSON.

Il existe deux options pour les métainformations : elles peuvent être codées en dur dans l'implémentation ou paramétrées (c'est-à-dire passées en arguments). La version codée en dur est similaire à la version générée par code et facile à utiliser, mais tout changement dans les profils de base exige de régénérer l'implémentation. La version paramétrée semble plus stable face à ces changements et peut être mise à jour à l'exécution.

Attributs polymorphes

Si l'on adopte une représentation interne de type JSON, on se heurte à des lacunes sémantiques liées aux attributs polymorphes (attr[x]) : dans les implémentations de référence générées et les profils, ces attributs sont représentés comme un seul élément ou champ, mais dans les formats de sérialisation, cette sémantique est perdue et ils sont exprimés par suffixation des clés ou des balises :

in Observation profile we have applies[x]in Java: observation.setApplies/getAppliesin json and xml: appliesPeriod,appliesDateTime

Si l'on souhaite vérifier la présence de l'élément **applies** (par exemple lors de la validation), il faut parcourir et manipuler les clés ou les balises selon le motif **applied***. Une autre conséquence est l'impossibilité d'assigner une multiplicité supérieure à un pour ces attributs. Ce problème pourrait être résolu en introduisant d'autres règles de sérialisation pour les attributs polymorphes :

applies: { Period: { start: '...', end: '...'}}//orapplies: { type: 'Period', value: {start: '...', end: '...'}}

Règles Schematron

Des invariants supplémentaires pour les ressources sont publiés sous forme de règles Schematron. Schematron opère sur du XML, ce qui nécessite une conversion supplémentaire vers le XML et tous les outils XML (XQuery, XSLT, etc.), ou l'analyse et la simulation des expressions Schematron.

Pour des raisons de symétrie, les règles pourraient être exprimées en JavaScript avec un ensemble de fonctions auxiliaires prédéfinies. Aujourd'hui, JavaScript/JSON est pris en charge partout (supplantant le XML) dans les langages, les navigateurs et les bases de données.

Spécification exécutable

Pour toutes les implémentations de référence, il serait utile de créer une suite de tests de haut niveau. Par exemple : des tests de conversion de formats décrits sous forme de paires de données sources/résultats XML <-> JSON avec de nombreux cas limites, et des cas de validation sous forme de paires RessourceInvalide->OperationOutcome.</->

Implémentation en Clojure et JavaScript

Nous avons démarré une implémentation FHIR en Clojure — https://github.com/fhirbase/fhir.clj. Il s'agit d'une version alpha précoce, mais elle illustre l'approche décrite. La bibliothèque est compacte et simple (seulement 6 fichiers ; ~600 lignes de code) et peu sensible aux ajouts ou modifications de profils. Lorsque la conception de la bibliothèque Clojure sera stabilisée, nous prévoyons de la porter vers fhir.js

Conclusion

Nous sommes d'avis que l'implémentation de FHIR pour les langages dynamiques pourrait fournir une rétroaction précieuse à la norme et aider à séparer les préoccupations d'implémentation du cœur de la norme. Nous vous encourageons à participer à la discussion et à contribuer. Le résultat attendu est un ensemble de lignes directrices et une conception d'API pour les implémentations FHIR. Voici un projet vide pour commencer à collaborer — https://github.com/FHIR/fhir-impl-guidelines.

Les langages dynamiques sont plus rapides et plus agréables pour le développement, et constituent un bon choix pour les mini-projets de connectathon. J'espère qu'à Paris nous pourrons présenter un REPL/console interactif pour Clojure et JavaScript ☺.

Commencez avec le serveur FHIR Aidbox pour le stockage de données, les intégrations, l'analytique en santé et bien plus encore, ou faites appel à notre équipe pour répondre à vos besoins en développement logiciel.

Aidbox FHIR Platform Free Trial

Voir aussi : Type Schema : construire des SDK FHIR et Le guide FHIR pour les directeurs techniques.

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

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