Brio-IO Communication Server
Volver al blog
por el equipo de Brio-IO

Transformers en Brio-IO: tres motores, una pipeline — y por fin rutas HL7 legibles

Cómo Brio-IO scripta los transformers: Groovy, GraalJS y Rhino en paralelo, por qué el `msg` parseado ofrece un acceso por propiedad legible (msg.PID.PID_5.PID_5_1), y cómo migrar scripts existentes paso a paso.

#transformer #scripting #hl7 #mirth #groovy

Transformers en Brio-IO: tres motores, una pipeline

Un servidor de integración pasa la mayor parte del tiempo reescribiendo mensajes: normalizar un campo, mapear un código, descartar un mensaje que no corresponde al canal de destino. En Brio-IO eso ocurre en el transformer — el paso en el que, con un pequeño script, das al mensaje la forma que espera tu sistema de destino.

Este artículo muestra cómo se ve eso en la práctica: qué motores de scripting trae Brio-IO, por qué las rutas HL7 son legibles en Brio-IO (y en Mirth Connect no), y cómo llevarte los scripts existentes de Mirth sin un big bang.

Actualizado en julio de 2026 — las rutas de componente ahora son posicionales: Este artículo tiene una pequeña historia, y la contamos con honestidad. Una versión intermedia mostraba los componentes bajo su tipo de dato HL7 (PID_5XPN_1FN_1), porque eso era lo que el serializador emitía entonces. Con la modernización del serializador (ADR-022), Brio-IO ahora nombra los componentes por número de posición: PID_5_1, PID_5_2 — exactamente la forma que este artículo mostraba en su origen, y la que usan HL7 y Mirth. Lo importante: esas rutas ahora funcionan contra mensajes reales (antes devolvían una cadena vacía en silencio). Todos los ejemplos de abajo se han ejecutado contra el serializador real y los tres motores.

Dónde encaja el transformer en la pipeline

Todos los canales procesan los mensajes en el mismo orden:

Source  →  Filter  →  Transformer  →  Destinations
  • El filter decide por mensaje: procesar o descartar. Devuelve un booleano — true significa „dejar pasar”.
  • El transformer modifica el mensaje. Recibe el mensaje actual, lo reescribe y devuelve la nueva versión.

Ambos son scripts. Y justo aquí se pone interesante, porque Brio-IO no ejecuta los scripts en un único lenguaje.

Tres motores, un contexto

Brio-IO opera tres motores de scripting en paralelo (véase ADR-005). Por cuál pasa un mensaje lo decides por script — no por canal.

MotorMarcadorLenguajeRol
GroovygroovyApache Groovyestándar para scripts nuevos
GraalJSjs-modernJavaScript moderno (ES2024)para quien prefiera escribir JS
Rhinojs-legacyJavaScript con E4Xsolo para scripts de Mirth importados

La clave: da igual el motor — los objetos built-in son los mismos en todas partes. Un script siempre tiene acceso a:

  • msg — el mensaje actual; en Groovy y GraalJS un objeto XML parseado con acceso por propiedad (un string con payloads no-XML como JSON)
  • xml — el helper XML (get/set/xpath) como API de transición para scripts existentes y payloads no-XML
  • channelMap, sourceMap, responseMap — maps para el contexto del mensaje
  • globalMap, globalChannelMap — maps persistentes a nivel de servidor y de canal
  • tmp — un scratchpad que solo existe para esta ejecución
  • logger — logger SLF4J para la salida de log
  • xslt — helper para transformaciones con stylesheets

Los scripts se compilan al hacer deploy de un canal (caché de compilación), no en cada mensaje. La comodidad de editar en un lenguaje de scripting no te cuesta rendimiento en tiempo de ejecución.

El problema del punto que todo desarrollador de Mirth conoce

Los mensajes HL7v2 se convierten en el servidor a una representación XML canónica. En el mundo clásico de Mirth, cada nivel lleva un punto en el nombre del elemento:

