Operaciones con HL7® FHIR®

12 de mayo, 2021

Clase 1: Arquitectura FHIR y REST

Esta clase fue dictada por Alejandro Medina donde vimos:

  • Arquitectura FHIR
  • Fundamentos REST
  • Operaciones básicas REST
  • Demo

Arquitectura FHIR

Principios arquitectónicos

Antes de continuar con los apuntes, me gustaría hacer un paréntesis sobre “arquitectura”. En software, cuando hablamos de arquitectura, nos referimos a los “planos” de un sistema informático. Es parecido a los planos de las casas o edificios, pero aquí los planos me indican cómo se organiza un software.

El profesor nos comentó sobre los “principios arquitectónicos”, es decir, la mentalidad con la cual se pensó la arquitectura de FHIR, que son:

  • Reutilización y composición: Los Recursos FHIR® están diseñados para coincidir con los requisitos comunes de muchos casos. Los perfiles o extensiones nos permiten adaptar para nuestros casos particulares. Por ejemplo, en Chile, podemos usar extensiones para agregar campos de Nombre y apellido, previsión y dirección.
  • Escalabilidad: (cuando hablamos de escalabilidad, nos referimos a que el sistema va a soportar muchas peticiones o conexiones de usuarios). En FHIR se logra gracias a que las transacciones (las operaciones sobre recursos) no tengan estado (cada operación funciona de manera independiente), lo cual facilita que dentro de un grupo de servidores se puedan distribuir las transacciones (se puede paralelizar carga para evitar cuellos de botella).
  • Rendimiento: los recursos FHIR® son livianos y adecuados para el intercambio (para un computador es fácil y rápido generar o leer un recurso XML o JSON).
  • Usabilidad: los recursos FHIR® pueden ser entendidos por perfiles técnicos y no técnicos, incluso en el caso de no conocer la sintaxis de JSON o XML es posible leer e interpretar la información de un recurso.
  • Fidelidad de datos: mecanismos incorporados para la asociación y validación de terminología clínica (es decir, un programa puede validar que los datos respeten las reglas y normas definidas).

Descomposición FHIR

FHIR puede “descomponerse” (documentación) en los siguientes módulos:

  • Modelo de información (Information Model): los componentes de FHIR relacionados con la creación de recursos.
  • Modelo de conformidad (Constraints): los componentes de FHIR que abordan las limitaciones y la validez.
  • Terminología (Terminology): los componentes de FHIR relacionados con terminologías clínicas y ontologías
  • Uso (Usage): el componente de FHIR que aborda el uso de FHIR para su uso en tiempo de ejecución (es decir, la API REST de FHIR).

Descomposición de módulos FHIR

Imagen extraida de documentación oficial FHIR

Implementación de servidores FHIR

Un servidor REST FHIR es cualquier software que implementa las API FHIR y utiliza recursos FHIR para intercambiar datos. El diagrama siguiente describe las definiciones de la interfaz FHIR. Los métodos se clasifican en:

  • iServeInstance: métodos que realizan operaciones Get, Put o Delete en un recurso
  • iServeType: métodos que obtienen información de tipo o metadatos sobre recursos
  • iServeSystem: métodos que exponen o habilitan comportamientos del sistema.

Arquitectura de un servidor FHIR

Imagen extraida de documentación oficial FHIR

Nota: esto fue traducido de la documentación.

Introducción a REST

En la clase no explicaron mucho sobre qué es REST, así que aquí va un resumen.

REST se refiere a un conjunto de principios de arquitectura que es muy adoptado por las aplicaciones web. Cuando hablamos de principios de arquitectura, nos referimos a ideas y reglas que se deben cumplir.

REST nos va a decir que la aplicación web debe hacer “esto” y “esto otro” (lo veremos más adelante). Cuando una aplicación web cumpla con todo lo que dice REST, diremos que es una “aplicación web RESTFul”.

Ok, entonces REST nos “obliga” a hacer las cosas de cierta manera. ¿Y qué nos pide hacer?

REST nos pide que nuestra aplicación web organice su información en recursos. Por eso FHIR está organizado en recursos y puedes ver que hay un recurso para Paciente, otro para Observación, entre otros.

Las siglas REST se refieren a REpresentational State Transfer. Esto quiere decir que cuando solicitamos información a una aplicación web RESTFul (que cumple con REST), esta nos transferirá una representación del estado del recurso solicitado.

Por ejemplo, cuando solicitamos la información de un Paciente particular, una API FHIR nos entregará el estado de ese paciente, incluyendo información como el nombre, dirección, entre otras cosas. El recurso estará representado comúnmente en formato JSON o XML.

Fin del paréntesis.

REST

