|
4 min de lectura
|

Implementación de FHIR en lenguajes dinámicos

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

Publicado el 3 de febrero de 2015 por Nicola Rizhikov

La mayoría de las implementaciones de referencia de FHIR se basan en la generación de código para lenguajes fuertemente tipados. Por ello, existe un territorio sin explorar en los lenguajes funcionales y/o de tipado dinámico como JavaScript, Ruby, Python, Clojure, Groovy, etc.

No es ningún secreto que el desarrollo con lenguajes dinámicos es a veces mucho más rápido y más entretenido. Podríamos generar SDKs para ellos mediante generación de código, pero entonces obtendríamos lo peor de ambos mundos: los lenguajes dinámicos no disponen de comprobaciones en tiempo de compilación ni de un buen soporte para el IDE, y no aprovechamos en absoluto el poder del dinamismo.

En esta entrada quiero tratar la implementación de FHIR en lenguajes dinámicos y recopilar ideas y directrices para los implementadores.

Representación interna

La mayoría de las implementaciones de FHIR generan clases para representar recursos en memoria, pero los lenguajes dinámicos disponen de hash-maps (dicts, tables) y arrays (vectors) de forma nativa con literales convenientes, por lo que la representación interna puede estar muy próxima al formato 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'}]}

Los tipos primitivos de FHIR (date, integer, etc.) pueden mapearse a los tipos primitivos correspondientes del lenguaje concreto.

Esta representación es muy genérica y conveniente para la manipulación e interacción con otras bibliotecas y frameworks.

«Es mejor tener 100 funciones que operen sobre una única estructura de datos que 10 funciones sobre 10 estructuras de datos.»

Alan Perlis

API minimalista

Podemos crear una API sencilla y sólida, compuesta únicamente por unas pocas funciones:

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

Las funciones **coerce** y **validate** están acopladas, porque algunas validaciones (formatos de fecha, URI, etc.) deben realizarse antes de la coerción, y otras requieren datos ya convertidos (por ejemplo: invariantes de fecha, expresiones numéricas). La función **convert** es derivada de parse->generate.

Transformaciones puras

Casi todas las funciones requieren metainformación (Profiles). En teoría, sería posible eliminar algunas dependencias de los metadatos si la correspondencia entre los formatos json->xml y xml->json fuera unívoca. Actualmente, la transformación pura de json->xml no es posible porque xml exige un orden estricto de los elementos (restricción accidental), y la de xml->json está rota porque xml no distingue entre colecciones y atributos singulares, mientras que json sí lo hace.

Existen dos opciones para la metainformación: puede estar codificada directamente en la implementación o parametrizada (es decir, pasada como argumentos). La versión codificada es similar a la generada mediante código y más fácil de usar, pero cualquier cambio en los perfiles base requiere regenerar la implementación. La versión parametrizada parece más estable ante dichos cambios y puede actualizarse en tiempo de ejecución.

Atributos polimórficos

Si adoptamos una representación interna similar a JSON, encontramos deficiencias semánticas relacionadas con los atributos polimórficos (attr[x]): en las implementaciones de referencia generadas y en los perfiles, dichos atributos se representan como un único elemento o campo, pero en los formatos de serialización pierden esta semántica y se expresan añadiendo sufijos a las claves o etiquetas:

en el perfil Observation tenemos applies[x]en Java: observation.setApplies/getAppliesin json and xml: appliesPeriod,appliesDateTime

Si quisiéramos comprobar la presencia del elemento **applies** (por ejemplo, durante la validación), tendríamos que explorar y manipular claves o etiquetas mediante el patrón **applied***. Otra consecuencia es la imposibilidad de asignar una multiplicidad superior a uno para dichos atributos. Este problema podría resolverse introduciendo reglas de serialización alternativas para los atributos polimórficos:

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

Reglas Schematron

Los invariantes adicionales de los recursos se publican como reglas Schematron. Schematron opera sobre XML, por lo que se necesita una conversión adicional a XML con todo lo que ello conlleva (xquery, xslt, etc.), o bien analizar y simular las expresiones Schematron.

Por simetría, las reglas podrían expresarse en JavaScript con un conjunto de funciones auxiliares predefinidas. Hoy en día JavaScript/JSON tiene soporte en todas partes (desplazando a XML) en lenguajes, navegadores y bases de datos.

Especificación ejecutable

Para todas las implementaciones de referencia sería útil crear una suite de pruebas de alto nivel. Por ejemplo: pruebas de conversión de formatos descritas como pares de fixtures XML <-> JSON de origen/resultado con numerosos casos límite, y casos de validación como pares RecursoInválido->OperationOutcome.

Implementación en Clojure y JavaScript

Hemos iniciado la implementación de FHIR en Clojure — https://github.com/fhirbase/fhir.clj. Se encuentra en una fase alfa muy temprana, pero ilustra el enfoque descrito. La biblioteca es compacta y sencilla (solo 6 archivos; ~600 sloc) y no es sensible a la adición o modificación de perfiles. Cuando el diseño de la biblioteca Clojure sea sólido, tenemos previsto portarla a fhir.js.

Conclusión

Nuestra opinión es que la implementación de FHIR para lenguajes dinámicos puede aportar una valiosa retroalimentación al estándar y contribuir a separar las preocupaciones de implementación del núcleo del estándar. Les animamos a participar en el debate y a realizar contribuciones. El resultado esperado son directrices y un diseño de API para las implementaciones de FHIR. Aquí tiene un proyecto vacío para comenzar a colaborar — https://github.com/FHIR/fhir-impl-guidelines.

Los lenguajes dinámicos son más ágiles y entretenidos para el desarrollo y constituyen una buena elección para los miniproyectos de los connectathons. Espero que en París podamos presentar un REPL/consola interactivo para Clojure y JavaScript ☺.

Comience a utilizar el servidor FHIR de Aidbox para el almacenamiento de datos, integraciones, análisis sanitario y mucho más, o contrate a nuestro equipo para dar soporte a sus necesidades de desarrollo de software.

Aidbox FHIR Platform Free Trial

Véase también: Type Schema: Building FHIR SDKs y The FHIR Guide for CTOs.

Compartir este artículo
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

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