<PID>
  <PID.5>
    <PID.5.1>Schmidt</PID.5.1>
  </PID.5>
</PID>

Y aquí empiezan los problemas. El punto es el operador de acceso a propiedades en todos los lenguajes. msg.PID.PID.5.PID.5.1 no se puede parsear — el parser interpreta los puntos como anidamiento. La salida de Mirth es la notación de brackets con strings:

// Mirth Connect (Rhino/E4X): strings, brackets, toString()
var nachname = msg['PID']['PID.5']['PID.5.1'].toString();

Funciona, pero es propenso a errores (comillas olvidadas, brackets mal puestos), poco amigable con el IDE (sin autocompletado sobre strings) y sencillamente feo. Es lo primero que se aprende en cualquier formación de Mirth.

La respuesta de Brio-IO: underscores en lugar de puntos

Brio-IO es un servidor nuevo y corrige este error de diseño histórico. En la representación canónica, Brio-IO separa los niveles de campo con underscores (véase ADR-016) — PID.5 pasa a ser PID_5. Un underscore no es un operador en ningún lenguaje, así que los nombres de elemento son de repente identificadores completamente normales.

Un campo, sus componentes — numerados por posición

Antes de los ejemplos, un detalle que si no te haría tropezar en tu primer script propio. Así se ve un segmento PID real en Brio-IO:

<PID>
  <PID_3>
    <PID_3_1>12345</PID_3_1>
    <PID_3_4>MRN</PID_3_4>
  </PID_3>
  <PID_5>
    <PID_5_1>Schmidt</PID_5_1>
    <PID_5_2>Hans</PID_5_2>
  </PID_5>
</PID>

El apellido está bajo PID_5_1 — campo PID-5, componente 1. Los componentes están numerados por posición, igual que en HL7 y en Mirth: PID-5.1 es el apellido, PID-5.2 el nombre, PID-3.1 el ID, PID-3.4 la autoridad asignadora. El nombre del elemento te dice la posición, no un tipo de dato que tendrías que consultar primero.

La regla detrás es corta:

  • Campo simple → se lee directamente: msg.MSH.MSH_10 devuelve "MSG00001".
  • Campo compuesto → un nivel por componente: msg.PID.PID_5.PID_5_1 devuelve "Schmidt". Si un componente lleva a su vez un subcomponente, se baja un nivel más (PID_5_1_1).

Es exactamente la numeración que HL7 describe y que un profesional de Mirth tiene en la cabeza — la misma posición, solo con guion bajo en vez de punto (más sobre esto enseguida). Conviene saberlo: acceder a una ruta que no existe no lanza — devuelve un string vacío. El message browser de la web UI te muestra el XML canónico de cada mensaje recibido si quieres comprobar una ruta contra un mensaje real.

Acceso por propiedad a msg: la vía recomendada

Como los nombres de elemento son identificadores válidos, Brio-IO vincula msg en Groovy y GraalJS como un objeto XML ya parseado (ADR-019). Así navegas directamente por propiedad — justo la comodidad que en Mirth fracasaba en los puntos. msg es el elemento raíz del mensaje, de modo que entras por debajo de la raíz (sin el prefijo ADT_A01):

// Groovy — msg es el nodo raíz, .text() devuelve el valor del campo
def nachname = msg.PID.PID_5.PID_5_1.text()   // "Schmidt"
def vorname  = msg.PID.PID_5.PID_5_2.text()        // "Hans"
// GraalJS (js-modern) — un campo de texto se lee directamente como string
let nachname = msg.PID.PID_5.PID_5_1;   // "Schmidt"
let patId    = msg.PID.PID_3.PID_3_1;         // "12345"

Si falta un campo, el acceso no lanza — se lee de forma tolerante como string vacío. El acceso por propiedad no solo es más legible, también es la vía más rápida: trabaja directamente sobre el objeto parseado, sin reserializar el mensaje en cada acceso a un campo.

xml.get/xml.set: la API de transición