Los principios fundamentales de REST son:

  • Cliente-Servidor: el cliente es un componente independiente del servidor (cuando hablamos de cliente, nos referimos a la persona o al software que utiliza una aplicación web).
  • Sin estado: Cada petición contiene toda la información para ejecutar una acción.
  • Cacheable: posibilidad de utilizar caché para peticiones recurrentes (es decir, la aplicación web recuerda que fue lo último que dijo y en vez de hacer todos los cálculos te da la respuesta anterior, permitiendo responder más rápido y utilizando menos recursos).
  • Interfaz Uniforme: simplifica y desacopla la arquitectura (interactuar con un recurso A lo mismo que interactuar con un recurso B).
  • Sistema de Capas: los componentes no pueden ver más allá de su capa (es decir, cuando hablo con una API REST no me interesa como guardan la información, solo me interesa que me la entregue).

Las aplicaciones web funcionan con el protocolo HTTP, que en resumen significa que habrá un “cliente” que le hará una solicitud a un “servidor” quien responderá con lo que le solicitaron. Por ejemplo, un cliente puede ser tu explorador Chrome, el servidor puede ser el servidor CENS, y la solicitud es ver el curso que estamos haciendo. El servidor CENS responderá con la información en formato HTML (página web) y Chrome nos mostrará de manera bonita y ordenada la información.

Request / Petición

Una solicitud se compone de:

  • Versión del protocolo: qué versión de HTTP se está usando.
  • Método: la acción que deseamos realizar, las cuales son:

    • POST: para crear un recurso, los datos se especifican en el body (“Cuerpo del mensaje”)
    • GET: para obtener uno o varios recursos
    • PUT: para actualizar un recurso, lo cual significa reemplazar un elemento completo.
    • DELETE: para eliminar un recurso (sin body).
  • URI identifica la entidad o recurso, permitiendo obtener un elemento en específico o todos. También puede incluir parámetros de búsqueda.
  • Cabeceras: características acerca de lo que se transmite. Aquí por ejemplo indicamos que queremos obtener la información en JSON o XML.
  • Cuerpo del mensaje: información del recurso (no todas las operaciones lo tienen). Aquí indicamos los datos del curso que queremos guardar (POST) o actualizar (PUT).

Responde / Respuesta

Una respuesta se compone de:

  • Versión del protocolo: qué versión de HTTP se está usando.
  • Código de estado: es un número que indica el estado de la respuesta.

    • Respuestas informativas (100–199)
    • Respuestas satisfactorias (200–299)
    • Redirecciones (300–399)
    • Errores de los clientes (400–499). Cuando entras a una página que no existe, aparece un 404 de “not found” (ejemplo).
    • Errores de los servidores (500–599). Por ejemplo, WePlay se cayó porque mucha gente entró a comprar un PS5 a $499.990, si entrabas a la página aparecía un error “502”.
  • Cabeceras: características acerca de lo que se transmite. Por ejemplo, nos dirá que la información fue entregada en formato JSON.
  • Cuerpo del mensaje: aquí nos entrega la información que solicitamos.

Demo y Ejemplos

En la clase dieron un ejemplo de API REST de autos.

En una plataforma de venta de autos, nuestro recurso principal serán los autos. Por ejemplo, podemos tener un servidor web en autos.venta.cl que ofrece una URI autos para obtener los recursos de autos.

Como sabemos que la URI es autos, y que el servidor es REST, entonces sabemos que podemos crear, actualizar, leer y eliminar autos.

Leer (GET)

Obtener todos los autos (notar que el URI es /autos)

GET /autos HTTP/1.1
Host: autos.venta.cl
Accept: application/json

Obtener un auto que tenga id = 1 (notar que el URI es /autos/1)

GET /autos/1 HTTP/1.1
Host: autos.venta.cl
Accept: application/json

Obtener todos los autos de color azul (notar que el URI es /autos?color=azul). Aquí el color=azul es un parámetro de búsqueda

GET /autos?color=azul HTTP/1.1
Host: autos.venta.cl
Accept: application/json

En REST, si decimos que los recursos de auto están en el URI /autos, asumimos que un auto particular se encuentra en /autos/:id (con :id el identificador del auto)

Crear (POST)

Para crear un auto, tendremos que crear una solicitud POST a /autos:

POST /autos/ HTTP/1.1
Host: autos.venta.cl
Content-Type: application/json
Accept: application/json
{ "marca": "peugeot",  "color": "azul" }

Aquí podemos ver todos los elementos mencionados anteriormente:

  • Versión del protocolo: HTTP/1.1
  • Método: POST
  • URI /autos
  • Cabeceras: Las líneas que dice Content-Type y Accept
  • Cuerpo del mensaje: { "marca": "peugeot", "color": "azul" }

Actualizar (PUT)

