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 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_5→XPN_1→FN_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 —
trueheiß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.
| Engine | Marker | Sprache | Rolle |
|---|---|---|---|
| Groovy | groovy | Apache Groovy | Standard für neue Skripte |
| GraalJS | js-modern | modernes JavaScript (ES2024) | für alle, die lieber JS schreiben |
| Rhino | js-legacy | JavaScript mit E4X | nur 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-PayloadschannelMap,sourceMap,responseMap— Maps für den Nachrichten-KontextglobalMap,globalChannelMap— server- bzw. channelweite persistente Mapstmp— ein Scratchpad, das nur für diese eine Ausführung existiertlogger— SLF4J-Logger für Log-Ausgabenxslt— 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_10liefert"MSG00001". - Zusammengesetztes Feld → eine Ebene je Komponente:
msg.PID.PID_5.PID_5_1liefert"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):
| Version | Verhalten |
|---|---|
| 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:
- Skripte auf
js-legacylegen — 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. - 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()). - 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.