Para scripts existentes y payloads no-XML (JSON, raw — donde msg sigue siendo un string), el helper de ruta xml permanece. Lee y escribe campos mediante una ruta — idéntico en todos los motores, y acepta msg tanto como string como objeto parseado:

// Groovy — API de transición: helper de ruta (ruta desde la raíz, incl. ADT_A01)
def nachname = xml.get(msg, "ADT_A01/PID/PID_5/PID_5_1")   // "Schmidt"

A diferencia del acceso por propiedad, la ruta del helper comienza en el elemento raíz (ADT_A01). Como separador acepta tanto la barra como el punto. Además de get, existe set para escribir y xpath para el acceso XPath completo:

// GraalJS — XPath completo cuando una ruta no basta
let nachname = xml.xpath(msg, "//*[local-name()='PID_5_1']/text()");

Un transformer modifica — devolviendo

Un transformer muta el objeto msg parseado y lo devuelve — el motor lo serializa automáticamente de vuelta a XML canónico en el límite del paso. Así que no tienes que serializar nada a mano.

Ejemplo 1 — normalizar el apellido a mayúsculas:

// Groovy: leer la propiedad, fijarla en el nodo, devolver el msg modificado
def nachname = msg.PID.PID_5.PID_5_1.text()
msg.PID.PID_5.PID_5_1[0].value = nachname.toUpperCase()
return msg
// GraalJS: fijar la propiedad directamente, devolver msg como última expresión
msg.PID.PID_5.PID_5_1 = msg.PID.PID_5.PID_5_1.toUpperCase();
msg;

En Groovy necesitas un return explícito y fijas el valor en el nodo mediante [0].value. En GraalJS basta la última expresión — por eso msg está ahí simplemente como última línea, y el campo se fija por asignación directa.

Ejemplo 2 — llevar un valor a un contexto de map y fijar un campo:

// Groovy: recordar el identificador de paciente y rellenar un campo destino
def patientId = msg.PID.PID_3.PID_3_1.text()
channelMap.put("patientId", patientId)
msg.PID.PID_3.PID_3_1[0].value = patientId.trim()
return msg

channelMap sobrevive dentro del mensaje hasta los destinations — práctico para pasar valores entre filter, transformer y plantillas de destino.

Filter: devuelve un booleano

Un filter solo decide sí/no. Debe devolver un booleano; true deja pasar el mensaje, cualquier otra cosa lo descarta.

// Groovy: procesar solo mensajes con ID de paciente informado
return msg.PID.PID_3.PID_3_1.text() != ""
// GraalJS: la última expresión es el resultado
msg.PID.PID_3.PID_3_1 !== "";

El mismo mensaje, tres idiomas — en paralelo

Leer un campo se ve así en los tres motores — el mismo campo tres veces, contra el mismo msg:

// Brio-IO Rhino (js-legacy) — brackets E4X, como en Mirth
msg['PID']['PID.5']['PID.5.1'].toString()
// Brio-IO Groovy (estándar) — msg parseado, acceso por propiedad
msg.PID.PID_5.PID_5_1.text()
// Brio-IO GraalJS (js-modern) — msg parseado, campo de texto como string
msg.PID.PID_5.PID_5_1

Que Rhino vea la variante con puntos del mismo mensaje (PID.5.1 en vez de PID_5_1) lo resuelve el procesador de la pipeline — de eso no te tienes que ocupar.

Lo que esto significa para un script existente de Mirth, dicho con franqueza: la mecánica se traslada sin cambios — brackets E4X, mutación, devolver msg al final, y el new XML(msg) que antes hacía falta ya sobra. Y las rutas de componente también: el msg['PID']['PID.5']['PID.5.1'] de Mirth se ejecuta en Brio-IO js-legacy sin cambios — ambos numeran por posición. El nivel de segmento, de campo y de componente se queda tal como lo conoces. La única diferencia es guion bajo en vez de punto en Groovy/GraalJS, y eso lo pone el procesador de la pipeline automáticamente por motor — de eso no te tienes que ocupar.

