Desarrollar y mantener el código de los componentes de interoperabilidad dentro de un entorno institucional sanitario.
Hay lecciones que aprendimos al desarrollar y mantener el código de los componentes de interoperabilidad dentro de un entorno institucional sanitario.
El avión ya está volando.
Estad preparados para reconstruir, mejorar, ampliar y reparar el avión en pleno vuelo.
Las ventanas de mantenimiento de los sistemas hospitalarios suelen ser muy limitadas, y algunos deben estar operativos las 24 horas del día, los 7 días de la semana. Mientras que los sistemas críticos para la salud, como las modalidades de imagen o los quioscos de registro —o al menos sus funciones críticas— deben funcionar de forma autónoma, la eficiencia y la usabilidad pueden verse comprometidas cuando los componentes de interoperabilidad fallan. Por ejemplo, los quioscos de registro de pacientes ambulatorios pueden dejar de funcionar o dirigir a los pacientes de manera incorrecta si los datos de citas y profesionales están ausentes o desactualizados, lo que provoca un aumento de la carga de trabajo y del estrés.
Dado que los equipos de TI y sus presupuestos suelen ser más reducidos en el sector sanitario que, por ejemplo, en la banca, los desarrolladores a menudo son responsables de todo el ciclo de vida de los componentes de interoperabilidad, abarcando desarrollo, pruebas, despliegue, mantenimiento y eventual desmantelamiento.
La alta disponibilidad y la réplica (mirror) en IRIS, combinados con infraestructuras modernas (por ejemplo, hosts de servidores virtuales), ofrecen una gran resiliencia técnica. Sin embargo, no eliminan el riesgo de problemas relacionados con el software. Para reducir esos riesgos mientras se garantiza un servicio ininterrumpido, podéis aplicar varias estrategias complementarias:
- Mantener un repositorio de código y utilizar control de versiones.
- Establecer y soportar entornos de operación dedicados donde se puedan desarrollar funciones y probar de forma completa las versiones planificadas.
- Monitorizar los entornos de producción y avisar a los administradores del sistema sobre fallos críticos.
Mantener un repositorio de código.
Desde mi perspectiva, utilizar un único repositorio Git para todos los espacios de nombres de interoperabilidad es la forma más eficaz de fomentar la reutilización del código y la coherencia. Dependiendo de la madurez del equipo de desarrollo, el esquema de ramas puede evolucionar desde un simple par main/develop hasta una arquitectura completa GitFlow.
El repositorio tiene los siguientes directorios:
El repositorio debe contener todos los artefactos (clases, tablas de consulta, esquemas HL7, etc.) que no se consideren elementos de configuración.
Algunas clases se consideran elementos de configuración y requieren un manejo especial, ya que su código suele generarse automáticamente, varía según los entornos de operación y puede ser modificado por los administradores del sistema mediante los editores del Management Portal.
Incluye producciones de interoperabilidad (clases que extienden Ens.Production) y reglas de negocio o de enrutamiento (clases que extienden Ens.Rule.Definition).
Entornos de operación y gestión de cambios
El desarrollo y la gestión de versiones de los servicios construidos sobre Intersystems IRIS siguen las mismas prácticas que se utilizan en otras plataformas de software. Para mantener una alta disponibilidad en el entorno de producción, se utilizan entornos de operación adicionales que permiten un proceso de lanzamiento estructurado y controlado.
Los entornos se ejecutan en instancias de IRIS separadas, idealmente con la misma versión de IRIS.
Usar plataformas diferentes para el desarrollo y otros entornos es perfectamente aceptable, siempre que el código tenga en cuenta estas diferencias. En la práctica, esto significa, por ejemplo, evitar problemas relacionados con el acceso a archivos compartidos, los terminadores de línea y los delimitadores de rutas.
Entorno de desarrollo (DEV)
Al desarrollar interfaces con software proporcionado por terceros, unas especificaciones precisas y completas son esenciales para un proceso de desarrollo exitoso y eficiente. En el contexto de los concursos públicos de la UE, es buena práctica exigir estas especificaciones técnicas como parte de la sección técnica de la documentación del concurso.
Para desarrollo y pruebas unitarias. Podéis utilizar un entorno compartido configurado con el excelente control de versiones del servidor Git integrado, o entornos separados usando un único repositorio Git compartido.
Las pruebas unitarias que se ejecutan en el entorno de desarrollo no dependen de recursos externos como bases de datos, APIs o interfaces HL7 de aplicaciones. Configuré dos espacios de nombres en las instancias de desarrollo:
- DEV, se utiliza para el desarrollo y para ejecutar las pruebas unitarias una a una durante el proceso de desarrollo.
- TEST (o SCRATCH ;-), se utiliza para ejecutar todas las suites de pruebas después de clonar una rama del repositorio y recrear (o limpiar) el espacio de nombres. Cargar y compilar toda la base de código en un espacio de nombres limpio permite comprobar que todas las dependencias se resuelven correctamente. Ejecutar las pruebas unitarias y corregir los problemas hasta que todas tengan éxito permite minimizar el riesgo de regresión.
Las tareas de mantenimiento en esos espacios de nombres se reducen al mínimo:
- Sin registro de journaling, excepto en los casos excepcionales en los que sea necesario para pruebas.
- Sin copias de seguridad.
- La depuración de mensajes de interoperabilidad se configura para mantener un historial mínimo.
Las pruebas se basan en clases que extienden las clases %UnitTest.* (ver, por ejemplo, ks-iris-lib). Las pruebas unitarias no dependen de recursos externos; utilizan datos almacenados en el directorio ‘resources’ del repositorio o incluidos como bloques XData en las clases de prueba.
Durante el desarrollo, las pruebas se ejecutan según sea necesario utilizando el entorno de pruebas de VSCode, mientras que las ejecuciones completas se realizan mediante un script.
El script clona una rama del repositorio (normalmente develop o de una rama de funcionalidad), carga y compila todas las clases y ejecuta el conjunto completo de pruebas unitarias disponibles; véase el ejemplo de script de Windows a continuación.
@echo off
setlocal
rem A utility to clone IRIS Health items from git repository and run unit test in an empty namespace
rem
rem Usage :
rem
rem unit-tests <iris instance> <iris namespace>
rem
rem example :
rem
rem deploy-classes IRISHEALTH SCRATCH
rem
rem pre-requisites :
rem
rem * git installed and in path
rem * intersystems IRIS Health installed and
rem * disk space in %TEMP% volume for a clone of repository
rem * existing IRIS Health instance and namespace
rem
rem
rem IRIS installation folder
rem
set IRISHome=c:\intersystems\IRISHealth
rem
rem repository http URL
rem
set Repository=https://github.com/theanor/ks-iris-lib
if "%3"=="" (set Branch=develop) else (set Branch=%3)
if "%1"=="" (set IRISInstance=IRISHealth) else (set IRISInstance=%1)
if "%2"=="" (set IRISNameSpace=SCRATCH) else (set IRISNameSpace=%2)
set TargetDir=%TEMP%\iris-health-deploy
echo Cleaning target directory
if exist %TargetDir% rmdir /s/q %TargetDir%
echo Cloning repository %Repository% branch %Branch%
git clone --branch %Branch% %Repository% %TargetDir%
echo Wiping namespace %IRISNameSpace%
"%IRISHome%\bin\irissession" %IRISInstance% -U %IRISNameSpace% "##class(%%UnitTest.Manager).WipeNamespace()"
echo Importing code into %IRISNameSpace%
"%IRISHome%\bin\irissession" %IRISInstance% -U %IRISNameSpace% "##class(%%SYSTEM.OBJ).ImportDir(""""""%TargetDir%\src"""""",""""""*.*"""""",""""""ckbry"""""",,1)"
echo Running repository tests in %IRISNameSpace%
"%IRISHome%\bin\irissession" %IRISInstance% -U %IRISNameSpace% "##class().RunRepositoryTests(""""""%IRISNameSpace%"""""",""""""%TargetDir%"""""")"
Entorno de pruebas (TEST)
La configuración y el mantenimiento del entorno de pruebas de la aplicación pueden implicar costes adicionales o licencias. Al redactar las especificaciones técnicas en el contexto de concursos públicos del mercado europeo, es buena práctica incluir el requisito de al menos un entorno de pruebas en las especificaciones técnicas.
Mientras que el entorno DEV se utiliza para pruebas unitarias, el entorno TEST se emplea para pruebas de integración. Este puede utilizar recursos externos como bases de datos y APIs expuestas por instancias de prueba o réplicas de aplicaciones interconectadas.
Desde esta perspectiva, es útil separar claramente las clases de pruebas unitarias de las clases de pruebas de integración. Esto se puede lograr colocándolas en paquetes diferentes o utilizando un parámetro marcador dedicado (por ejemplo, INTEGRATIONTEST = 1) para identificar las pruebas de integración.
El código del entorno de pruebas se despliega (normalmente desde la rama de desarrollo o de una funcionalidad) usando las mismas técnicas que para el entorno de producción, ya sea mediante scripts o mediante Git integrado para importar y compilar los artefactos desde las ramas del repositorio Git.
Entornos de aseguramiento de la calidad (QA)
Mientras que el entorno TEST se utiliza para pruebas de integración, los entornos QA se emplean para pruebas de extremo a extremo y de aceptación por el usuario. También pueden utilizarse para la formación de usuarios.
Están conectados a instancias de prueba de aplicaciones externas.
Si necesitáis realizar pruebas de no regresión y análisis de incidentes (reproduciendo errores fuera del entorno de producción) y, al mismo tiempo, formación de usuarios, pueden ser necesarios entornos QA separados para mantener tres configuraciones diferentes:
- QA -1: versión anterior, utilizada para la gestión de incidentes (comprobar si un error ya estaba presente en la versión anterior).
- QA 0: versión actual, utilizada para formación y gestión de incidentes (reproducir un incidente o error).
- QA +1: utilizada para pruebas de integración y de no regresión de la próxima versión.
Una buena cobertura de los casos de uso de la aplicación que involucren componentes de operabilidad en las pruebas de extremo a extremo es clave para despliegues exitosos. Definid los escenarios de prueba de extremo a extremo basándoos en casos de uso reales, junto con los interesados del proyecto..
Monitorización y alertas
Dado que estos entornos conectan decenas de sistemas que intercambian datos a través de la red, las producciones de interoperabilidad —y las instancias de IRIS que las ejecutan— pueden generar volúmenes considerables de registros e información de alertas..
Mantener una lista depurada de las aplicaciones interconectadas y configurarlas como socios comerciales permite aprovechar el marco de alertas existente. El código del socio comercial también puede relacionarse con datos adicionales sobre la aplicación procedentes de otras fuentes, como una base de datos de gestión de la configuración.
Para evitar la fatiga por alertas, es absolutamente necesario filtrar el ruido para enviar únicamente alertas claras y relevantes a los destinatarios correctos. ¡Hacerlo con un alto grado de precisión no es nada fácil!
Es útil empezar con algo sencillo e ir reduciendo gradualmente el nivel de ruido ajustando el filtrado y el enrutamiento de alertas.
En su forma más simple, las alertas se enrutan mediante un EnsLib.MsgRouter.RoutingEngine hacia EnsLib.EMail.AlertOperation, enviando todas las alertas a las direcciones de correo configuradas mediante SMTP.
Para avanzar, definid procesadores de alertas.
El CreateManagedAlertRule de Ens.Alerting.AlertManager permite decidir si se debe crear una alerta gestionada.
El OnProcessNotificationRequest() de Ens.Alerting.NotificationManager permite decidir si se debe notificar una alerta gestionada.
Las funciones personalizadas pueden ayudar con esas decisiones. Aquí tenéis algunos ejemplos.
El elemento de configuración de origen en el origen de la sesión: pasad AlertRequest.SessionId a una función como
Include Ensemble
Class dc.alert.FunctionSet Extends Ens.Rule.FunctionSet
{
ClassMethod HeaderSourceConfigName(headerId As %Integer) As %String
{
#Dim header as Ens.MessageHeader
s header = ##class(Ens.MessageHeader).%OpenId(headerId)
return $select($isobject(header):header.SourceConfigName,1:"")
}
ClassMethod ConfigNameBusinessPartner(configName As Ens.DataType.ConfigName) As %String
{
#Dim result As StPierre.Type.ApplicationCode
return:$get(configName)="" ""
return $get($$$ConfigSetting(configName,"Host","BusinessPartner"))
}
}Para enviar alertas de baja criticidad solo durante el horario laboral, es útil definir un calendario para el horario de trabajo, por ejemplo
STOP:WEEK-*-01T17:00:00,START:WEEK-*-01T08:00:00,STOP:WEEK-*-02T17:00:00,START:WEEK-*-02T08:00:00,STOP:WEEK-*-03T17:00:00,START:WEEK-*-03T08:00:00,STOP:WEEK-*-04T17:00:00,START:WEEK-*-04T08:00:00,STOP:WEEK-*-05T17:00:00,START:WEEK-*-05T08:00:00
Para evitar enviar la misma alerta repetidamente, el gestor de alertas utiliza la función Ens.Alerting.Rule.FunctionSet.IsRecentManagedAlert.
La notificación de alertas puede integrarse con herramientas de monitorización externas como Prometheus o Nagios ampliando Ens.Alerting.NotificationOperation.
Desplegar cambios mientras la producción está en funcionamiento
Cambios en el código
En su excelente serie de artículos sobre entrega continua usando GitLab, @Eduard Lebedyuk explica cuáles son nuestras opciones para actualizar código y configuración mientras la interoperabilidad está en funcionamiento. Este artículo se centra en las producciones de interoperabilidad en particular.
Importar y compilar clases no afecta a los procesos de los hosts de negocio que ya se están ejecutando; estos solo utilizarán el código recién compilado tras un reinicio.
Sin embargo, las clases recién instanciadas se ven afectadas. Por ejemplo, las nuevas instancias de procesos de negocio utilizarán inmediatamente el código recién compilado. Esto también es cierto para todo el código que se invoque en el contexto de otro proceso, como las transformaciones de datos.
Incluso si evitáis un acoplamiento fuerte, las clases pueden depender unas de otras, y puede ser necesario controlar con precisión en qué orden se deshabilitan y habilitan los hosts de negocio. Para lograrlo, utilizad un despliegue mediante scripts para
- Hacer copia de seguridad de todas las clases en un archivo XML
-
Importar y compilar los elementos desde la rama del repositorio
Ejemplo de script para hacer copia de seguridad de clases
@echo off
setlocal
rem A utility to backup IRIS Health classes from a namespace to a xml file
rem
rem Usage :
rem
rem backup-classes <iris instance> <iris namespace> <target file>
rem
rem example :
rem
rem backup-classes IRISHM PROD
rem
rem
rem IRIS installation folder
rem
set IRISHome=c:\intersystems\IRISHealth
set IRISInstance=%1
set IRISNameSpace=%2
set BackupFile=%3
for /f %%i in ('powershell get-date -format "{yyyyMMdd}"') do set CurrentDate=%%i
if "%3"=="" (set BackupFile=classes-%CurrentDate%.xml)
echo %CurrentDate%
echo exporting classes to %BackupFile%
"%IRISHome%\bin\irissession" %IRISInstance% -U %IRISNameSpace% "##class(%%SYSTEM.OBJ).ExportAllClasses(""""""%BackupFile%"""""")"
echo:
echo Finished exporting classesEjemplo de script para importar y compilar elementos UDL desde el repositorio:
@echo off
setlocal
rem A utility to clone IRIS Health items from git repository and deploy (load, compile) them to an iris instance
rem
rem Usage :
rem
rem deploy-classes <iris instance> <iris namespace>
rem
rem example :
rem
rem deploy-classes IRISHEALTH SCRATCH
rem
rem pre-requisites :
rem
rem * git installed and in path
rem * intersystems IRIS Health installed and
rem * disk space in %TEMP% volume for a clone of repository
rem * existing IRIS Health instance and namespace
rem
rem
rem IRIS installation folder
rem
set IRISHome=c:\intersystems\IRISHealth
rem
rem repository http URL
rem
set Repository=http://git.stpierre-bru.be/integration/iris-health.git
set Branch=develop
set TargetDir=%TEMP%\iris-health-deploy
set IRISInstance=%1
set IRISNameSpace=%2
echo Cleaning target directory
if exist %TargetDir% rmdir /s/q %TargetDir%
echo Cloning repository %Repository% branch %Branch%"
git clone --branch %Branch% %Repository% %TargetDir%
echo Loading UDL items
"%IRISHome%\bin\irissession" %IRISInstance% -U %IRISNameSpace% "##class(%%SYSTEM.OBJ).ImportDir(""""""%TargetDir%\src"""""",""""""*.*"""""",""""""ckbry"""""",,1)"
Cambios de configuración
En el contexto de producciones de interoperabilidad que se ejecutan de forma continua, y dado que es necesario un control estricto sobre qué clases se compilan y cuándo respecto a los hosts de negocio en ejecución, no recomendaría usar IPM por el momento.
No conozco ningún sitio que utilice las funciones del Management Portal para el despliegue. Puede ser porque requiere un entorno que refleje exactamente el entorno de producción en vivo para generar los paquetes usando el Management Portal.
Al empezar desde cero, incluso con un único repositorio y módulo para vuestro código de interoperabilidad, recomiendo utilizar scripts de despliegue.
Los scripts de despliegue se pueden ejecutar de forma “manual” usando el Management Portal, la terminal, VSCode o una combinación de estos.
Por ejemplo, al desplegar nuevos hosts de negocio, copio su configuración desde el bloque XData de la clase de producción del entorno QA, ajustando los elementos XML resultantes en el entorno de producción según sea necesario.
En el caso poco frecuente de un cambio complejo o tedioso, suelo usar métodos de clase ad hoc para actualizar programáticamente la producción de interoperabilidad. Los métodos se prueban usando el entorno QA.
Correcciones urgentes o cambios pequeños
Correcciones urgentes y pequeños cambios de configuración que afectan a pocos elementos y que se pueden desplegar utilizando:
- La terminal, usando los métodos de clase de SYSTEM.OBJ o las clases Security.* para elementos de seguridad como Roles, privilegios SQL y aplicaciones.
- VSCode con edición del lado del servidor o Studio. Studio permite arrastrar y soltar clases de una instancia a otra, lo cual encuentro muy útil.
- El Management Portal, para importar elementos compilables (clases, LUTs, esquemas HL7,…) y elementos de configuración (Aplicaciones Web, Roles,…).
