Brio-IO Communication Server
Zurück zum Blog
vom Brio-IO-Team

Transformer in Brio-IO: drei Engines, eine Pipeline — und endlich lesbare HL7-Pfade

Wie Brio-IO Transformer skriptet: Groovy, GraalJS und Rhino nebeneinander, warum `msg` als geparstes XML lesbaren Property-Zugriff bietet (msg.PID.PID_5.PID_5_1), und wie du Bestandsskripte Schritt für Schritt migrierst.

#transformer #scripting #hl7 #mirth #groovy

Transformer in Brio-IO: drei Engines, eine Pipeline

Ein Integration-Server verbringt die meiste Zeit damit, Nachrichten umzuschreiben: ein Feld normalisieren, einen Code mappen, eine Nachricht verwerfen, die nicht in den Zielkanal gehört. In Brio-IO passiert das im Transformer — dem Schritt, in dem du die Nachricht mit einem kleinen Skript in die Form bringst, die dein Zielsystem erwartet.

Dieser Artikel zeigt, wie das konkret aussieht: welche Skript-Engines Brio-IO mitbringt, warum HL7-Pfade in Brio-IO lesbar sind (und in Mirth Connect nicht), und wie du Bestandsskripte aus Mirth ohne Big-Bang mitnimmst.

Aktualisiert Juli 2026 — Komponenten-Pfade jetzt positional: Dieser Artikel hat eine kleine Geschichte, und wir sagen sie ehrlich. Eine Zwischenfassung zeigte Komponenten unter ihrem HL7-Datentyp (PID_5XPN_1FN_1), weil der Serializer das damals so ausgab. Mit der Serializer-Modernisierung (ADR-022) benennt Brio-IO Komponenten jetzt positional durchnummeriert: PID_5_1, PID_5_2 — genau die Form, die dieser Artikel ursprünglich zeigte und die HL7 und Mirth verwenden. Der wichtige Teil: Diese Pfade funktionieren jetzt gegen echte Nachrichten (vorher lieferten sie still einen leeren String). Alle Beispiele unten sind gegen den echten Serializer und alle drei Engines ausgeführt.

Wo der Transformer in der Pipeline steht

Jeder Channel verarbeitet Nachrichten in derselben Reihenfolge:

Source  →  Filter  →  Transformer  →  Destinations
  • Der Filter entscheidet pro Nachricht: verarbeiten oder verwerfen. Er gibt einen Wahrheitswert zurück — true heißt „durchlassen”.
  • Der Transformer verändert die Nachricht. Er bekommt die aktuelle Nachricht, schreibt sie um und gibt die neue Fassung zurück.

Beide sind Skripte. Und genau hier wird es interessant, denn Brio-IO führt Skripte nicht in einer einzigen Sprache aus.

Drei Engines, ein Kontext

Brio-IO betreibt drei Skript-Engines parallel (siehe ADR-005). Welche eine Nachricht durchläuft, entscheidest du pro Skript — nicht pro Channel.

EngineMarkerSpracheRolle
GroovygroovyApache GroovyStandard für neue Skripte
GraalJSjs-modernmodernes JavaScript (ES2024)für alle, die lieber JS schreiben
Rhinojs-legacyJavaScript mit E4Xnur für importierte Mirth-Skripte

Der Clou: Egal welche Engine — die Built-in-Objekte sind überall dieselben. Ein Skript hat immer Zugriff auf:

  • msg — die aktuelle Nachricht; in Groovy und GraalJS ein geparstes XML-Objekt mit Property-Zugriff (bei Nicht-XML-Payloads wie JSON ein String)
  • xml — der XML-Helfer (get/set/xpath) als Übergangs-API für Bestandsskripte und Nicht-XML-Payloads
  • channelMap, sourceMap, responseMap — Maps für den Nachrichten-Kontext
  • globalMap, globalChannelMap — server- bzw. channelweite persistente Maps
  • tmp — ein Scratchpad, das nur für diese eine Ausführung existiert
  • logger — SLF4J-Logger für Log-Ausgaben
  • xslt — Helfer für Stylesheet-Transformationen

Skripte werden beim Deployment eines Channels kompiliert (Compile-Cache), nicht bei jeder Nachricht. Der Editier-Komfort einer Skriptsprache kostet dich zur Laufzeit also keine Performance.

