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.
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_5→XPN_1→FN_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
—
truesignifica „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.
| Motor | Marcador | Lenguaje | Rol |
|---|---|---|---|
| Groovy | groovy | Apache Groovy | estándar para scripts nuevos |
| GraalJS | js-modern | JavaScript moderno (ES2024) | para quien prefiera escribir JS |
| Rhino | js-legacy | JavaScript con E4X | solo 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-XMLchannelMap,sourceMap,responseMap— maps para el contexto del mensajeglobalMap,globalChannelMap— maps persistentes a nivel de servidor y de canaltmp— un scratchpad que solo existe para esta ejecuciónlogger— logger SLF4J para la salida de logxslt— 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_10devuelve"MSG00001". - Campo compuesto → un nivel por componente:
msg.PID.PID_5.PID_5_1devuelve"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ón | Comportamiento |
|---|---|
| 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í:
- 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. - 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()). - 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.