Buscar

Limpiar filtro
Artículo
Luis Angel Pérez Ramos · 11 jul, 2023

Instalación y adaptación de EMPI en modo Standalone - Alimentando a la bestia con FHIR - Configuración

¡Volvemos al ataque con nuestro EMPI! En artículos anteriores hemos visto como configurar y personalizar nuestro EMPI, hemos visto como podemos como podemos incluir nuevos pacientes en nuestro sistema mediante mensajería HL7, pero claro, ¡no todo es HL7 v.2 en esta vida! ¿Cómo podríamos configurar nuestra instancia del EMPI para trabajar con mensajería FHIR? ¿Qué es FHIR? Para los que no estéis demasiado familiarizados con el término FHIR únicamente indicar que son las iniciales de Fast Healthcare Interoperability Resource. FHIR es un estándar de interoperabilidad sanitario desarrollado por HL7 en el que, basándose en el formato JSON y comunicaciones REST, se establecen una serie de "recursos" que portan diferentes tipos de información (desde datos del paciente, centros hospitalarios, diagnósticos, citas médicas...) podéis echar un vistazo a todos estos recursos en su página oficial InterSystems y FHIR Desde InterSystems somos conscientes de la utilidad que FHIR proporciona para el mundo de la interoperabilidad sanitaria y por ello disponemos de una amplia gama de productos y funcionalidades que permiten aprovechar y exprimir todo el potencial del mismo: InterSystems FHIR Server que permite el almacenamiento y gestión de los recursos FHIR. InterSystems FHIR SQL Builder que pone al alcance de los usuarios toda la información almacenada en el repositorio FHIR mediante consultas SQL. InterSystems FHIR Adapter que permite el envío y recepción de mensajería FHIR en nuestras producciones. Pues bien, para nuestro artículo vamos a aprovechar la funcionalidad que nos proporciona el Adapter para recibir mensajería FHIR. Configuración del EMPI Instalación del adaptador de FHIR Repasemos en primer lugar de qué servicios disponemos en nuestra instalación standalone del EMPI: Cómo podéis observar, tras la instalación del EMPI en modo standalone se creó un servicio de Registry con todas las opciones necesariarias para configurar nuestro EMPI, así como un namespace que llamamos HSPIDATA y en el que disponemos de una producción para gestionar las funcionalidades de interoperabilidad que necesitamos. Para nuestro caso hemos creado un nuevo namespace denominado WEBINAR en el que desplegaremos una producción de interoperabilidad normal y corriente. Será en este namespace donde instalaremos el FHIR Interoperability Adapter, esta instalación nos publicará una aplicación web que será a la que enviemos nuestras llamadas REST con los mensajes FHIR. Para ello ejecutaremos los siguientes comandos desde el terminal: zn "WEBINAR" set status = ##class(HS.FHIRServer.Installer).InteropAdapterConfig("/csp/healthshare/webinar/fhir/r4") Una vez instalado comprobaremos desde el portal de gestión la nueva aplicación creada: Comprobemos nuestra producción en el namespace WEBINAR: Por defecto se han creado dos nuevos Business Components: InteropService: en nuestra producción le hemos cambiado el nombre a su clase HS.FHIRServer.Interop.Service que será el Business Service encargado de la recepción del mensaje FHIR remitido a nuestro endpoint /csp/healthshare/webinar/fhir/r4 InteropOperation: que hemos cambiado a HS.FHIRServer.Interop.Operation. Para nuestro ejemplo hemos ignorado este Business Operation ya que vamos a enviar la información del mensaje FHIR vía TCP a nuestra producción del EMPI Creación de Business Components para gestionar la mensajería FHIR Muy bien, ya tenemos nuestro Business Service habilitado para recibir los mensajes FHIR, ahora lo que nos interesa es extraer la información de dicho mensaje para poder remitírla a nuestra producción del EMPI. Echemos un ojo al tipo de mensaje que va a recibir HS.FHIRServer.Interop.Service: Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic"; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ]; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ]; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status El mensaje es del tipo HS.FHIRServer.Interop.Request y si consultamos la documentación veremos que lleva asociado una propiedad denominada QuickStreamId la cual utilizaremos para extraer el mensaje FHIR asociado. Para ello redirigiremos el mensaje recibido en el Business Service al Business Process Webinar.BP.FHIRToHubRequest Este Business Process sólo tendrá como función la de redirigir el mensaje al Business Operation FromFHIRToMPI encargado del envío del mensaje vía TCP a la producción del EMPI. Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync("FromFHIRToMPI", pRequest, .pResponse) Quit $$$OK } Echemos un vistazo a la clase Webinar.BO.ToMPI y expliquemos como obtiene el mensaje de FHIR y se transforma en un String que enviaremos a nuestra producción del EMPI: Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter"; Property Adapter As EnsLib.TCP.CountedOutboundAdapter; Parameter INVOCATION = "Queue"; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}"; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String(MAXLEN = 256); Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W") // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'="" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'="" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat="json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat="xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content")) } // Transform Dynamic Object to string set tRequest = tDynObj.%ToJSON() // Send message to EMPI production Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse) $$$ThrowOnError(pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject { set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}.%FromJSON(stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType="HS.FHIRServer.Interop.Request"> <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } } Analicemos paso a paso nuestra clase: El primer punto será conocer la versión de nuestro mensaje FHIR que habremos configurado en las opciones de nuestro Business Operation. Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W") Recuperamos el tipo de mensaje FHIR que estamos tratando, en nuestro caso será R4. A continuación extraemos el contenido del mensaje FHIR: // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'="" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'="" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat="json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat="xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content")) } Obtenemos el %DynamicObject de nuestro mensaje FHIR: ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject { set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}.%FromJSON(stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } Finalmente lo transformamos en un String y se lo enviamos vía TCP a nuestra producción del EMPI, devolviendo un 200 al sistema que nos envió el mensaje original: // Transform Dynamic Object to string set tRequest = tDynObj.%ToJSON() // Send message to EMPI production Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse) $$$ThrowOnError(pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC Pues ya tendríamos nuestro mensaje remitido a nuestra producción del EMPI. Observemos la traza de un mensaje de ejemplo: En la siguiente entrega recuperaremos el String enviado, lo transformaremos nuevamente a un %DynamicObject y lo introduciremos en el EMPI para que ejecute la operación de registro/actualización. ¡Espero que os sea de utilidad!
Anuncio
David Reche · 17 mayo, 2019

Lanzamiento de HealthShare 2019.1

Nos complace anunciar la disponibilidad de HealthShare 2019.1.Con esta versión, HealthShare ofrece:Soluciones sanitarias, incluyendo mejoras sustanciales en el Visor Sanitario en el teléfono móvil, mejoras en las Infografías sanitarias, y la primera de una nueva línea de respuestas a cuestiones importantes de índole operativa y sanitaria, empezando por el Panel de control para el usuario frecuente del Departamento de Urgencias Health Insight. Mejora de los procesos con la nueva página para los usuarios sanitarios y comerciales, Inicio de sesión único (SSO) con historias clínicas electrónicas (HCE), y mejoras en los formularios para la recopilación de datos de los pacientes.Una base superior que ofrece nuevas herramientas para controlar, gestionar, e informar sobre la calidad de datos, flujo de datos, interacción del usuario y muchas más.Herramientas que incentivan la innovación, incluyendo intercambios flexibles de Arquitectura de Documentos Clínicos y Recursos de Interoperabilidad Sanitaria Rápida mejorados, así como servicios de proveedor de identidad compatibles con aplicaciones de terceros que trabajan conjuntamente con Personal Community.Soporte Internacional para Patient Index y Personal CommunityAsimismo, nos complace anunciar la disponibilidad del HealthShare Provider Directory en Estados Unidos, una solución para la gestión de datos personales maestros desarrollado para ser su única fuente fiable a la hora de recabar datos demográficos y profesionales.Por último, en reconocimiento de la naturaleza básica del registro completo y longitudinal de atención sanitaria, hemos cambiado el nombre del producto conocido como HealthShare Information Exchange: ahora es HealthShare Unified Care Record. Este nombre aparecerá en las Notas de la Versión ("Release notes") y en cualquier referencia futura del producto. Para conseguir todos los detalles de este producto, por favor conéctese al Portal de E-Learning de InterSystems para ver la Documentación del producto HealthShare 2019.1 y las Notas de la Versión ("Release notes"):Unified Care Record 2019.1 NotasHealth Insight 2019.1 NotasPatient Index 2019.1 NotasPersonal Community 2019.1 NotasProvider Directory 2019.1 NotasPara conseguir HealthShare 2019.1, visite la web del WRC Centro de Soporte Internacional.
Artículo
Mathew Lambert · 31 jul, 2020

ToolBox-4-Iris, caja de herramientas para IRIS

¡Hola desarrolladores! Después de trabajar un poco con IRIS, queremos compartir con vosotros la "caja de herramientas" para InterSystems IRIS: ToolBox-4-Iris. ¿En qué consiste? ToolBox-4-Iris es una API para IRIS que incluye un conjunto de herramientas muy útiles, no disponibles en IRIS y que simplifican enormemente el desarrollo de aplicaciones. Permite ahorrar tiempo y esfuerzo en las "herramientas típicas" que todo desarrollador necesita. Esto incluye clases adicionales, métodos individuales o incluso macros más eficientes, que se describen en sus respectivos paquetes. Contenido MacrosMacros generales de ObjectScript, macros de estado, macros de National Language Support y macros de JavaScript. Tipos de datosTipos de datos dinámicos adicionales para fechas, horas y registro de horas, que incluyen formato, ocupación de memoria, SQL, rendimiento y más. Entrada y salida de datosFunciones útiles para trabajar con archivos y directorios, incluyendo funciones generales para archivos y directorios, buscar contenidos en archivos y directorios, eliminar archivos y directorios, serializar instancias de objetos hacia/desde JSON y más. Gestión de eventos del lado del servidorFuncionalidad que permite desarrollar aplicaciones que reaccionen de forma dinámica a eventos del sistema o de los datos. Si bien IRIS cuenta con gestión de eventos basados en procesos, no tiene capacidad para responder de forma dinámica a eventos del sistema o de los datos (sólo mediante la implementación de disparadores).Incluye la gestión sincrónica o asincrónica de eventos por procesos del usuario o cola de eventos, uso sincrónico o asincrónico de eventos del sistema o gestión de eventos de datos persistentes. Utilidades varias Utilidades como funciones de calendario (año bisiesto, semana, día de la semana, días festivos, etc.), formateo libre de fecha, hora y registros de hora, funciones para ejecutar comandos del sistema operativo y alguna utlidad JSON (para copiar, revisar, comparar, exportar, importar, leer, escribir, etc.).. ¡Esperamos ansiosos vuestros comentarios! Y que ToolBox-4-Iris os resulte útil.
Anuncio
Esther Sanchez · 26 oct, 2020