Das Punkt-Problem, das jeder Mirth-Entwickler kennt

HL7v2-Nachrichten werden im Server in eine kanonische XML-Repräsentation überführt. In der klassischen Mirth-Welt trägt jede Ebene einen Punkt im Element-Namen:

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

Und hier fängt der Ärger an. Der Punkt ist in jeder Programmiersprache der Property-Zugriffs-Operator. msg.PID.PID.5.PID.5.1 kann der Parser nicht lesen — er versteht die Punkte als Verschachtelung. Mirths Ausweg ist die Bracket-Notation mit Strings:

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

Das funktioniert, ist aber fehleranfällig (vergessene Quotes, falsche Klammern), IDE-unfreundlich (kein Autocomplete auf Strings) und schlicht hässlich. Es ist die erste Sache, die man in jeder Mirth-Schulung lernt.

Die Brio-IO-Antwort: Underscores statt Punkte

Brio-IO ist ein neuer Server und korrigiert diesen historischen Designfehler. In der kanonischen Repräsentation trennt Brio-IO Feld-Ebenen mit Underscores (siehe ADR-016) — aus PID.5 wird PID_5. Ein Underscore ist in keiner Sprache ein Operator, also sind Element-Namen plötzlich ganz normale Bezeichner.

Ein Feld, seine Komponenten — positional durchnummeriert

Bevor die Beispiele kommen, ein Detail, das dir sonst beim ersten eigenen Skript vor die Füße fällt. So sieht ein echtes PID-Segment in Brio-IO aus:

<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>

Der Nachname liegt unter PID_5_1 — Feld PID-5, Komponente 1. Die Komponenten sind positional durchnummeriert, genau wie in HL7 und in Mirth: PID-5.1 ist der Nachname, PID-5.2 der Vorname, PID-3.1 die ID, PID-3.4 die zuweisende Stelle. Der Elementname sagt dir also die Position, nicht einen Datentyp, den du erst nachschlagen müsstest.

Die Regel dahinter ist kurz:

  • Einfaches Feld → direkt lesbar: msg.MSH.MSH_10 liefert "MSG00001".
  • Zusammengesetztes Feld → eine Ebene je Komponente: msg.PID.PID_5.PID_5_1 liefert "Schmidt". Steckt in einer Komponente noch eine Subkomponente, geht es eine Ebene tiefer (PID_5_1_1).

Das ist genau die Nummerierung, die HL7 beschreibt und die ein Mirth-Praktiker im Kopf hat — dieselbe Position, nur mit Underscore statt Punkt (dazu gleich mehr). Wichtig zu wissen: Ein Zugriff auf einen Pfad, den es nicht gibt, wirft nicht — er liefert einen leeren String. Der Message-Browser der Web-UI zeigt dir die kanonische XML jeder empfangenen Nachricht, wenn du einen Pfad gegen eine echte Nachricht prüfen willst.

Property-Zugriff auf msg: der empfohlene Weg

Weil die Element-Namen gültige Bezeichner sind, bindet Brio-IO msg in Groovy und GraalJS als bereits geparstes XML-Objekt ein (ADR-019). Du navigierst also direkt per Property — genau der Komfort, der in Mirth an den Punkten scheiterte. msg ist dabei das Wurzel-Element der Nachricht, du steigst also unterhalb der Wurzel ein (kein ADT_A01-Präfix):

// Groovy — msg ist der Wurzel-Knoten, .text() liefert den Feldwert
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) — ein Text-Feld liest sich direkt als String
let nachname = msg.PID.PID_5.PID_5_1;   // "Schmidt"
let patId    = msg.PID.PID_3.PID_3_1;         // "12345"

Fehlt ein Feld, wirft der Zugriff nicht — er liest sich tolerant als leerer String. Der Property-Zugriff ist nicht nur lesbarer, sondern auch der schnellere Weg: er arbeitet direkt auf dem geparsten Objekt, ohne die Nachricht bei jedem Feldzugriff neu zu serialisieren.

xml.get/xml.set: die Übergangs-API

Für Bestandsskripte und Nicht-XML-Payloads (JSON, Raw — dort bleibt msg ein String) bleibt der Pfad-Helfer xml erhalten. Er liest und schreibt Felder über einen Pfad — engine-übergreifend identisch, und akzeptiert msg sowohl als String als auch als geparstes Objekt:

// Groovy — Übergangs-API: Pfad-Helfer (Pfad ab Wurzel, inkl. ADT_A01)
def nachname = xml.get(msg, "ADT_A01/PID/PID_5/PID_5_1")   // "Schmidt"

Anders als beim Property-Zugriff startet der Helfer-Pfad am Wurzel-Element (ADT_A01). Als Trennzeichen akzeptiert er sowohl den Schrägstrich als auch den Punkt. Neben get gibt es set zum Schreiben und xpath für den vollen XPath-Zugriff:

// GraalJS — voller XPath, wenn ein Pfad nicht reicht
let nachname = xml.xpath(msg, "//*[local-name()='PID_5_1']/text()");

Ein Transformer verändert — indem er zurückgibt

Ein Transformer mutiert das geparste msg-Objekt und gibt es zurück — die Engine serialisiert es an der Schritt-Grenze automatisch zurück zu kanonischer XML. Du musst also nichts von Hand serialisieren.

Beispiel 1 — Nachname in Großbuchstaben normalisieren:

// Groovy: Property lesen, am Knoten setzen, geändertes msg zurückgeben
def nachname = msg.PID.PID_5.PID_5_1.text()
msg.PID.PID_5.PID_5_1[0].value = nachname.toUpperCase()
return msg
// GraalJS: Property direkt setzen, msg als letzter Ausdruck zurückgeben
msg.PID.PID_5.PID_5_1 = msg.PID.PID_5.PID_5_1.toUpperCase();
msg;

In Groovy brauchst du ein explizites return und setzt den Wert am Knoten über [0].value. In GraalJS reicht der letzte Ausdruck — deshalb steht msg dort einfach als letzte Zeile, und das Feld setzt sich per direkter Zuweisung.

Beispiel 2 — einen Wert in einen Map-Kontext übernehmen und ein Feld setzen:

// Groovy: Patient-Identifier merken und ein Zielfeld befüllen
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 überlebt innerhalb der Nachricht bis zu den Destinations — praktisch, um Werte zwischen Filter, Transformer und Ziel-Templates weiterzureichen.

Filter: gib einen Wahrheitswert zurück

Ein Filter entscheidet nur ja/nein. Er muss einen Boolean zurückgeben; true lässt die Nachricht durch, alles andere verwirft sie.

// Groovy: nur Nachrichten mit gesetzter Patienten-ID verarbeiten
return msg.PID.PID_3.PID_3_1.text() != ""
// GraalJS: letzter Ausdruck ist das Ergebnis
msg.PID.PID_3.PID_3_1 !== "";

Dieselbe Nachricht, drei Idiome — nebeneinander

Ein Feld lesen sieht in allen drei Engines so aus — dreimal dasselbe Feld, dreimal dasselbe msg:

// Brio-IO Rhino (js-legacy) — E4X-Brackets, wie in Mirth
msg['PID']['PID.5']['PID.5.1'].toString()
// Brio-IO Groovy (Standard) — geparstes msg, Property-Zugriff
msg.PID.PID_5.PID_5_1.text()
// Brio-IO GraalJS (js-modern) — geparstes msg, Text-Feld als String
msg.PID.PID_5.PID_5_1

Dass Rhino die Punkt-Variante derselben Nachricht sieht (PID.5.1 statt PID_5_1), erledigt der Pipeline-Prozessor — darum musst du dich nicht kümmern.

Was das für ein Mirth-Bestandsskript heißt, ehrlich gesagt: Die Mechanik kommt unverändert mit — E4X-Brackets, Mutation, msg am Ende zurückgeben, und das früher nötige new XML(msg) entfällt sogar. Und die Komponenten-Pfade auch: Mirths msg['PID']['PID.5']['PID.5.1'] läuft in Brio-IO js-legacy unverändert — beide nummerieren positional. Segment-, Feld- und Komponenten-Ebene bleiben, wie du sie kennst. Der einzige Unterschied ist Underscore statt Punkt in Groovy/GraalJS, und den setzt der Pipeline-Prozessor automatisch je Engine — darum musst du dich nicht kümmern.

Wenn du in Groovy komplex navigierst oder über viele Wiederholfelder iterierst, steht dir zusätzlich der volle XPath-Zugriff über xml.xpath offen.

