Transformer in Brio: drei Engines, eine Pipeline — und endlich lesbare HL7-Pfade
Wie Brio Transformer skriptet: Groovy, GraalJS und Rhino nebeneinander, warum der xml-Helfer den alten Mirth-Bracket-Wahnsinn ablöst, und wie du Bestandsskripte Schritt für Schritt migrierst.
Transformer in Brio: 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 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 mitbringt, warum HL7-Pfade in Brio lesbar sind (und in Mirth Connect nicht), und wie du Bestandsskripte aus Mirth ohne Big-Bang mitnimmst.
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 führt Skripte nicht in einer einzigen Sprache aus.
Drei Engines, ein Kontext
Brio 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 als kanonisches XMLxml— den XML-Helfer zum Lesen und Schreiben von FeldernchannelMap,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. Das Feld PID-5.1 (der Nachname) sieht in der klassischen Mirth-Welt so aus:
<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-Antwort: Underscores statt Punkte
Brio ist ein neuer Server und korrigiert diesen historischen Designfehler. In der kanonischen Repräsentation trennt Brio Feld-Ebenen mit Underscores (siehe ADR-016):
<PID>
<PID_5>
<PID_5_1>Schmidt</PID_5_1>
</PID_5>
</PID>
Ein Underscore ist in keiner Sprache ein Operator — plötzlich sind Element-Namen ganz normale Bezeichner.
Der xml-Helfer: der empfohlene Weg
Wichtig zu verstehen: msg ist ein String (das serialisierte kanonische
XML), kein bereits geparstes Objekt. Direkt msg.PID... darauf zuzugreifen
funktioniert nicht — du würdest einen String dereferenzieren.
Deshalb bindet Brio in jeden Skript-Kontext den xml-Helfer ein. Er liest
und schreibt Felder über einen Pfad — engine-übergreifend identisch:
// Groovy — ein Feld lesen
def nachname = xml.get(msg, "ADT_A01.PID.PID_5.PID_5_1") // "Schmidt"
// GraalJS (js-modern) — dasselbe, gleiche Signatur
let nachname = xml.get(msg, "ADT_A01.PID.PID_5.PID_5_1");
Der Pfad startet am Wurzel-Element der Nachricht (bei einem ADT^A01 also
ADT_A01). Als Trennzeichen akzeptiert der Helfer sowohl den Punkt als auch
den Schrägstrich — "ADT_A01/PID/PID_5/PID_5_1" tut dasselbe. Fehlt ein
Feld, gibt get einen leeren String zurück statt zu werfen.
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, "//PID_5/PID_5_1/text()");
Ein Transformer verändert — indem er zurückgibt
Ein Transformer mutiert msg nicht in place. Er gibt die neue Nachricht
als String zurück — dieser Rückgabewert wird zur neuen kanonischen XML der
Nachricht. xml.set liefert genau so einen String: die komplette Nachricht
mit dem geänderten Feld.
Beispiel 1 — Nachname in Großbuchstaben normalisieren:
// Groovy: lesen, umformen, geändertes Dokument zurückgeben
def nachname = xml.get(msg, "ADT_A01.PID.PID_5.PID_5_1")
return xml.set(msg, "ADT_A01.PID.PID_5.PID_5_1", nachname.toUpperCase())
// GraalJS: der Wert des letzten Ausdrucks ist der Rückgabewert
let nachname = xml.get(msg, "ADT_A01.PID.PID_5.PID_5_1");
xml.set(msg, "ADT_A01.PID.PID_5.PID_5_1", nachname.toUpperCase());
In Groovy brauchst du ein explizites return. In GraalJS (und Rhino) reicht
der letzte Ausdruck — deshalb steht xml.set(...) dort einfach als letzte
Zeile.
Beispiel 2 — einen Wert in einen Map-Kontext übernehmen und ein Feld setzen:
// Groovy: Patient-Identifier merken und ein Zielfeld befüllen
def patientId = xml.get(msg, "ADT_A01.PID.PID_3")
channelMap.put("patientId", patientId)
return xml.set(msg, "ADT_A01.PID.PID_3", patientId.trim())
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 PID-3 verarbeiten
return xml.get(msg, "ADT_A01.PID.PID_3") != ""
// GraalJS: letzter Ausdruck ist das Ergebnis
xml.get(msg, "ADT_A01.PID.PID_3") !== "";
Groovy-Bonus: Property-Zugriff dank Underscores
Weil die Element-Namen jetzt gültige Bezeichner sind, kannst du in Groovy die
Nachricht auch klassisch mit XmlSlurper parsen und direkt per Property
navigieren — genau der Komfort, der in Mirth an den Punkten scheiterte:
// Groovy: einmal parsen, dann natürlicher Property-Zugriff
def hl7 = new XmlSlurper().parseText(msg)
def nachname = hl7.PID.PID_5.PID_5_1.text()
Man sieht den Unterschied am besten nebeneinander:
// Mirth Connect
msg['PID']['PID.5']['PID.5.1'].toString()
// Brio (Groovy, geparst)
hl7.PID.PID_5.PID_5_1.text()
Für die meisten Transformer ist der xml-Helfer der direktere Weg (kein
explizites Parsen, engine-übergreifend). Aber wenn du in Groovy über viele
Felder iterierst oder komplex navigierst, ist der XmlSlurper mit
Underscore-Namen eine echte Erleichterung.
Rhino (js-legacy): Mirth-Kompatibilität mit Ablaufdatum
Warum dann überhaupt noch Rhino? Weil Mirth-Bestandsskripte in JavaScript mit
E4X geschrieben sind und ab Tag 1 laufen sollen, ohne dass du sie
anfassen musst. Beim Mirth-Import setzt Brio 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. E4X
arbeitet dabei auf einem echten XML-Objekt, das du aus msg erzeugst:
// Rhino (js-legacy): msg zu E4X parsen, dann Mirth-gewohnter Zugriff
var m = new XML(msg);
var nachname = m['PID']['PID.5']['PID.5.1'].toString();
Rhino ist bewusst deprecated. Bei jedem Deploy eines Channels mit
mindestens einem Rhino-Skript gibt Brio 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 1.x (heute) | Rhino läuft, Warnung beim Channel-Deploy |
| Brio 2.x (~2027) | Warnung wird prominenter (Banner im Channel-Editor) |
| Brio 3.x (~2028) | Rhino nur noch per Feature-Flag aktivierbar |
| Brio 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 aus Mirth importierten js-legacy-Transformer
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:
- Channel importieren — alle Skripte landen auf
js-legacyund laufen sofort. - Die Deprecation-Warnung zeigt dir genau, welche Skripte betroffen sind.
- Skript für Skript auf Groovy (empfohlen) oder GraalJS umstellen und
dabei die Bracket-Strings durch
xml.get/xml.setersetzen. - Fertig, wenn kein
js-legacy-Skript mehr übrig ist — dann verschwindet auch die 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. Wir verlinken ihn hier, sobald er steht.
Bis dahin: Wenn du überlegst, deine Mirth-Channels auf Brio 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.