Sesiones de Preguntas y Respuestas ("Live Q&A Sessions") en el Virtual Summit 2020

¡Hola Comunidad! Como sabéis, mañana martes 27 de octubre comienzan las Sesiones Técnicas ("Focus Sessions") del Virtual Summit. Más de 100 sesiones, de 20 minutos cada una, durante tres días, sobre mejores prácticas, nuevas tecnologías y hojas de ruta. Queremos comentaros que, al final de cada jornada de Sesiones Técnicas (martes, miércoles y jueves), habrá una sesión de Preguntas y Respuestas con los Product Managers de InterSystems. Y cualquiera puede participar en las sesiones y enviar sus preguntas. ¿Te animas? Para inscribirte a las sesiones de Preguntas y Respuestas ("Live Q&A"): 1. Regístrate o inicia sesión en el Virtual Summit. Irás a un lobby virtual. 2. Haz clic en el botón de "Focus Sessions" para entrar en el auditorio virtual. 3. Haz clic en Data Platform 1. Allí verás el horario de todas las sesiones de Preguntas y Respuestas ("Live Q&A sessions"): ➡️ Día 1: Martes, 27 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM ➡️ Día 2: Miércoles, 28 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM ➡️ Día 3: Jueves, 29 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM Nota: Puedes enviar tus preguntas por adelantado a VS2020questions@InterSystems.com Aprovechad para resolver vuestras dudas (Si aún no os habéis registrado en el Virtual Summit 2020, podéis hacerlo aquí >>)
Pregunta
Bernabé Martín · 9 dic, 2020

Cache sobre Virtualbox

Hola Comunidad, Tengo un Cache 5.2 instalado en una maquina Virtualbox con XP y otra con WIN7, para programas personales. En las dos maquinas de tanto en tanto me sale este error (adjunto al final) cuando ejecuto desde el CUBO “Portal de Gestión de Sistema”. Para solucionarlo reinstalas todo y vuelve a funcionar hasta que decide dejar de funcionar. Solicito de la comunidad algún atajo para no tener que gestionar toda la engorrosa maniobra de reinstalarlo todo de nuevo, por suerte no se utiliza muy a menudo la Gestión del Sistema. La version que tienes instalada es bastante antigua - lo adecuado sería el upgrade a la version más reciente de InterSystems IRIS. La puedes descargar desde Docker Hub. Es totalmente gratuita y tiene la funcionalidade de Cache actualizada y ampliada. Con respecto al caso que comentas lo más usual es que tenga que ver con el web server o bien con alguna dll relacionada.Lo que podrías hacer es:1) reiniciar el servicio 'web server for ...' desde las herramientas administrativas de windows;2) Si no te va con el 1), baja el servicio herramientas administrativas. Abre la consola de administrador y reinicia el apache con el listner en el puerto que usas. Algo como: httpd -k start -n INSTANCIACACHEhttpd -c "Listen 8972"Si tras hacer todo esto sigues con el error, te recomiendo una consulta al soporte WRC. Gracias, No funciona he parado el servicio apache "web server for cache" Yo no soy especialista en el sistema toda mi vida profesional he sido lo antiguamente llamado "analista/programador", lo cual quiere decir que ya tengo unos años y por ello tengo esta versión. Miraré el tema del IRIS para utilizarlo como Cache. Reitero gracias y salud. Miraré Ya he comentado antes que yo me quedé en MUMPS. He instalado el contenedor con IRIS (en un MAC), funciona pero tengo una primera dificultad, para mis necesidades necesito utilizar un TERMINAL más potente (por ejemplo Wrq Reflection), u otro de licencia libre. No he llegado aún a la importacion de RUTINAS Y GLOBALES y para ella tendré que generar los NAMESPACE. Dada mi situación de jubilado, todo esto, solo tiene el sentido de la inquietud y la invstigación personal. Gracias y salud
Artículo
Muhammad Waseem · 15 nov, 2021

Cómo guardar datos en Caché usando la conexión ODBC de Appeon PowerBuilder