Rhino (js-legacy): Mirth-Kompatibilität mit Ablaufdatum

Warum dann überhaupt noch Rhino? Weil Mirth-Bestandsskripte in JavaScript mit E4X geschrieben sind und so weit wie möglich unverändert weiterlaufen sollen. Beim Mirth-Import setzt Brio-IO importierte Skripte automatisch auf js-legacy.

Damit dieselben Skripte weiterlaufen, bekommt Rhino als einzige Engine die Punkt-Notation in der XML — die Welt, die Mirth-Skripte erwarten. msg ist in Rhino direkt ein E4X-XML-Objekt — ein new XML(msg) ist nicht mehr nötig, du greifst wie in Mirth gewohnt zu:

// Rhino (js-legacy): msg ist bereits E4X — Bracket-Zugriff wie in Mirth
var nachname = msg['PID']['PID.5']['PID.5.1'].toString();

Auch Mutation und Rückgabe bleiben Mirth-vertraut — die Engine serialisiert das E4X-Objekt an ihrer Grenze zurück nach XML:

// Rhino: Feld setzen, msg als letzter Ausdruck zurückgeben
msg['PID']['PID.5']['PID.5.1'] = 'Mueller';
msg;

Rhino ist bewusst deprecated. Bei jedem Deploy eines Channels mit mindestens einem Rhino-Skript gibt Brio-IO eine Deprecation-Warnung aus — im Log und in der API-Antwort — und listet die betroffenen Skripte namentlich auf. Auch im Skript-Editor der Web-UI erscheint eine Warnung, sobald du js-legacy wählst. Der Sunset-Pfad ist fest (ADR-016):

VersionVerhalten
Brio-IO 1.x (heute)Rhino läuft, Warnung beim Channel-Deploy
Brio-IO 2.x (~2027)Warnung wird prominenter (Banner im Channel-Editor)
Brio-IO 3.x (~2028)Rhino nur noch per Feature-Flag aktivierbar
Brio-IO 4.0 (~2029)Rhino und Punkt-Notation werden entfernt

Du hast also drei Jahre Vorlauf, um Skripte in Ruhe zu migrieren — Skript für Skript, nicht als Big-Bang.

In der Praxis: Transformer im UI anlegen

Im Web-UI legst du einen Transformer direkt im Channel-Editor an. Der Editor basiert auf Monaco (dieselbe Engine wie in VS Code), mit Syntax-Highlighting pro Sprache. Die Engine wählst du pro Skript — ein Channel kann problemlos einen Groovy-Filter und einen js-legacy-Transformer aus Mirth kombinieren. Der Pipeline-Prozessor serialisiert die Nachricht vor jedem Schritt automatisch in der richtigen Notation (Underscore für Groovy/GraalJS, Punkt für Rhino) — darum musst du dich nicht kümmern.

Ein typischer Migrationsweg aus Mirth sieht so aus:

  1. Skripte auf js-legacy legen — E4X-Mechanik und Komponenten-Pfade laufen unverändert weiter (positional, wie in Mirth). Ein Blick in den Message-Browser bestätigt dir die kanonische XML jeder empfangenen Nachricht.
  2. Skript für Skript auf Groovy (empfohlen) oder GraalJS umstellen und dabei die Bracket-Strings durch Property-Zugriff ersetzen (msg.PID.PID_5.PID_5_1 / in Groovy mit .text()).
  3. Fertig, wenn kein js-legacy-Skript mehr übrig ist — dann verschwindet auch die Deprecation-Warnung.

Ausblick

Ein ausführlicher Migrations-Guide von Rhino zu Groovy ist in Arbeit — mit Muster-für-Muster-Übersetzungen der häufigsten E4X-Idiome, inklusive dem Anlegen von Segmenten (createSegment) und dem Umgang mit Escape-Sequenzen. Wir verlinken ihn hier, sobald er steht.

Bis dahin kannst du dir die Engines live ansehen: Auf der öffentlichen Demo laufen Channels mit echten Transformern, und der Message-Browser zeigt dir die kanonische XML, gegen die deine Pfade laufen würden.

Und wenn du überlegst, deine Mirth-Channels auf Brio-IO zu heben, und sehen willst, wie sich deine echten Skripte übersetzen lassen — frag uns nach einem Early-Access-Zugang. Schreib uns einfach, welche Schnittstellen du integrieren willst.