Para actualizar el auto de id igual a 1, tenemos que hacer una solicitud PUT a /autos/1:

POST /autos/ HTTP/1.1
Host: autos.venta.cl
Content-Type: application/json
Accept: application/json
{ "marca": "toyota", "color": "rojo" }

En el body estamos actualizando la marca a toyota y la marca a rojo.

A diferencia de la operación para crear, aquí estamos editando un auto particular, por eso la URI es /autos/1.

Eliminar (DELETE)

Para eliminar el auto de id igual a 1:

DELETE /autos/1 HTTP/1.1
Host: autos.venta.cl
Accept: application/json

Preguntas en clases

¿Se pueden obtener todos los recursos sin paginar?

Sí, pero el servidor o el cliente pueden colapsar debido a la cantidad de información.

Nota: paginar es cuando obtienes la información de a poco, dividida en páginas en vez de toda la información.

¿Existe Graphql en FHIR?

Sí, hay discusiones e implementaciones pero no es normativo.

¿Las URI son RESTFul en FHIR?

Sí pero se ven un poco limitadas. Generalmente es /RECURSO y /RECURSO/:id (listado y uno en particular). Si queremos obtener asociaciones (por ejemplo, las observaciones de un paciente), se hace mediante búsqueda.

¿Podemos usar no-content en vez de 404?

No me acuerdo que dijo el profe 😂, pero si un recurso no existe, lo correcto es responder 404. “Lo correcto” es definido por estándar de HTTP. Si respondes no-content cuando debería ser 404, harás que los desarrolladores tengan que hacer chequeos adicionales en las aplicaciones, lo cual no es idóneo.

Nota: no content es una cabecera para indicar que mi respuesta no tiene contenido (sin body). Generalmente se usa cuando eliminamos un recurso.

Clase 2: Operaciones

Esta clase (por José María Andrade) fue sobre operaciones en FHIR. Parecido a los conceptos vistos sobre operaciones en REST, pero aplicadas en el estándar.

¿Qué es un Recurso?

Un Recurso es una entidad definida por el estándar con el propósito de facilitar el intercambio de datos de salud.

Se caracteriza por:

  • URL definida para acceder.
  • En su estructura se define el tipo de recurso
  • Posee un conjunto de elementos estructurados
  • Versionado

La mayoría derivan de DomainResource, heredando extensiones, texto, recursos contenidos, etc. Esto quiere decir que recursos distintos tendrán algunas propiedades en común provenientes de la definición de DomainResource (en programación esto se conoce como “herencia”).

Existe un recurso especial llamado Bundle el cual sirve para agrupar recursos. Un ejemplo del uso de Bundle es para resultados de búsqueda.

Operaciones básicas

  • GET [base]/Patient: obtiene listado de pacientes. Cuando aplicamos esta operación REST el servidor FHIR contesta con código de HTTP 200 (que significa ok, que todo salió bien) un JSON de tipo Bundle que contiene todos los recursos de ese tipo.
  • GET [base]/Patient/example: obtiene un paciente particular (el que tiene ID = example). Cuando aplicamos esta operación, el servidor FHIR contesta con un JSON (con código HTTP 200) que contiene el recurso solicitado.
  • POST [base]/Patient: crea un paciente nuevo. Cuando aplicamos esta operación junto a un body JSON que contiene la estructura de ese recurso, el servidor crea el recurso y responde con el recurso creado y un código HTTP 201 (que significa created, recurso creado exitosamente).
  • PUT [base]/Patient/example: actualiza un paciente particular (el que tiene ID = example). Cuando aplicamos esta operación junto a un body JSON que contiene la estructura de ese recurso, el servidor actualiza el recurso y responde con el recurso actualizado y un código 200,el history del recurso aumenta en 1.
  • DELETE [base]/Patient/example: elimina a un paciente particular (el que tiene ID = example). Cuando aplicamos esta operación rest el servidor FHIR contesta con un JSON (con código HTTP 204, no content, sin contenido) y elimina el recurso (más bien lo desactiva).

Como puedes ver, hay operaciones que tienen que ver con un recurso particular (todas las que tienen el ID en la URL) y otras que tienen que ver con el conjunto de pacientes (sin el ID).

La parte que dice [base] se refiere a la URL base donde se encuentra el servidor FHIR. Puede ser algo como https://www.example.org, y va a depender del servicio al cual nos queramos comunicar.

Además vimos dos operaciones más:

  • HISTORY: GET [base]/[resource]/[id]/_history/[x], donde [x] corresponde al número de la versión que queremos obtener. Nos sirve para obtener un recurso en una versión particular.
  • PATCH [base]/[resource]/[id], esta es una operación que nos permite actualizar un recurso (tal y como hace PUT) con la diferencia que aquí solo debemos indicar lo que queremos hacer. Cuando aplicamos esta operación junto a un body JSON que contiene la operación a ejecutar de ese recurso, el servidor actualiza el recurso y responde con el recurso actualizado y un código 200, el history del recurso aumenta en 1.