En mis artículos anteriores, mostré los pasos para conectar y recuperar datos de Caché desde Appeon PowerBuilder usando ODBC. En este artículo, mostraré cómo guardar datos en Caché con Appeon PowerBuilder (https://www.appeon.com/products/powerbuilder) usando ODBC. Estoy usando Company.cls de Samples-Data (https://github.com/intersystems/Samples-Data/tree/master/cls/Sample) ¡Empecemos! Paso 1 : En primer lugar, debemos establecer una conexión (https://community.intersystems.com/post/connecting-cach%C3%A9-appeon-powerbuilder-using-odbc)Paso 2 : Necesitamos crear un objeto de ventana de datos (datawindow object), que se vinculará a la clase Company. En el menú File, selecciona New y elige el objeto de ventana de datos Freeform en la pestaña DataWindow. Paso 3: Selecciona SQL Select de la lista de fuentes de datos. Paso 4: Selecciona sample.company de la lista de tablas: Paso 5 : Selecciona las columnas deseadas en la lista de columnas y haz clic en Return. Paso 6: Esto abrirá una vista de diseño. Guarda la ventana de datos (datawindow) como d_company_entry después de los ajustes deseados. Paso 7: Asegúrate de anular la selección de la columna id en la lista de columnas sin fecha (Updatable columns) y selecciona id en la lista desplegable del campo Identity Column, ya que la identificación se generará automáticamente desde Caché. Paso 8: En el control de ventana, añade el objeto de ventana de datos (d_company_entry) que ya creamos al control de ventana de datos Paso 9: Ahora necesitamos insertar una fila en el control de ventana de datos usando la función Insertrow(0) del control de ventana de datos. Paso 10: Añade algunos datos usando el control de ventana de datos de PowerBuilder. Paso 11: Guarda los datos en el control de la ventana de datos utilizando la función update() del control de la ventana de datos. Eso es todo. Se añade el ID 21
Artículo
Ricardo Paiva · 18 nov, 2021

Descripción de la aplicación fhir-integratedml-example

Hablando con mi amigo @Renato.Banzai, especialista en Machine Learning, me expuso uno de los mayores retos a los que se enfrentan actualmente las empresas: la implementación del Machine Learning (ML) y la Inteligencia Artificial (IA) en entornos reales. Intersystems IRIS ofrece IntegratedML. IntegratedML es una excelente herramienta para practicar, probar y realizar implementaciones de modelos de ML e IA. La parte más complicada de crear ML/IA es procesar los datos, depurarlos y hacerlos fiables. ¡Ahí es donde podemos aprovechar el estándar FHIR! La idea del proyecto muestra cómo podemos crear/practicar/validar modelos ML/IA con FHIR y utilizarlos con datos de distintas fuentes. Creemos que este proyecto tiene un gran potencial y también hay algunas ideas que pueden analizarse: * Reutilizar/ampliar las transformaciones DTL en otras bases de datos FHIR para modelos de ML personalizados * Utilizar las transformaciones DTL para normalizar los mensajes FHIR y publicar los modelos ML como servicios * Crear un tipo de modelos + un repositorio con las reglas de las transformaciones para utilizarlos en cualquier conjunto de datos FHIR Si exploramos nuevas posibilidades de este proyecto, imaginemos datos de distintas fuentes. ![](/sites/default/files/inline/images/whatsapp_image_2021-07-21_at_09.55.32.jpeg)   Como se muestra en la imagen anterior, el recurso FHIR, que consume la API REST, se puede utilizar con un FHIRaaS. Y no solo es posible utilizar FHIRaaS en AWS, sino que además podemos aprovechar el nuevo servicio de [HealthShare Message Transformation Services](https://aws.amazon.com/marketplace/pp/prodview-q7ryewpz75cq2?sr=0-9&ref_=beagle&applicationId=AWSMPContessa), que automatiza la conversión de HL7v2 a FHIR® para ingresar datos en Amazon HealthLake, donde podrás extraer más valor para tus datos. Con estas sencillas demostraciones, creo que estos recursos pueden utilizarse muy bien en escenarios más grandes, permitiendo implementaciones más sencillas en entornos de producción verdaderamente novedosos, como el de [AWS Healthlake](https://aws.amazon.com/healthlake/). ¿Por qué no? 😃 
Artículo
Henrique Dias · 25 mayo, 2022

El explorador de ZPM (zpm-explorer)

¡Hola Comunidad! @José.Pereira y yo queremos presentaros ZPM Explorer, nuestra interfaz gráfica para explorar las excelentes aplicaciones que hay en InterSystems Package Manager. ## La idea La idea de un explorador de ZPM es facilitar a las personas encontrar lo que ZPM ofrece. Cada semana, cada día, una nueva app se une al mundo ZPM, así que... ¿por qué no ayudar a los desarrolladores y no-desarrolladores a aprovechar las ventajas de este increíble mundo?! ## La aplicación ZPM es sencillo y potente, así que intentamos trasladar esa sencillez a algo fácil de usar, ofreciendo una búsqueda potente, para hacer más fácil que nunca localizar una aplicación específica, descubrir nuevas apps, encontrar nuevas soluciones... con un simple clic. La página principal del Explorador de ZPM es una tabla de datos con la información ofrecida por el *endpoint* [https://pm.community.intersystems.com/packages/-/all](https://pm.community.intersystems.com/packages/-/all) Los campos son: - Name: el nombre de la aplicación - Descripción: descripción de lo que hace la aplicación - Repository: enlace al repositorio en Github - Version: versión actual de la aplicación dentro de Package Manager ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/zpmexplorer.png) ## Cómo instalar nuevas apps El uso de la app es bastante sencillo. 1. Busca la aplicación que necesitas 2. Selecciona la aplicación 3. Haz clic en el botón Install 4. Confirma 5. Hecho 6. Empieza a hacer un buen uso de ella ## Cómo gestionar las aplicaciones instaladas ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/installedApps.png) ZPM Explorer ofrece una página para gestionar las apps existentes instaladas a través de ZPM. Puedes actualizar, eliminar e incluso usar un asistente para que sea más sencillo crear tu propio module.xml, especialmente la etiqueta Dependencies. ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/export.png) ## Demo [ZPM Explorer Demo](https://www.youtube.com/watch?v=24nlYIH83iY) .
Artículo
Alberto Fuentes · 11 abr, 2025

Configurar una conexión de tabla vinculada ODBC/JDBC a MySQL desde Iris

Debido a que la interpretación de SCHEMA por parte de MySQL difiere de la comprensión interpretación común en SQL (como se ve en IRIS, SQL Server u Oracle), nuestro asistente automático de tablas vinculadas puede encontrar errores al intentar recuperar la información de metadatos para construir la tabla vinculada. (Esto también se aplica a procedimientos y vistas vinculadas) Al intentar crear una tabla vinculada mediante el asistente, os encontraréis con un error que se parece a esto: ERROR #5535: SQL Gateway catalog table error in 'SQLPrimaryKeys'. Error: ' SQLState: (HY000) NativeError: [0] Message: [MySQL][ODBC 8.3(a) Driver][mysqld-5.5.5-10.4.18-MariaDB]Support for schemas is disabled by NO_SCHEMA option Para crear una tabla vinculada a una base de datos MySQL que emplea una estructura “sin esquema” (comportamiento predeterminado), seguid las instrucciones que aparecen a continuación: Cread una conexión SQL Gateway: Configurad la conexión SQL Gateway como de costumbre. Aseguraos de que la casilla "No usar identificadores delimitados por defecto" esté marcada. Haced clic en "Probar conexión" para confirmar que la conexión es exitosa. Usad la API basada en Terminal para crear la tabla vinculada: Utilizad la siguiente API: $SYSTEM.SQL.Schema.CreateLinkedTable() El método CreateLinkedTable() utiliza los siguientes parámetros: CreateLinkedTable(dsn As %String, externalSchema As %String, externalTable As %String, primaryKeys As %String, localClass As %String = "User.LinkedClass", localTable As %String, ByRef columnMap As %String = "") Ejemplo: En este ejemplo, usamos la tabla del sistema de MySQL help_keyword con name como clave primaria USER>do $SYSTEM.SQL.Schema.CreateLinkedTable("MyDSN", "", "help_keyword", "name", "User.LinkedClass", "LocalTable") Aseguraos de que todos los parámetros estén especificados correctamente para evitar cualquier error durante el proceso de configuración. En cualquier caso, ¿estáis utilizando actualmente tablas vinculadas? os recomiendo que echéis un vistazo su evolución en InterSystems IRIS, las Foreign Tables.
Artículo
Ricardo Paiva · 14 nov, 2019

Despliegue de aplicaciones con %Installer

¡Hola Comunidad! Suponga que desarrolló su propia aplicación con la tecnología de InterSystems y ahora quiere realizar varias implementaciones en sus distintos clientes. Durante el proceso de desarrollo usted escribió una guía de instalación detallada para aplicarla, ya que no solo necesita importar clases, también configurar el entorno de acuerdo a sus necesidades. Para atender esta tarea específica, InterSystems creó una herramienta especial llamada %Installer. Siga con la lectura para saber cómo utilizarla. %Installer Con esta herramienta, podrá definir el manifiesto de instalación, que describe la configuración de Caché que se desea, en lugar de los pasos para su instalación. Lo único que debe hacer es describir lo que quiere, y Caché generará automáticamente el código necesario para modificar el entorno por usted. Por lo tanto, solo debe distribuir el manifiesto en sí, mientras que todo el código de instalación se generará para el servidor específico de Caché en el momento de la compilación. Para definir un manifiesto, cree un nuevo bloque XData con una descripción detallada de la configuración de destino e implemente un método para generar el código en Caché ObjectScript para este bloque XData (este código siempre es el mismo). Una vez que el manifiesto esté listo, puede acceder a él desde la consola/terminal o bien desde código Caché ObjectScript, o automáticamente durante la instalación de Caché. El manifiesto debe ejecutarse en el namespace %SYS. Los manifiestos pueden manejar tanto parámetros del sistema (superport, OS, directorio mgr, etc.) como parámetros arbitrarios proporcionados por el usuario. En general, cada clase de instalación debe cumplir los siguientes requisitos: Contener un enlace para %occInclude.inc Contener un bloque XData con la configuración del servidor de Caché El bloque puede tener cualquier nombre que sea válido Agregar [XMLNamespace = INSTALLER] después del nombre del bloque, si es necesario consulte las indicaciones de Studio Llamar al elemento raíz (solo debe tener uno) <Manifest> que incluye a todos los demás elementos También debe implementar el método setup(), el cual generará el código que necesite el programa para el bloque XData. Conocimientos básicos sobre el instalador Puede ejecutar un manifiesto de instalación de varias formas: En el namespace %SYS desde la consola/terminal o desde código en Caché ObjectScript do ##class(MyPackage.MyInstaller).setup() Se realiza automáticamente durante la instalación de Caché. Para ello, exporte la clase del instalador en DefaultInstallerClass.xml que está almacenada en la carpeta con el paquete de instalación de Caché (por ejemplo, donde se almacenan setup_cache.exe o cinstall). Durante la instalación de Caché, esta clase se importará al namespace %SYS y se ejecutará mediante el método setup(). Ejemplo Consideremos un ejemplo sencillo. Establezca la clase App.Installer que contiene un instalador, el cual generará un nuevo namespace con el nombre definido por el usuario, después creará la aplicación web predeterminada e importará el código a este nuevo namespace: Include %occInclude Class App.Installer { /// You can see generated method in zsetup+1^App.Installer.1 XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <If Condition='(##class(Config.Namespaces).Exists("${Namespace}")=0)'> <Log Text="Creating namespace ${Namespace}" Level="0"/> <Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="0" Data="${Namespace}"> <Configuration> <Database Name="${Namespace}" Dir="${MGRDIR}${Namespace}" Create="yes"/> </Configuration> </Namespace> <Log Text="End Creating namespace ${Namespace}" Level="0"/> </If> <Role Name="AppRole" Description="Role to access and use the App" Resources="%DB_CACHESYS:RW,%Admin_Secure:U" /> <Namespace Name="${Namespace}" Create="no"> <CSPApplication Url="/csp/${Namespace}" Directory="${CSPDIR}${Namespace}" AuthenticationMethods="64" IsNamespaceDefault="true" Grant="AppRole" /> <IfDef Var="SourceDir"> <Log Text="SourceDir defined - offline install from ${SourceDir}" Level="0"/> <Import File="${SourceDir}"/> </IfDef> </Namespace> </Manifest> } ///Entry point method, you need to call /// At class compile time it generate Caché ObjectScript code from the manifest /// After that you can run this installer from a terminal: /// Set pVars("Namespace")="NewNamespace" /// Set pVars("SourceDir")="C:\temp\distr\" /// Do ##class(App.Installer).setup(.pVars) ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } } En este ejemplo, el instalador realiza las siguientes acciones: Comprueba si existe un namespace con el mismo nombre que el valor de la variable Namespace (para que quede claro, especificaremos que la variable Namespace se estableció en NewNamespace) Si no es así, entonces registrará la creación de un nuevo namespace llamado NewNamespace Definir un nuevo namespace: El nombre es NewNamespace Crea un nuevo namespace La base de datos para las rutinas es NewNamespace No activa Ensemble La base de datos para globales es NewNamespace Crea una nueva base de datos Su nombre es NewNamespace; La crea en la carpeta mgr/NewNamespace (tenga en cuenta que la variable MGRDIR está disponible de forma predeterminada) La creación del namespace está completa y registrada Crea una nueva función: AppRole (con los recursos %DB_CACHESYS:RW y %Admin_Secure:U) Crea una nueva aplicación web predeterminada /csp/NewNamespace (también asigna AppRole de forma automática) Si la variable SourceDir está definida, entonces importa todos los archivos desde allí a NewNamespace Para que este instalador se inicie en un terminal, ejecute los siguientes comandos: Set pVars("Namespace")="NewNamespace" Set pVars("SourceDir")="C:\temp\distr\" Do ##class(App.Installer).setup(.pVars) Durante la ejecución el terminal muestra información importante: 2016-02-17 19:26:17 0 App.Installer: Installation starting at 2016-02-17 19:26:17, LogLevel=0 2016-02-17 19:26:17 0 : Creating namespace NewNamespace 2016-02-17 19:26:17 0 : End Creating namespace NewNamespace 2016-02-17 19:26:17 0 : SourceDir defined - offline install from C:\temp\distr\ 2016-02-17 19:26:18 0 App.Installer: Installation succeeded at 2016-02-17 19:26:18 2016-02-17 19:26:18 0 %Installer: Elapsed time .545148s Para recibir aún más información sobre lo que está sucediendo, especifique LogLevel (de 0 (default) a 3 (raw); más alto = más información). Do ##class(App.Installer).setup(.pVars, 3) Ahora hablaremos sobre las cosas que pueden hacerse en el manifiesto de instalación. Nodos disponibles Un manifiesto se compone de los siguientes elementos: Nodo Nodo padre Atributos (valores predeterminados) Descripción Arg Invoke, Error Value – es el valor de un argumento Pasa un argumento hacia un método que se llama mediante Invoke o Error ClassMapping Configuration Package - un paquete que debe mapearse From - nombre de la base de datos de origen que se utilizó para el mapeo Crea un mapeo de clases desde una base de datos hacia el namespace que contiene el elemento Configuration Compile Namespace Class - son los nombres de las clases para compilación Flags - son marcas de compilación (ck) IgnoreErrors - se utiliza para ignorar errores (0) Son la clases de los compiladores. Llama a $System.OBJ.Compile(Class, Flags) Configuration Namespace Se utiliza para crear namespaces y bases de datos. Cierra las etiquetas que activan los mapeos y actualiza el archivo CPF CopyClass Namespace Src - es la clase de origen Target - es la clase de destino Replace - elimina la clase de origen (0) Copia o desplaza la definición de la clase de origen a la de destino CopyDir Manifest Src - es el directorio fuente Target - es el directorio de destino IgnoreErrors - se utiliza para ignorar errores (0) Copia un directorio CopyFile Manifest Src - es el archivo de origen Target - es el archivo de destino IgnoreErrors - se utiliza para ignorar errores (0) Copia un archivo Credential Production Name - es el nombre de las credenciales de acceso Username - es el nombre de usuario Password - es la contraseña de usuario Overwrite - sobrescribe si la cuenta ya existe Crea o sobreescribe las credenciales de acceso CSPApplication Namespace AuthenticationMethods - activa los métodos de autenticación AutoCompile - realiza compilaciones automáticas (en la configuración CSP) CSPZENEnabled - la marca CSP/ZEN ChangePasswordPage - es la ruta para cambiar la contraseña de la página CookiePath - ruta hacia la sesión de cookies CustomErrorPage - ruta para personalizar la página de error DefaultSuperclass - es una superclase personalizada DefaultTimeout - se agotó el tiempo en espera de la sesión Description - realiza descripciones Directory - ruta hacia los archivos CSP EventClass - nombre de la clase del evento Grant - lista de funciones asignadas en el momento que el sistema actualiza su registro GroupById - realiza agrupaciones mediante las propiedades del Id InboundWebServicesEnabled - marca de los servicios web entrantes IsNamespaceDefault - es la marca de la aplicación para un Namespace predeterminado LockCSPName - Bloqueo de la marca del nombre CSP LoginClass - ruta para acceder a la página de inicio de sesión PackageName - nombre del paquete de propiedades PermittedClasses - clases de propiedades permitidas Recurse - indicador de recursión (sirve a los subdirectorios) (0) Resource - recurso requerido para acceder a la aplicación web ServeFiles - propiedad de los archivos de servicio ServeFilesTimeout - es el tiempo, en segundos, que le toma a Chaché almacenar los archivos estáticos. TwoFactorEnabled - es una autenticación de dos pasos Url - es el nombre de la aplicación web UseSessionCookie - utiliza las cookies para la sesión Crea o modifica una aplicación web. Para obtener más detalles, consulte la documentación y la clase Security.Applications Database Configuration BlockSize - tamaño del bloque en bytes de la base de datos ClusterMountMode - se encarga de organizar la base de datos como parte del cluster Collation - orden de la clasificación Create - si desea crear una nueva base de datos (yes/no/overwrite (yes)) Dir - es el directorio Encrypted - cifrado de la base de datos EncryptionKeyID - ID de la clave de cifrado ExpansionSize - tamaño en MB que puede expandirse InitialSize - tamaño inicial MaximumSize - tamaño máximo MountAtStartup - organización después del lanzamiento MountRequired - especifica que la base de datos DEBE organizarse con éxito en el momento del inicio Nombre - es el nombre de la base de datos PublicPermissions - son los permisos públicos Resource - es el recurso StreamLocation - es el directorio a donde se dirigen los flujos que están asociados a esta base de datos. Crea o modifica una base de datos. Para obtener más información, consulte la documentación y las clases Config.Databases y SYS.Database Default Manifest Name - es el nombre de la variable Value - es el valor de la variable Dir - valor de la variable (si es una ruta hacia una carpeta/archivo) Establece el valor de la variable (si aún no se estableció) Else Manifest, Namespace Puede ejecutarse cuando la sentencia if es falsa Error Manifest Status - código de error Source - origen del error Envía una excepción. Tenga en cuenta que la sintaxis para ${} y #{} no está disponible ForEach Manifest Index - es el nombre de la variable Values - una lista con los valores para la variable Collection-es un bucle controlado GlobalMapping Configuration Global - es el nombre del global From - es el nombre de la base de datos para el mapeo Collation - orden de la clasificación (Caché por defecto) Mapea un global If Manifest, Namespace Condition - es una sentencia condicional Sentencia condicional if IfDef Manifest, Namespace Var – nombre de la variable La sentencia condicional if se utiliza cuando la variable ya fue establecida IfNotDef Manifest, Namespace Var – nombre de la variable La sentencia condicional if se utiliza cuando la variable aún no fue establecida Import Namespace File - archivo/carpeta para importación Flags - marcas de compilación (ck) IgnorarErrores - se utiliza para ignorar errores (0) Recurse - importar recursivamente (0) Importa archivos. Llama a: $System.OBJ.ImportDir(File,,Flags,,Recurse) y $System.OBJ.Load(File, Flags) Invoke Namespace Class - nombre de la clase Method - nombre del método CheckStatus - comprueba el estado de la respuesta Return - escribe el resultado en una variable Hace una llamada a un método de una clase con varios argumentos y devuelve los resultados de la ejecución LoadPage Namespace Name: ruta a la página CSP Dir - es una carpeta con páginas CSP Flags - indicadores de compilación (ck) IgnoreErrors - se utiliza para ignorar errores (0) Carga archivos CSP mediante $System.CSP.LoadPage(Name, Flags) y $System.CSP.LoadPageDir(Dir, Flags) Log Manifest Level - nivel de registro desde 0 (mínimo) hasta 3 (detallado) Text - cadena con una longitud de hasta 32,000 caracteres Añade un mensaje al registro cuando el nivel de registro es mayor o igual al atributo "level" Manifest Elemento de la raíz. Es el único elemento de la raíz en un manifiesto, contiene todos los demás elementos Namespace Manifest Name - nombre del namespace Create - si se debe crear un nuevo namespace (yes/no/overwrite (yes)) Code - base da datos para el código del programa Data - base de datos Ensemble - activa Ensemble para el namespace Todos los demás atributos pueden utilizarse con las aplicaciones web de Ensemble Define el alcance del instalador Production Namespace Nombre - nombre de la producción AutoStart - lanzamiento automático de la producción Configura la producción en Ensemble Resource Manifest Name - nombre del recurso Description - descripción del recurso Permission - permisos públicos Crea o modifica un recurso. Role Manifest Name - nombre del role Description - descripción de la función (no debe contener comas) Resources - son los recursos asignados a la función, se representan como "MyResource:RW,MyResource1:RWU" RolesGranted - si se otorgan las funciones correspondientes Crea un nuevo role RoutineMapping Configuration Routines - nombre de la rutina Type - tipos de rutinas (MAC, INT, INC, OBJ o ALL) From - base de datos de origen Crea un nuevo mapeo para las rutinas Setting Production Item - elemento que puede configurarse Target - tipos de parámetros: Item, Host, Adapter Setting - nombre del parámetro Value - valor del parámetro Configura un elemento en la producción de Ensemble. Hace una llamada al método Ens.Production:ApplySettings SystemSetting Manifest Name - class.property del paquete Config Value - valor del atributo Establece los valores para los atributos del paquete Config (usando el método Modify) User Manifest Username - nombre de usuario PasswordVar - variable que contiene la contraseña Roles - lista de funciones del usuario Fullname - nombre completo Namespace - espacio de nombres de inicio Routine - rutina de inicio ExpirationDate - fecha después de la cual el usuario será desactivado ChangePassword - cambie la contraseña al iniciar sesión por última vez en el sistema Enabled - si el usuario está activo Crea o modifica un usuario. Var Manifest Name - nombre de la variable Value - valor de la variable Asigna un valor a la variable Variables Variables suministradas por el usuario Algunos atributos pueden contener expresiones (cadenas) que aumentan cuando el manifiesto se ejecuta. Existen tres tipos de expresiones que aumentarían y son parecidas a las siguientes: ${<Variable_name>} – es el valor de la variable (definida por el usuario o una variable de entorno, consulte más adelante) que se calcula durante la ejecución del manifiesto, ${#<Parameter_name>} – se sustituirá por el valor del parámetro especificado desde la clase del instalador durante la compilación, #{<Caché_ObjectScript_code>} — es el valor de la sentencia que se especificó en Caché ObjectScript y se calculará durante la ejecución del manifiesto. Asegúrese de poner comillas según sea necesario. Los valores de los parámetros se definen durante la compilación y, por lo tanto, pueden formar parte de una variable, o de una sentencia de Caché ObjectScript. Dado que las variables se interpretan antes que el código de Caché ObjectScript, puede utilizarlas en las sentencias de Caché ObjectScript, por ejemplo: #{$ZCVT("${NAMESPACE}","L")}. Variables del sistema Las siguientes variables siempre están disponibles: Variable Descripción Ejemplo del valor SourceDir (Disponible solo cuando se ejecuta el instalador) Directorio desde el que se ejecuta la instalación (setup_cache.exe o cinstall). /InterSystems/distr/ ISCUpgrade (Disponible solo cuando se ejecuta el instalador) Indica si se trata de una instalación nueva o de una actualización. Cuando esta variable es 0 se considera una instalación nueva, o 1 cuando es actualización. 0 (instalación) 1 (actualización) CFGDIR Consulte INSTALLDIR. /InterSystems/Cache/ CFGFILE Ruta al archivo CPF /InterSystems/Cache/cache.cpf CFGNAME Es el nombre de la instancia CACHE CPUCOUNT Número de núcleos en el CPU 4 CSPDIR Es el directorio CSP /InterSystems/Cache/csp/ HOSTNAME Es el nombre del servidor web SCHOOL15 HTTPPORT Es el puerto del servidor web 80 INSTALLDIR Es el directorio donde se instaló Caché /InterSystems/Cache/ MGRDIR Es el directorio de administración (mgr) /InterSystems/Cache/mgr/ PLATFORM Es el sistema operativo UNIX PORT Es el puerto del super servidor de Caché 1972 PROCESSOR Es el nombre de la plataforma x86-64 VERSION Es la versión de Caché 2015.1.1 Depuración de errores Algunas veces es difícil entender qué valores pueden asignarse como valores de atributos en los nodos. Para averiguarlo, compruebe el código int generado para el método de instalación. En la mayoría de los casos, la llamada principal se realiza a tInstaller.<ElementName> que es un objeto de la clase %Installer.Installer que, a su vez, hará llamadas directas a los métodos del sistema. Alternativamente, puede comprobar el código de %Installer.class<ElementName> en la que los atributos del nodo son propiedades de la clase. El código del programa se genera en los métodos %OnBeforeGenerateCode, %OnGenerateCode y %OnAfterGenerateCode. Con fines de depuración, recomiendo que coloque una llamada en una transacción dentro del instalador. Por ejemplo, puede utilizar los comandos TSTART/TROLLBACK para deshacer fácilmente todos los cambios realizados dentro de Caché (sin embargo, los cambios externos, como crear un archivo nuevo para la base de datos, no se revertirán). Por último, no olvide configurar LogLevel en 3. Ejemplos El proyecto MDX2JSON proporciona un instalador. Para instalar el proyecto, importe el archivo installer.xml que contiene la clase MDX2JSON.Installer en cualquiera de los siguientes formatos namespace. Puede realizar la importación desde SMP o arrastrando y soltando el archivo en Studio. Entonces ejecute el siguiente comando en un terminal: do ##class(MDX2JSON.Installer).setup() Como resultado, Caché cargará los archivos de la aplicación desde el repositorio GitHub y luego realizará la instalación en la base de datos predeterminada MDX2JSON namespace/MDX2JSON, mapeará el paquete MDX2SJON a %All y SAMPLES, mapeará el ^MDX2SJON global a %All y SAMPLES, creará la aplicación REST llamada /MDX2JSON, y así sucesivamente, verá todos estos pasos en el terminal. Para obtener información más detallada sobre el instalador de MDX2JSON, consulte el proyecto Léame. Ejemplos adicionales Ejemplo de los documentos de apoyo . La clase Sample.Installer en el namespace Samples. Los proyectos CacheGitHubCI proporcionan un instalador . El proyecto SYSMON que se encuentra en el panel de controles proporciona un instalador . El proyecto DeepSee Audit proporciona un instalador. Resumen %Installer es una herramienta conveniente para distribuir e implementar aplicaciones basadas en InterSystems Caché y Ensemble. Referencias Documentos de apoyo
Artículo
Nancy Martínez · 20 ene, 2020

Cómo ejecutar transformaciones XSL en HealthShare desde el Terminal

¡Hola Comunidad! HealthShare utiliza muchas transformaciones XSL. Estas transformaciones se utilizan para convertir los documentos médicos de la iniciativa “Integración de las Empresas Sanitarias (IHE)” en SDA (formatos interno de HealthShare), y convertirlos nuevamente en los formatos IHE, con el fin de crear resúmenes de los informes y para lidiar con los perfiles en el IHE (por ejemplo, consultar la información de los pacientes, proporcionar documentos y registrarlos). Los clientes pueden configurar las XSLT para personalizar los informes, o para utilizarlas de alguna otra manera. Para la depuración y el desarrollo es muy conveniente que se pueda ejecutar un XSLT desde el Terminal. El método de clase A continuación, se muestra una clase que contiene un método de clase, que permitirá ejecutar un XSLT desde el terminal en Windows. Previamente, se debe crear la clase en HSREGISTRY o en algún otro namespace en HealthShare (no utilizar HSLIB o VIEWERLIB), y procede a la compilación. Class Local.XsltTransformer Extends %RegisteredObject { ClassMethod Transform(XslDirectory As %String, XslBaseFilename As %String, Directory As %String, InputFilename As %String, OutputFilename As %String, byref Parameters = "") { // Run the XSLT transform with the base filename XslBaseFilename (i.e., without the .xsl // extension) that is in the XslDirectory. Run it on the input file with name InputFilename // and put the output in the file with name OutputFilename. The input file must be in the // directory Directory, and the output will be put in the same directory. The Parameters // argument may be used to pass parameters to the transform (rarely needed). This class // method should be run from Terminal in a HealthShare namespace other than HSLIB or // VIEWERLIB. The method will write out the path and name of the transform and any error // messages. set In = ##class(%Stream.FileCharacter).%New() set In.Filename = Directory _ "\" _ InputFilename set Out = ##class(%Stream.FileCharacter).%New() set Out.Filename = Directory _ "\" _ OutputFilename set Transformer = ##class(HS.Util.XSLTTransformer).%New() set Transformer.XSLTCacheMode = "N" set Transformer.XSLTDirectory = XslDirectory write !, XslDirectory _ "\" _ XslBaseFilename _ ".xsl" set Status = Transformer.Transform( .In, XslBaseFilename _ ".xsl", .Out, .Parameters ) if $system.Status.IsOK( Status ) { set Status = Out.%Save() } if $system.Status.IsError( Status ) { write $system.Status.GetErrorText( Status ) } } } Los siguientes son los parámetros del método de la clase Transformar: XslDirectory es el directorio donde está el archivo XSLT. La clase primero intentará añadir \Custom al directorio cuando busque la transformación, después intentará hacerlo sin él. XslBaseFilename es el nombre del archivo para la transformación XSL, pero sin la extensión .xsl. Directory es el directorio en el que se encuentra el documento de entrada y donde se quiere que la transformación transcriba los datos de salida de dicha transformación. InputFilename es el nombre del archivo del documento de entrada, incluida su extensión. OutputFilename es el nombre del archivo del documento de salida, incluida su extensión. Los parámetros pueden utilizarse para pasar parámetros la transformación, pero pocas veces son necesarios. Cómo ejecutar la transformación en el terminal Para ejecutar una transformación XSL, abre el Terminal, cambia el namespace y ejecuta el método Transformar. Por ejemplo, vamos a ejecutar la transformación de CCDA a SDA que viene con HealthShare. Tengo instalado a HealthShare en C:\InterSystems\HealthShare2016.1.1. Pondré un CCDA llamado "Sample_CCDA.xml" en mi carpeta c:\Junk. USER>zn "hsregistry" HSREGISTRY>do ##class(Local.XsltTransformer).Transform( "C:\InterSystems\HealthShare2016.1.1\CSP\xslt\SDA3", "CCDA-to-SDA", "c:\Junk", "Sample_CCDA.xml", "SDA_Out.xml" ) C:\InterSystems\HealthShare2016.1.1\CSP\xslt\SDA3\CCDA-to-SDA.xsl HSREGISTRY> El archivo de salida SDA_Out.xml ahora se encuentra en c:\Junk. Se debe tener en cuenta que no se debe utilizar las transformaciones que se encuentran en el directorio CSP\xslt\SDA. Se debe utilizar las transformaciones que se encuentran en el directorio CSP\xslt\SDA3. Las transformaciones que se encuentran en el directorio SDA son para una versión antigua de SDA (la versión 2). Asistente para transformaciones Studio XSL Una alternativa a ejecutar tus transformaciones desde el Terminal es utilizar el Asistente para transformaciones Studio XSL. En Studio, selecciona la ruta Herramientas > Complementos > Asistente para transformaciones XSL. Introduce el archivo de entrada en el campo "XML File" y la transformación XSLT en el campo "XSL File". Para el campo "XSLT Helper Class", seleccione "HSREGISTRY" y "HS.Util.XSLTHelper". Hacer clic en "Finalizar". El resultado se visualizará en la ventana de diálogo (podrás copiarlo y pegarlo): Depuración de errores Cuando realices la depuración de errores en las transformaciones XSL, un método para depurar los archivos es añadir elementos <xsl:comment> a las XSLT para que puedas observar varios elementos en la salida. Estos son algunos ejemplos: <xsl:comment>useFirstTranslation <xsl:value-of select="$useFirstTranslation"/>. referenceValue <xsl:value-of select="$referenceValue"/>. displayName <xsl:value-of select="@displayName"/>. originalText <xsl:value-of select="hl7:originalText/text()"/>. descriptionValue <xsl:value-of select="$descriptionValue"/>. </xsl:comment> <xsl:comment>Context node:</xsl:comment> <xsl:copy-of select="." /> <xsl:comment>End of context node.</xsl:comment> Varios de los XSLT de HealthShare envían una solicitud a una plantilla de "Canonización". Por ejemplo, CCDA-to-SDA.xsl esto se hace cuando veas el comentario "Canonizar la salida SDA". La canonización eliminará los comentarios desde la salida, por ello es posible que desee realizar los comentarios durante la depuración. Documentación Para obtener más información sobre los XSLT en HealthShare, puedes consultar los siguientes capítulos del libro "Descripción general de Health Connect (Overview of Health Connect)", los cuales se encuentran disponibles en los documentos: Capítulo 6: Documentos CDA y Transformaciones XSL en HealthShare Capítulo 7: Personalización del CDA y transformaciones en XSL En particular, en el Capítulo 7 se proporcionan algunas sugerencias para realizar la depuración. Además, puedes consultar el siguiente capítulo del libro "Registros de los intercambios de información": Capítulo 14: Gestión de los tipos de informes de resumen en XML
Artículo
Pierre-Yves Duquesnoy · 10 mar, 2021

Cómo migrar de Java Business Host a PEX

Con el lanzamiento de [PEX](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EPEX) en InterSystems IRIS 2020.1 e InterSystems IRIS for Health 2020.1, los clientes tienen una mejor forma de incorporar Java en las producciones que con el Java Business Host. PEX proporciona un completo conjunto de APIs para construir la interoperabilidad de los componentes y está disponible tanto en Java como en .NET. Java Business Host ha sido discontinuado y se retirará en una versión futura. Ventajas de PEX * Permite que los desarrolladores creen cualquier componente de producción tanto en Java como en .NET * Se pueden transferir estructuras de mensajes más complejas entre los componentes * Configuración simplificada * Workflow de desarrollo simplificado, sin necesidad de ObjectScript. El resto de este artículo se centra en cómo migrar el código existente de Java Business Host a PEX. ## Resumen Las clases y las interfaces que utiliza PEX son diferentes a las de Java Business Host (JBH). Aquí ofrecemos un resumen de las diferencias, pero la [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EPEX_apiref) recoge toda la información necesaria. * [Cómo cambiar un Business Service](#Converting-a-Business-Service-from-Java-Business-Host-to-PEX) * [Cómo cambiar una Business Operation](#Converting-a-Business-Operation-from-Java-Business-Host-to-PEX) * [Configuración](#Settings) * [Mensajes](#Messages) * [Logging](#Logging) ## Cómo cambiar un Business Service desde Java Business Host a PEX Para crear un PEX Business Service, debes implementar `com.intersystems.enslib.pex.BusinessService` en vez de `com.intersystems.gateway.bh.BusinessService`. El patrón de diseño utilizado por PEX para el Business Service ha cambiado de uno en el que se espera que el servicio inicie un subproceso para producir mensajes, a otro en el que el servicio implementa una función que es llamada periódicamente para producir mensajes. En JBH, tu código se parecería a esto: ```java @Override public boolean OnInit(Production p) throws Exception { production = p; if (messageThread == null) { Messager messager = new Messager(); messageThread = new Thread(messager); messageThread.start(); } return true; } ``` En PEX, solo necesitas implementar tres funciones: ```java public void OnInit() throws Exception { // Initialization return; } public Object OnProcessInput(Object messageInput) throws Exception { // Here is where you call SendMessage() or SendMessageAsync() return null; } public void OnTearDown() throws Exception { // Shut down return; } ``` También deberás cambiar la forma en que se utiliza la configuración, se entregan los mensajes y se hace logging. Hablaremos de eso más adelante. ## Cómo cambiar una Business Operation desde Java Business Host a PEX Para crear una PEX Business Operation, debes implementar `com.intersystems.enslib.pex.BusinessOperation` en vez de `com.intersystems.gateway.bh.BusinessOperation`. El patrón de diseño para Business Operations es estructuralmente el mismo entre JBH y PEX, pero han cambiado los parámetros a dos puntos de acceso principales. ### Cambios en OnInit() En PEX, `OnInit()` no requiere de ningún parámetro. ### Cambios en OnMessage() En PEX, `OnMessage()` recibe un `Object` genérico en vez del `String` usado en JBH. Esto permite al autor de la producción transmitir cualquier tipo de mensaje que desee. En JBH, tu aplicación pudo haber tenido este aspecto: ```java public boolean OnMessage(String message) throws Exception { // Business logic here return true; } ``` En PEX, el parámetro es un Java Objetct genérico, que se debe lanzar de forma adecuada, lo que permite transmitir mensajes más complejos que únicamente cadenas. Este es un ejemplo de cómo extraer una solicitud que es una secuencia de archivos: ```java public Object OnMessage(Object request) throws Exception { com.intersystems.jdbc.IRISObject streamContainer = (com.intersystems.jdbc.IRISObject)request; com.intersystems.jdbc.IRISObject str = (com.intersystems.jdbc.IRISObject)streamContainer.get("Stream"); String originalFilename = (String)streamContainer.get("OriginalFilename"); Long contentSize = (Long)str.get("Size"); String content = (String)str.invoke("Read", contentSize); // Business logic here return null; } ``` También deberás cambiar la forma en que se utiliza la configuración, se entregan los mensajes y se hace logging. Hablaremos de eso más adelante. ## Configuración Se ha simplificado la declaración de la configuración. La configuración en JBH se declaraba mediante una cadena `SETTINGS` y se obtenía a través de un código que se parece a algo como esto: ```java String setting = production.GetSetting("Min"); if (!setting.isEmpty()) { min = Integer.parseInt(setting); } ``` En PEX, la configuración son solo campos para miembros públicos. Estos se completan automáticamente cuando la clase crea una instancia. ```java public int Min = 0; ``` Cualquier campo de un miembro público está disponible para que se establezca en una producción, en la medida en que el campo del miembro sea de un tipo básico de Java (String, int, etc.). ## Mensajes El envío de mensajes es más potente. En JBH, los mensajes se envían como cadenas. En PEX, los mensajes se envían como objetos- IRISObject, para objetos definidos en ObjectScript, o una subclase de `com.intersystems.enslib.pex.Message`, para clases definidas en Java. En JBH, tu código se parecería a esto: ```java production.SendRequest(value.toString()); ``` En PEX, sería algo como esto: ```java MyExampleMessageClass req = new MyExampleMessageClass("message to send"); SendRequestAsync(Target, req); ``` ## Logging Todas las funciones de logging son similares, solo que se nombran de manera diferente. En PEX, se registraría un mensaje informativo a través de `LOGINFO()` ```java LOGINFO("Received message"); ``` ## Object Gateway JBH necesitaba su propio portal. Con PEX, puedes utilizar un único portal de Java para todas tus necesidades de Java. O puedes utilizar varios portales. Depende de ti. Aquí encontrarás una buena [introducción al portal de Java](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EJVG_intro). ## Conclusión y comentarios Si aún no has probado PEX, ¿a qué está esperando? PEX permite resolver un conjunto mucho más amplio de problemas empresariales con menos código, además de que ahora también puedes hacer cualquier cosa en .NET. Si tienes salguna pregunta o problema para migrar tu aplicación de JBH a PEX, puedes contactar conmigo o con el Centro de Soporte Internacional (WRC). Este artículo está etiquetado como "Mejores prácticas" ("Best practices") (Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Ricardo Paiva · 4 sep, 2020

Uso y depuración de %Net.SSH.Session para conexiones SSH

¡Hola desarrolladores! La clase %Net.SSH.Session permite conectarse a servidores mediante SSH. Lo más habitual es usarlo con SFTP, especialmente en los adaptadores de FTP entrantes y salientes. En este artículo se dará un breve ejemplo de cómo conectarse a un servidor SSH usando la clase, se describirá las opciones para autenticar y cómo hacer la depuración cuando surjan problemas. A continuación un ejemplo de cómo hacer la conexión: ~~~ Set SSH = ##class(%Net.SSH.Session).%New() Set return=SSH.Connect("ftp.intersystems.com")​ ~~~ Esto crea una nueva conexión, y luego se conecta al servidor SFTP ftp.intersystems.com en el puerto predeterminado. En este punto, el cliente y el servidor han elegido opciones y algoritmos de cifrado, pero ningún usuario ha iniciado sesión aún. Una vez conectado, podrá elegir cómo realizar la autenticación. Hay tres métodos principales para elegir: - AuthenticateWithUsername - AuthenticateWithKeyPair - AuthenticateWithKeyboardInteractive Cada uno de estos es un tipo distinto de autenticación. La siguiente es una breve introducción a cada tipo: #### AuthenticateWithUsername Esta usa un nombre de usuario y contraseña. #### AuthenticateWithKeyPair Esta usa un par de claves pública y privada. La clave pública se debe haber precargado en el servidor, y debe contar con la clave privada correspondiente. Si la clave privada está cifrada en el disco, debe introduzir una contraseña para descifrarla en la llamada al método. Nota: nunca envíe su clave privada a otra persona. Las claves públicas deben estar en formato OpenSSH, y las claves privadas deben estar cifradas con PEM. El formato OpenSSH se ve así: ~~~ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfi2Vq+u0rtt2OC84pyrkq1k7WkrS+s76u3a+2gdD43KQ2Z3vSUUfksymJjp11JBZEpOtBVIAy221UKdc7j7Qk6sUjZaK8LIy+bzDVwMyFWgVvQge7EjdWjrJLBRCDXYML6y1Y25XexThkTWSGyXzGNdr+wfIHYn/mIt0hfvrusauvT/9Wz8K2MGAj4BL7UQZpFJrlXzGmewe6++6cZDQQYi0aztwLK798oc9j0LsccdMpqWrjqoU1uANFhYIuUu/T47TEhT+e6M+KFYK5TR998eJTO25IjdN2Tgw0feXhQFF/nngbol0bA4auSPaZQsgokKK+E+Q/8UtBdetEofuV user@hostname ~~~ Las claves privadas cifradas con PEM tienen un encabezado en la parte superior del archivo que se ve así: ~~~ -----BEGIN RSA PRIVATE KEY----- ~~~ y terminan con: ~~~ -----END RSA PRIVATE KEY----- ~~~ #### AuthenticateWithKeyboardInteractive Permite realizar una autenticación de desafío y respuesta. Por ejemplo, podría pedir el código de un uso enviado por mensaje de texto o generado por una aplicación autenticadora de Google. Para usar este tipo de autenticación, deberá escribir una función lambda para manejar la solicitudes de comandos enviadas por el servidor. Puede que vea que algunos servidores usan esto con solo una solicitud de nombre de usuario y contraseña, de una forma que para el usuario se ve idéntica a una autenticación por contraseña. Las marcas de depuración SSH descritas a continuación pueden ayudarle a determinar si eso es lo que está viendo. Un último comentario sobre la autenticación: Si le interesa usar dos formas de autenticación para una única conexión, asegúrese de usar Ensemble/Cache 2018.1+ o cualquier versión de InterSystems IRIS. Esta versión tiene actualizaciones que permiten el uso de múltiples formatos, tales como par de claves y nombre de usuario. ## Qué hacer cuando algo sale mal... ### Algunos errores comunes que podría encontrarse son: #### Error al intentar obtener el banner Esto podría verse así: ~~~ ERROR #7500: SSH Connect Error '-2146430963': SSH Error [8010100D]: Failed getting banner [FFFFFFFF8010100D] at Session.cpp:231,0 ~~~ Obtener el banner es lo primero que hace un cliente SSH. Si ve este error, debería verificar que se está conectando al servidor correcto y que este es un servidor SFTP. Por ejemplo: si el servidor es en realidad un servidor FTPS, verá este error. Los servidores FTPS usan SSL, no SSH, y por lo tanto no funcionan con la clase %Net.SSH.Session. Puede usar la clase %Net.FtpSession class para conectarse a un servidor FTPS. #### No es posible intercambiar claves de cifrado Este error podría verse así: ~~~ ERROR #7500: SSH Connect Error '-2146430971': SSH Error [80101005]: Unable to exchange encryption keys [80101005] at Session.cpp:238,0 ~~~ Este error generalmente significa que el cliente y el servidor no pudieron negociar algoritmos de MAC o cifrado. Si ve este error, puede que necesite actualizar ya sea el cliente o el servidor para agregar compatibilidad con nuevos algoritmos. Si está usando una versión de Ensemble/Caché anterior a la 2017.1, le recomiendo actualizar a InterSystems IRIS o probar con 2017.1 o posterior. La biblioteca libssh2 se actualizó en la versión 2017.1 y se agregaron múltiples algoritmos nuevos. Puede ver más detalles en los registros provistos por las marcas de depuración que describo a continuación. #### Firma inválida para clave pública suministrada ~~~ Error [80101013]: Invalid signature for supplied public key, or bad username/public key combination [80101013] at Session.cpp:418 ~~~ Este error podría ser fácil de malinterpretar. Verá este error si su servidor pidió dos formas de autenticación y usted solo facilitó una. Si ese es el caso, continue y pruebe con la próxima. Es posible que todo se arregle. #### Error -37 Puede ver mensajes sobre el error -37. Por ejemplo, aquí está en el registro de depuración: ~~~ [libssh2] 0.369332 Failure Event: -37 - Failed getting banner ~~~ Siempre que aparezca el error -37, la operación que fracasó volverá a intentarse. Este error no es lo que causó la falla final. Busque otros mensajes de error. ### Las marcas de depuración de SSH Se puede habilitar el registro detallado de conexiones SSH para una conexión mediante las marcas de depuración de SSH. Las marcas se habilitan con el método SetTraceMethod. Este es un ejemplo de una conexión que las usa: ~~~ Set SSH = ##class(%Net.SSH.Session).%New() Do SSH.SetTraceMask(511,"/tmp/ssh.log") Set Status=SSH.Connect("ftp.intersystems.com")​ ~~~ El primer argumento de SetTraceMask le indica qué recolectar. Es una representación decimal de bits. 511 solicita todos los bits excepto el 512, y es la configuración usada más comúnmente. Si desea conocer más acerca de cada bit, están enumerados en la documentación de la clase %Net.SSH.Session. El segundo argumento le indica en qué archivo colocar la información de registro sobre la conexión. En este ejemplo usé el archivo /tmp/ssh.log, pero puede ingresar cualquier ruta absoluta o relativa que quiera usar. En el ejemplo anterior, solo ejecuté el método Connect. Si su problema está en la autenticación, deberá ejecutar también el método de autenticación correspondiente. Luego de ejecutar su prueba, podrá buscar información en el archivo de registro. Si no está seguro de cómo interpretar el archivo de registro, el Centro Mundial de Respuesta de Intersystems (WRC) puede ayudar.
Artículo
Ricardo Paiva · 4 nov, 2021

Pruebas unitarias y Cobertura de pruebas en ObjectScript Package Manager

En este artículo describiré los procesos para ejecutar pruebas unitarias mediante ObjectScript Package Manager (consulta ), incluyendo el cálculo de la Cobertura de pruebas (mediante ). ## Pruebas unitarias en ObjectScript Ya hay mucha documentación sobre cómo escribir pruebas unitarias en ObjectScript, por lo que no repetiré nada de eso. Puedes consultar el Tutorial de Pruebas Unitarias aquí: La práctica recomendada es incluir las pruebas Unitarias en algún lugar/carpeta separada en la estructura de fuentes, ya sea simplemente "/pruebas" o algo más sofisticado. Dentro de InterSystems, terminamos usando /internal/testing/unit_tests/ como nuestro estándar *de facto*, lo que tiene sentido porque las pruebas son internas/no distribuibles y hay otros tipos de pruebas además de las unitarias, pero esto podría ser un poco complejo para proyectos sencillos de código abierto. Puedes ver esta estructura en algunos de nuestros repositorios de GitHub. Desde el punto de vista del flujo de trabajo, esto es súper fácil en VSCode: solo hay que crear el directorio y colocar las clases allí. Con enfoques más antiguos centrados en el servidor para el control de la fuente (los utilizados en Studio), tendrás que mapear este paquete de manera apropiada, y el enfoque para eso varía según la extensión del control de la fuente. Desde la perspectiva de los nombres de clases para las pruebas unitarias, mi preferencia personal (y la práctica recomendada de mi grupo) es: UnitTest.<package/class being tested>[.<method/feature being tested>] Por ejemplo, si las pruebas unitarias son para el Método Foo en la clase MyApplication.SomeClass, la clase de la prueba unitaria se llamaría UnitTest.MyApplication.SomeClass.Foo; si las pruebas fueran para la clase en su totalidad, simplemente sería UnitTest.MyApplication.SomeClass. ## Pruebas unitarias en ObjectScript Package Manager ¡Hacer que ObjectScript Package Manager esté informado de tus pruebas unitarias es sencillo! Basta con añadir una línea como la siguiente a module.xml (tomada de , una bifurcación del excelente paquete matemático de @Peter.Steiwer de Open Exchange, el cual utilizo como un simple ejemplo inspirador): `<Module><br>  ...<br>  <UnitTest Name="tests" Package="UnitTest.Math" Phase="test"/><br></Module>` Lo que todo esto significa es: * Las pruebas unitarias están en el directorio "tests" debajo de la raíz del módulo. * Las pruebas unitarias están en el paquete "UnitTest.Math". Esto tiene sentido, porque las clases que se están probando están en el paquete "Math". * Las pruebas unitarias se ejecutan en la fase "test" en el ciclo de vida del paquete. (También hay una fase de "verificación" en la que podrían ejecutarse, pero esa es una historia para otro día). ### Cómo ejecutar pruebas unitarias Con las pruebas unitarias definidas como se explicó anteriormente, el administrador de paquetes ofrece algunas herramientas realmente útiles para ejecutarlas. Todavía puedes configurar ^UnitTestRoot, como lo harías normalmente con %UnitTest.Manager, pero probablemente encontrarás las siguientes opciones mucho más fáciles, especialmente si estás trabajando en varios proyectos en el mismo entorno. Puedes probar todos estas opciones clonando el repositorio objectscript-math enumerado anteriormente y luego cargarlo con `zpm "load /path/to/cloned/repo/"`, o en tu propio paquete reemplazando "objectscript-math" con los nombres de tus paquetes (y nombres de prueba). Para recargar el módulo y luego ejecutar todas las pruebas unitarias: `zpm "objectscript-math test"` Para simplemente ejecutar las pruebas unitarias (sin recargar): `zpm "objectscript-math test -only"` Para simplemente ejecutar las pruebas unitarias (sin recargar) y proporcionar una salida detallada: `zpm "objectscript-math test -only -verbose"` Para ejecutar un conjunto de pruebas en particular (es decir, un directorio de pruebas, en este caso, todas las pruebas en UnitTest/Math/Utils) sin recargar, y proporcionar una salida detallada: `zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils"` Para ejecutar un caso particular de prueba (en este caso, UnitTest.Math.Utils.TestValidateRange) sin recargar y proporcionar una salida detallada: `zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange"` O, si solo estás resolviendo los problemas de un único método de prueba: `zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull"` Cálculo de la Cobertura de pruebas mediante ObjectScript Package Manager Así que tienes algunas pruebas unitarias, pero ¿son buenas? Calcular la cobertura de pruebas no responderá completamente a esa pregunta, pero al menos ayuda. Presenté esto en la Convención anual (*Global Summit*) de InterSystems, allá por el año 2018 - aquí puedes ver el vídeo: https://youtu.be/nUSeGHwN5pc . Lo primero que tendrás que hacer es instalar el paquete de cobertura de pruebas: `zpm "install testcoverage"` Ten en cuenta que esto no requiere la instalación/ejecución de ObjectScript Package Manager; puedes encontrar más información en Open Exchange: Dicho esto, puedes aprovechar al máximo la herramienta de cobertura de pruebas si también utilizas ObjectScript Package Manager. Antes de ejecutar pruebas, debes especificar qué clases/rutinas esperas que cubran tus pruebas. Esto es importante porque, en las bases de código muy grandes (por ejemplo, HealthShare), calcular y recopilar la Cobertura de pruebas para todos los archivos del proyecto puede requerir más memoria de la que tiene tu sistema. (Específicamente, gmheap para un análisis por linea de código, si tienes curiosidad). La lista de archivos se incluye en un archivo llamado cover.list, que está dentro de la raíz de la prueba unitaria. Diferentes subdirectorios (conjuntos) de pruebas unitarias pueden tener su propia copia de esto para anular las clases/rutinas que se rastrearán mientras se ejecuta el conjunto de pruebas. Para ver un ejemplo sencillo con objectscript-math, consulta: . La [guía de usuario para la Herramienta de cobertura de pruebas](https://github.com/intersystems/TestCoverage#user-guide) incluye más detalles. Para ejecutar las pruebas unitarias con el cálculo de la Cobertura de pruebas habilitado, solo hay que añadir un argumento más al comando, especificando que se debe utilizar TestCoverage.Manager en vez de %UnitTest.Manager para ejecutar las pruebas: `zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager"`   La salida (incluso en el modo resumido) incluirá una URL donde podrás ver qué líneas de tus clases/rutinas estaban cubiertas por las pruebas unitarias, así como algunas estadísticas agregadas. ## Siguientes pasos ¿Qué sucede con la automatización de todo esto en CI? ¿Qué sucede con los reportes de resultados de las pruebas unitarias y las puntuaciones/diffs de cobertura? ¡También puedes hacer eso! Para ver un ejemplo sencillo usando Docker, Travis CI y codecov.io, consulta . Estoy planeando escribir esto en un artículo futuro que analice algunos enfoques diferentes.
Artículo
Alberto Fuentes · 25 mayo, 2021

Cómo desarrollar una API REST con un enfoque spec-first

En este artículo, me gustaría hablar sobre el enfoque *spec-first* para el desarrollo de una API REST. Mientras que el desarrollo tradicional *code-first* de una API REST es así: * Escribir el código * Habilitarlo en REST * Documentarlo (como una API REST) *Spec-first* sigue los mismos pasos, pero a la inversa. Comenzamos con una especificación, — que también actúa como documentación — , generamos el código base de la aplicación REST a partir de ella, y finalmente escribimos la lógica de negocio concreta que nos haga falta. Esto ofrece varias ventajas: * Siempre se dispone de documentación relevante y útil para desarrolladores externos o de frontend que quieran utilizar tu API REST * La especificación creada en OAS (Swagger) se puede importar a una variedad de herramientas que permiten la edición, generación de clientes, administración de la API, pruebas unitarias y automatización o simplificación de muchas otras tareas * Arquitectura de la API mejorada. En el enfoque *code-first*, la API se desarrolla método a método, por lo que un desarrollador puede perder fácilmente la pista de la arquitectura general de la API. Sin embargo, con el enfoque *spec-first*, el desarrollador se ve obligado a interactuar con una API desde la posición de consumidor de la misma, lo que con frecuencia puede ayudarle a diseñar una arquitectura de API más limpia * Desarrollo más rápido: como todo el código base se genera automáticamente, no tendrás que escribirlo, lo único que necesitas es desarrollar la lógica de negocio de tu aplicación en particular. * Obtienes sugerencias de forma más rápida: los consumidores pueden obtener una visión de la API inmediatamente y pueden ofrecer sugerencias de forma más sencilla, simplemente modificando la especificación. ¡Vamos a desarrollar nuestra API con un enfoque *spec-first*! ### Plan 1. Desarrollo de la especificación en swagger * Docker * Localmente * Online 2. Carga de la especificación en IRIS * API management REST API * ^REST * Clases 3. ¿Qué pasó con nuestra especificación? 4. Implementación 5. Desarrollos posteriores 6. Consideraciones * Parámetros especiales * CORS 7. Carga de la especificación en IAM   ### Desarrollo de la especificación El primer paso es, naturalmente, escribir la especificación. InterSystems IRIS es compatible con la Open API Specification (OAS): > **OpenAPI Specification** (anteriormente Swagger Specification) es un formato de descripción de las APIs para API REST. Un archivo OpenAPI te permite describir toda tu API, incluyendo: > > * Endpoints disponibles (`/users`) y operaciones en cada endpoint (`GET /users`, `POST /users`) > * Los parámetros de operación entrada y salida, para cada operación > * Métodos de autenticación > * Información de contacto, licencia, términos de uso y otro tipo de información > > Las especificaciones de la API se pueden escribir en YAML o JSON. El formato es fácil de aprender y leer tanto para los humanos como para las máquinas. La especificación completa de OpenAPI se puede encontrar en GitHub: [Especificación OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)   - de la documentación de Swagger. Utilizaremos Swagger para escribir nuestra API. Hay varias formas de utilizar Swagger: * [Online](https://editor.swagger.io/) * Docker: `docker run -d -p 8080:8080 swaggerapi/swagger-editor` * [Instalación local](https://swagger.io/docs/open-source-tools/swagger-editor/) Después de instalar/ejecutar Swagger, deberías ver esta ventana en un navegador web: A la izquierda se edita la especificación de la API; y a la derecha se ve inmediatamente la herramienta de prueba/documentación de la API representada de una forma visual. Vamos a cargar nuestra primera especificación de la API en él (en [YAML](https://en.wikipedia.org/wiki/YAML)). Se trata de una API sencilla con una solicitud GET que devuelve un número aleatorio en un rango especifico.   Especificación Math API swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" host: "localhost:52773" basePath: "/math" schemes: - http paths: /random/{min}/{max}: get: x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" Esto es en lo que consiste. Información básica sobre nuestra API y la versión de OAS utilizada. swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" Host del servidor, protocolo (http, https) y nombres de las aplicaciones web: host: "localhost:52773" basePath: "/math" schemes: - http A continuación, especificamos una ruta (por lo que la URL completa sería `http://localhost:52773/math/random/:min/:max`) y el método de solicitud HTTP (get, post, put, delete): paths: /random/{min}/{max}: get: A continuación, especificamos la información sobre nuestra solicitud: x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" En esta parte definimos nuestra solicitud: * Habilitar esta ruta para CORS (explicaremos esto más adelante) * Proporcionar _summary_ y _description_ * _operationId_ permite una referencia dentro de las propias especificaciones, además es un nombre de método generado en nuestra clase de implementation * _produces_: formato de respuesta (como text, xml, json) * _parameters_ especifica los parámetros de entrada (ya sea en la URL o en el cuerpo); en nuestro caso especificamos 2 parámetros, el rango para nuestro generador de números aleatorios * _responses_ lista de respuestas posibles del servidor Como ves, este formato no es especialmente difícil, aunque hay muchas más funciones disponibles, aquí hay una [especificación](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md). Por último, vamos a exportar nuestra definición como JSON. Ir a File → Convert y guardar como JSON. La especificación debería tener este aspecto:   Especificación Math API { "swagger": "2.0", "info": { "description": "Math", "version": "1.0.0", "title": "Math REST API" }, "host": "localhost:52773", "basePath": "/math", "schemes": [ "http" ], "paths": { "/random/{min}/{max}": { "get": { "x-ISC_CORS": true, "summary": "Get random integer", "description": "Get random integer between min and max", "operationId": "getRandom", "produces": [ "application/json" ], "parameters": [ { "name": "min", "in": "path", "description": "Minimal Integer", "required": true, "type": "integer", "format": "int32" }, { "name": "max", "in": "path", "description": "Maximal Integer", "required": true, "type": "integer", "format": "int32" } ], "responses": { "200": { "description": "OK" } } } } } } ### Carga de la especificación en IRIS Ahora que tenemos nuestra especificación, podemos generar el código base para esta API REST en InterSystems IRIS. Para pasar a esta etapa necesitaremos tres cosas: * Nombre de la aplicación REST: paquete para nuestro código generado (digamos `math`) * La especificación OAS en formato JSON: la acabamos de crear en un paso anterior * Nombre de la aplicación WEB: una ruta base para acceder a nuestra API REST (`/math` en nuestro caso) Hay tres maneras de utilizar nuestra especificación para generar códigos, que son esencialmente lo mismo y solo ofrecen varias maneras de acceder a la misma función 1. Llamar a la rutina `^%REST` (`Do ^%REST` en una sesión terminal interactiva), [documentación](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_routine). 2. Llamar a la clase `%REST` (`Set sc = ##class(%REST.API).CreateApplication(applicationName, spec)`, forma no interactiva), [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_objectscriptapi). 3. Utilizar API Management REST API, [documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_apimgmnt). Creo que la documentación describe adecuadamente los pasos necesarios, así que solo tienes que seleccionar uno. Añadiré dos notas: * En los casos (1) y (2) puedes transmitir a un objeto dinámico, un nombre de archivo o una URL * En los casos (2) y (3) **debes** realizar una llamada adicional para crear una aplicación WEB: `set sc = #class(%SYS.REST). DeployApplication(restApp, webApp, authenticationType)`, así que en nuestro caso `set sc = ##class(%SYS.REST). DeployApplication("math", "/math")`, obtenemos los valores del argumento `authenticationType` desde el archivo *include* `%sySecurity`, las entradas correspondientes son `$$$Authe*`, por lo que para el acceso no autenticado transmitimos `$$$AutheUnauthenticated`. Si se omite, el parámetro se ajusta de forma predeterminada a la autenticación por contraseña. ### ¿Qué pasó con nuestra especificación? Si has creado la aplicación con éxito, debería crearse un nuevo paquete `math` con tres clases: * _Spec_: almacena la especificación tal y como está. * _Disp_: se llama directamente cuando se invoca el servicio REST. Se ocupa de la gestión del manejo de REST y llama a los métodos de implementación. * _Impl_: contiene la implementación interna real del servicio REST. Solo deberías editar esta clase. [Documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_intro#GREST_intro_classes) con más información sobre las clases. ### Implementación Inicialmente nuestra *implementation class* `math.impl` contiene solo un método, que corresponde a nuestra operación `/random/{min}/{max}`: /// Get random integer between min and max<br/> /// The method arguments hold values for:<br/> /// min, Minimal Integer<br/> /// max, Maximal Integer<br/> ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { //(Place business logic here) //Do ..%SetStatusCode(<HTTP_status_code>) //Do ..%SetHeader(<name>,<value>) //Quit (Place response here) ; response may be a string, stream or dynamic object } Comenzaremos con la implementación sencilla: ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { quit {"value":($random(max-min)+min)} } Y finalmente podemos llamar a nuestra API REST abriendo esta página en el navegador: `http://localhost:52773/math/random/1/100` El resultado debería ser: { "value": 45 } También en el editor Swagger al presionar el botón `Try it out`, y rellenando los parámetros de la solicitud, también se enviaría la misma solicitud: ¡Enhorabuena! ¡Nuestra primera API REST creada con un enfoque *spec-first* ya está funcionando! ### Desarrollos posteriores Por supuesto, nuestra API no es estática y tenemos que agregar nuevas rutas, y así sucesivamente. Con el desarrollo *spec-first*, empiezas por modificar la especificación, después actualizas la aplicación REST (las mismas llamadas que para crear la aplicación) y finalmente escribes el código. Ten en cuenta que las actualizaciones de la especificación son seguras: tu código no se verá afectado, incluso si la ruta se elimina de una especificación, el método no se eliminará en la *implementation class*.   ### Consideraciones ¡Más notas! #### Parámetros especiales InterSystems añadió parámetros especiales a la especificación de swagger. Son estos: Nombre Tipo de datos Predeterminado Lugar Descripción x-ISC_DispatchParent classname %CSP.REST información Superclase para la clase dispatch. x-ISC_CORS booleano falso operación Marca para indicar que las solicitudes CORS para esta combinación de endpoint/método deben ser soportadas. x-ISC_RequiredResource matriz   operación Lista separada por comas de los recursos definidos y sus modos de acceso (resource:mode) que se requieren para acceder a este endpoint del servicio REST. Por ejemplo: ["%Development:USE"] x-ISC_ServiceMethod cadena   operación Nombre del método de clase llamado en el back end para dar servicio a esta operación; de forma predeterminada es operationId, que normalmente es el más adecuado.   #### CORS Hay tres maneras de activar el soporte de CORS. 1. En cada ruta, especificando `x-ISC_CORS` como verdadero. Eso es lo que hemos hecho en nuestra API REST Math. 2. En cada API, añadiendo Parameter HandleCorsRequest = 1; y recompilando la clase. También sobreviviría a la actualización de las especificaciones. 3. (Recomendada) En cada API, mediante la implementación de la superclase *custom dispatcher* (debe extender `%CSP.REST`), y escribiendo la lógica del procesamiento de CORS allí. Para utilizar esta superclase, añade `x-ISC_DispatchParent` a tu especificación.   ### Carga de la especificación en IAM   Por último, vamos a añadir nuestra especificación en IAM para que sea publicada por otros desarrolladores. Si no has comenzado con IAM, consulta [este artículo](https://es.community.intersystems.com/node/465921). También explica cómo publicar APIs REST por medio de IAM, por eso no lo describimos aquí. Es posible que quieras modificar los parámetros spec `host` y `basepath` para que apunten a IAM, en lugar de a la instancia de InterSystems IRIS. Abre el portal del Administrador de IAM y ve a la pestaña `Specs` en el espacio de trabajo correspondiente. Haz clic en el botón `Add Spec` e introduce el nombre de la nueva API (`math` en nuestro caso). Después de crear nuevas especificaciones en IAM, haz clic en `Editar` y pega el código de las especificaciones (JSON o YAML, no importa para IAM): No olvides hacer clic en `Update file`. Ahora nuestra API está publicada para los desarrolladores. Abre el *Developer Portal* y haz clic en `Documentation` en la esquina superior derecha. Además de las tres API predeterminadas, nuestra nueva `API REST Math` debería estar disponible: Ábrela: ¡Ahora los desarrolladores pueden ver la documentación de nuestra nueva API y probarla en el mismo lugar!  ###   ### Conclusiones InterSystems IRIS simplifica el proceso de desarrollo de una API REST y el enfoque spec-first permite una gestión más rápida y sencilla del ciclo de vida de la API REST. Con este enfoque, puedes utilizar una variedad de herramientas para una variedad de tareas relacionadas, como la generación de clientes, las pruebas unitarias, la administración de la API y muchas otras más. ### Enlaces * [Especificación OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) * [Cómo crear servicios REST](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST) * [Presentación de IAM](https://es.community.intersystems.com/post/presentaci%C3%B3n-de-intersystems-api-manager) * [Documentación de IAM](https://docs.intersystems.com/irislatest/csp/docbook/apimgr/index.html) Este artículo está etiquetado como "Mejores prácticas" ("Best practices"). Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.