Si en Groovy navegas estructuras complejas o iteras sobre muchos campos repetidos, también tienes a tu disposición el acceso XPath completo con xml.xpath.

Rhino (js-legacy): compatibilidad con Mirth con fecha de caducidad

¿Por qué mantener entonces Rhino? Porque los scripts heredados de Mirth están escritos en JavaScript con E4X y deben seguir funcionando sin cambios en la medida de lo posible. Al importar de Mirth, Brio-IO pone automáticamente los scripts importados en js-legacy.

Para que esos mismos scripts sigan funcionando, Rhino es el único motor que recibe la notación con puntos en el XML — el mundo que los scripts de Mirth esperan. En Rhino msg es directamente un objeto XML de E4X — un new XML(msg) ya no hace falta, accedes igual que en Mirth:

// Rhino (js-legacy): msg ya es E4X — acceso con brackets como en Mirth
var nachname = msg['PID']['PID.5']['PID.5.1'].toString();

La mutación y el retorno también siguen siendo familiares para Mirth — el motor serializa el objeto E4X de vuelta a XML en su límite:

// Rhino: fijar un campo, devolver msg como última expresión
msg['PID']['PID.5']['PID.5.1'] = 'Mueller';
msg;

Rhino está deprecated a propósito. En cada deploy de un canal con al menos un script de Rhino, Brio-IO emite un aviso de deprecación — en el log y en la respuesta de la API — y lista por nombre los scripts afectados. También aparece un aviso en el editor de scripts de la web UI en cuanto eliges js-legacy. La ruta de sunset es fija (ADR-016):

VersiónComportamiento
Brio-IO 1.x (hoy)Rhino funciona, aviso al hacer deploy del canal
Brio-IO 2.x (~2027)el aviso se vuelve más prominente (banner en el editor)
Brio-IO 3.x (~2028)Rhino solo se puede activar mediante un feature flag
Brio-IO 4.0 (~2029)se eliminan Rhino y la notación con puntos

Tienes por tanto tres años de margen para migrar los scripts con calma — script a script, no como un big bang.

En la práctica: crear un transformer en la UI

En la web UI creas un transformer directamente en el editor de canales. El editor se basa en Monaco (el mismo motor que en VS Code), con resaltado de sintaxis por lenguaje. El motor lo eliges por script — un canal puede combinar sin problema un filter en Groovy y un transformer js-legacy venido de Mirth. El procesador de la pipeline serializa el mensaje automáticamente en la notación correcta antes de cada paso (underscore para Groovy/GraalJS, punto para Rhino) — de eso no te tienes que ocupar.

Un camino de migración típico desde Mirth se ve así:

  1. Poner los scripts en js-legacy — la mecánica E4X y las rutas de componente siguen funcionando sin cambios (posicionales, igual que en Mirth). Un vistazo al message browser confirma el XML canónico de cada mensaje recibido.
  2. Pasar script a script a Groovy (recomendado) o GraalJS, sustituyendo los strings con brackets por acceso por propiedad (msg.PID.PID_5.PID_5_1 / en Groovy con .text()).
  3. Listo cuando no queda ningún script js-legacy — entonces desaparece también el aviso de deprecación.

Perspectivas

Está en marcha una guía de migración de Rhino a Groovy detallada — con traducciones patrón por patrón de los idiomas E4X más frecuentes, incluido crear segmentos (createSegment) y el manejo de secuencias de escape. La enlazaremos aquí en cuanto esté lista.

Hasta entonces puedes ver los motores en vivo: en la demo pública funcionan canales con transformers reales, y el message browser te muestra el XML canónico contra el que correrían tus rutas.

Y si estás pensando en llevar tus canales de Mirth a Brio-IO y quieres ver cómo se traducen tus scripts reales — pídenos un acceso de Early Access. Cuéntanos simplemente qué interfaces quieres integrar.