Los pasos para hacer un PATCH son:

  1. Obtener el recurso que se va a modificar
  2. Llevar el json/xml a un editor.
  3. Agregar a los headers: “Content-type: application/json-patch+json”
  4. Agregar el body relacionado a la operación: add, replace, remove. Ejemplo:

    • Agregar: [{ "op": "add", "path": "/name/1", "value": {"given": "dos"} }]
    • Actualizar: [{ "op": "replace", "path": "/name/1", "value": {"given": "uno"} }]
    • Eliminar: [{ "op": "remove", "path": "/name/1" }]
  5. Aplicar el PATCH sobre [base]/[resource]/[id]

Búsquedas en FHIR

Podemos realizar búsquedas de dos formas:

  1. GET [base]/[recurso]/?[parametros]&_format=[mime-type]
  2. GET [base]/[recurso]/_search?[parametros]&_format=[mime-type]

Donde:

  • base: Es la URL base del servidor FHIR.
  • recurso: Es el recurso con cual deseo interactuar, ej: Patient, Observation, etc.
  • parámetros: Son los parámetros de búsquedas que desea utilizar para obtener el resultado
  • _format: Es el formato en cual desea obtener el resultado de la búsqueda JSON o XML, una manera más elegante de realizar esta tarea es a través de HTTP headers.

Por ejemplo, para buscar a los pacientes que se llaman Nicolás: GET [base]/Patient?given=Nicolás

Clase 3: Perfiles y extensiones

¿Qué se puede hacer con un perfil?

  1. Reglas de elementos que se usan y cuales no (podemos agregar restricciones).
  2. Elementos nuevos que no están en la definición base (a través de extensiones, utilizar para requisitos específicos)
  3. Reglas de terminología
  4. Descripciones de elementos que permiten establecer un nexo con los requerimientos locales.

¿Para qué se usan?

Son parte de:

  • Guías de implementación, que a su vez son la formalización de los cambios realizados.
  • Paquetes o Releases.

    • Esto es más o menos nuevo, crearon un repositorio de manera que los perfiles puedan ser descargables. Es decir, puedo instalar un perfil.
    • Simplifier está incluido, así que podemos instalar cualquier perfil que esté en dicha página.
  • Capability Statement: qué se puede hacer y qué no.

¿Cuáles son los cambios aplicables?

  • Cardinalidad: 0..1, 0..n, 1..1, 1..n
  • Restringir valores por defecto.
  • Restringir datos mediante fhir path.
  • Restringir relaciones. Por ejemplo, una relación yo puedo decir que si o sí debe ser de cierto recurso.
  • Especificar cierta terminología.
  • Restringir relaciones a perfiles específicos.
  • Marcar atributos como is-modifier (cambia el contexto de lo que es), must support (son elementos con contexto nuevo que tiene que ser utilizado), maxLenght (máximo largo de texto), isSummary (para que sea parte del resumen).

 ¿Qué son las extensiones?

  • Son un tipo de dato especial.
  • Permiten extender un recurso base.
  • Permite extender elementos nuevos.
  • Se puede usar casi cualquier tipo de dato de los disponibles en la especificación.

Las extensiones se componente de una URL (generalmente ficticias, pero que le da sentido a la extensión) y de un valor.

El valor se guarda en una variable particular dependiendo del tipo de dato. Por ejemplo:

  • valueBoolean para valores de si y no
  • valueDateTime para hora y fecha
  • valueInteger para números
  • valueAddress para direcciones
  • valuePeriod para periodos de tiempo
  • valueString para textos

Como la extensión hereda de recurso base, podemos hacer una extensión de una extensión 🤯

Hay un registro general donde está el índice de madurez de las extensiones.

Finalmente, podemos buscar por extensiones, para lo cual debemos crear un SearchParameter.

¿Qué pasa si queremos extender con extensiones?

Lo primero que hay que hacer es ver si hay una que existe y que cubre nuestro caso de uso.

Preguntas

  • ¿Qué pasa si tengo un sistema FHIR andando y luego lo perfilo? puedes listar pacientes de acuerdo a un perfil. Si quieres que todos lo cumplan, entonces tienes que actualizar tus datos.
  • ¿Qué tan factible es tener aplicaciones SMART, si al final todos tienen que perfilar? Puedes hacer una APP de acuerdo a un perfil. Por eso son importantes las guías de implementación.


Hecho por Codeness con ❤️
© 2024 Codeness