Transformieren von Nachrichten mit Mule DataWeave – Teil 1: Einführung

Keine Kommentare

MuleSoft hat mit Version 3.7 den DataMapper in Rente geschickt und durch DataWeave ersetzt. Was verbirgt sich dahinter? Mein erster Eindruck: semantisch eine Mischung aus SQL und Xslt, syntaktisch JavaScript. Ist das gelungen? Das soll jeder selbst beurteilen.

Mule bietet auch schon vor Version 3.7 verschiedene Bausteine zur Transformation von Nachrichten an, teilweise für Metadaten (Properties), teilweise für den Inhalt (Payload). Hier einige Beispiele:

  • Properties oder Inhalt lassen sich durch die Mule Expression Language (MEL) berechnen.
  • XML lässt sich mit Xslt transformieren.
  • JavaScript und Groovy stehen für Fans von Scriptsprachen zur Verfügung (serienmäßig, weitere JSR-223-konforme Scriptsprachen müssen nur in den Classpath aufgenommen werden).
  • Java-Components oder Java-Transformer bieten sich an, wenn es mal komplexer wird oder man existierenden Transformationscode wiederverwenden möchte.
  • Maus-Schubser nutzen den grafisch konfigurierbaren DataMapper um zwischen XML, Json, CSV und Pojos zu transformieren.

Gerade den letzten Baustein, mit seiner bunten Drag-&-Drop-Konfiguration der Star jeder Präsentation, wirft MuleSoft nun raus und ersetzt ihn durch DataWeave. Warum? Darüber lässt sich spekulieren. Einer der Gründe mag sein, dass der DataMapper auf Clover ETL aufsetzt und MuleSoft keine Lizenzgebühren an eine andere Firma zahlen möchte. Für Anwender ist das egal.

Was mich als Anwender am DataMapper stört, ist die Tatsache, wie man ihn konfiguriert: rein grafisch. Nichts gegen grafische Konfiguration (ich male meine Flows gerne in AnypointStudio), das Ergebnis sollte jedoch eine Textdatei sein, die man notfalls auch ohne Tool lesen und bearbeiten kann. Der DataMapper arbeitet jedoch mit einem XML-Format, das man getrost auch als Binärformat hätte schreiben können: komplett unleserlich.

Warum ist mir ein menschenlesbares Textformat wichtig? In ihm kann man problemlos Änderungen nachvollziehen, wenn man sich Diffs in der Versionsverwaltung (svn, git, …) anschaut. Es skaliert meistens auch besser, wenn’s mal komplizierter wird. Dagegen verliert man in dem Liniengewusel des DataMapper schnell die Übersicht.

Beispiel

Genug philosophiert, starten wir mit einem DataWeave-Beispiel. Im Mule-Flow wird DataWeave folgendermaßen referenziert:

<dw:transform-message doc:name="Transform Message">
  <dw:set-payload resource="simpleObject2Json.dwl"/>
</dw:transform-message>

Und in der DWL-Datei (die man auch inline in den Flow schreiben könnte) steht:

%dw 1.0
%input payload application/java
%output application/json
---
{
    "name": payload.name,
    "gender": payload.gender
}

Was passiert darin? Aus einem POJO werden zwei Attribute (name, gender) in ein Json-Objekt transformiert. Die dazu notwendige Meta-Information steht direkt unter der Versionsnummer von DataWeave.

Unterhalb von der Zeile „—“ folgt die Transformationslogik. Ihre Syntax lehnt sich an Json an (auch wenn ein anderes Format wie XML ausgegeben wird). Sie definiert die Struktur der Ausgabe. Man muss sich dabei natürlich an die Restriktionen des Ausgabeformats halten, zum Beispiel erlaubt Json keine doppelten Keys und in XML muss es genau einen Wurzelknoten geben.

Ob die DWL inline im XML oder extern in einer eigenen Datei (unter src/main/resources) steht, ist Geschmacksache. Ich persönlich finde es getrennt übersichtlicher. Innerhalb des AnypointStudios ist der Unterschied jedoch nur ein gesetzter Haken und die Auswahl eines Dateinamens.

Trotz des Textformats gibt es im AnypointStudio natürlich Unterstützung, so sieht der Design-Dialog aus:

Einfaches Beispiel im DataWeave-Editor vom AnypointStudio

Links stehen die Metadaten der ankommenden Mule-Message, rechts die der transformierten Message. In der Mitte wird die DWL-Datei editiert. Interessant wird es, wenn man unten auf „payload“ und „Preview“ umschaltet. Links stehen eingehende Beispieldaten (die man jederzeit editieren darf), rechts zeigt Anypoint das Ergebnis der Transformation direkt an. Bei XML-, Json- und CSV-Daten sieht man die Daten in der jeweiligen Syntax, bei Java-Klassen (wie im Beispiel links für die Eingabedaten zu sehen) wird der Inhalt als Json dargestellt:

Beispieldaten, Transformationsvorschrift und zugehörige Ausgabedaten.

Was man selbst erleben muss: Die Preview rechts wird aktualisiert, sobald man die Transformation in der Mitte editiert. Und zwar direkt, ohne Speichern oder Wartezeiten. Im Falle von Syntaxfehlern in der DWL werden diese unten rechts ebenso sofort dargestellt. Auch wenn es in einer Präsentation nicht so bunt daherkommt wie Drag-&-Drop von Attributen: Für die Praxis finde ich diese Lösung sehr brauchbar.

Anypoint kennt die Message

Kennt Mule das Format der Ausgabedaten (Stichwort DataSense, siehe Blogpost von meinem Kollegen Tobias Schaber), so lässt sich ein Gerüst der Transformation auf Knopfdruck erzeugen. Das Gleiche gilt für die Eingabedaten: In dem im Bild zu sehenden Beispiel habe ich nur „Peter“ und „male“ statt der beiden „???“ eingetragen.

Standardmäßig liest DataWeave die Payload als Eingabe und schreibt die Ausgabe auch wieder in die Payload. Die Eingabedaten müssen nicht komplett als String oder Byte-Array vorliegen, es darf auch ein Stream sein. Die Ausgabe erfolgt – außer bei Java-Objekten – ebenfalls als Stream. Falls das nächste Flow-Element kein Streaming unterstützt (was bei Mule selten ist), so muss man den Stream zuerst „materialisieren“. Am einfachsten geschieht dies mit dem <object-to-string-transformer/>.

Eine Falle lauert in der Eingabe: Hier wählt DataWeave mittels Mime-Type den korrekten Parser aus (Json, Xml, CSV). Auch wenn es so aussieht, die Zeile

%input payload appplication/xml

in der Transformationsdatei reicht dafür nicht aus. Aber es existieren drei andere Wege:

1. Der Endpoint ermittelt den Mime-Type automatisch aus den eingehenden Daten (z.B. bei http)
2. Er wird im Endpoint am Anfang des Flows manuell konfiguriert (z.B. in einem VM-Endpoint: mimeType=“application/json“)
3. Er wird im DataWeave-Transformer manuell konfiguriert (z.B.: mimeType=“application/json“)

Dummerweise fällt dieser Fehler nicht in der Preview auf, stattdessen meldet sich der Parser mit einer nicht offensichtlichen Fehlermeldung.

Mehr als ein Ziel

Die beiden Bilder oben zeigen noch zwei andere Features von DataWeave, allerdings nicht auf den ersten Blick: Über dem Editor lässt sich einstellen, wo das Ergebnis der Transformation gespeichert werden soll:

Es sind auch mehrere Ziele für die Transformation möglich.

Neben „Payload“ existieren dort die diversen Variablen-Scopes als Ziele, per Drop-Down auszuwählen. Unten rechts im Bild sieht man ein kleines Plus-Zeichen im Kreis: Ein Klick darauf öffnet einen weiteren Tab-Reiter, dessen Ausgabe an ein weiteres Ziel geleitet werden kann. Ein DataWeave-Transformer kann somit nicht nur eine neue Payload setzen, sondern zusätzlich eine oder mehrere Variablen bestücken.

Das geht auch anders, mit dem Message-Enricher-Pattern. Das hat jedoch gegenüber der Integration in DataWeave einige Nachteile:

  1. Es bläht den Flow auf, für jedes Ziel benötigt man einen Message-Enricher mit embedded DataWeave-Transformer.
  2. Streaming funktioniert nicht: Vor dem ersten Message-Enricher muss der Stream komplett in den Speicher gelesen werden, damit die Daten in den weiteren Enrichern noch zur Verfügung stehen. Der Speicherverbrauch wird dadurch erhöht.
  3. Es erfordert, dass die ankommende Payload mehrfach durch den Parser von DataWeave läuft.

Pojos und Schleifen

Im nächsten Bild habe ich das Beispiel von oben um zwei weitere Features ergänzt:

  • Pojos als Ziel
  • Schleifen

Erzeugt man aus XML oder Json Java-Objekte, muss man DataWeave mitteilen, welche Klassen verwendet werden sollen. Dies geschieht mit „as :object“ hinter der schließenden Klammer eines Blocks in der DWL. Das darauf folgende class-Attribut hat als Wert den voll qualifizierten Klassennamen (mit Package).

Ansonsten werden an die Klasse keine besonderen Anforderungen gestellt. Sie muss einen public-Konstruktor ohne Argumente enthalten und auch ansonsten ein Pojo sein, das heißt es sind Public Getter und Setter für die Member-Variablen notwendig. Public Member-Variablen funktionieren nicht.

Transformation in Pojos, mit einer Liste als Attribut.

Nur ein Pojo zu transformieren ist eher banal, daher enthält die Klasse „Parent“ noch eine java.util.List von „Child(ren)“. Den Typ der Listenelemente könnte man hier sogar weglassen, Mule würde ihn in diesem einfachen Beispiel auch alleine erkennen. Die Angabe des Typen kann aber beim Einsatz von Vererbungshierarchien oder Interfaces notwendig sein.

Wie geschieht das Mapping der Liste? Es wird keine Schleife ausprogrammiert, stattdessen erstellt man ein Mapping des Json-Arrays „children“ auf die Member-Variable „children“ im Pojo. Dahinter folgt dann, wie ein einzelnes Element transformiert werden soll. Der einzige Unterschied zum Mapping des Wurzelelements besteht darin, dass man nicht mehr mit „payload“ auf die Wurzel zugreift, sondern mit „$“ auf das aktuelle Element.

Das funktioniert natürlich nicht nur über eine 1:n-Beziehung, sondern auch mit tiefer verschachtelten Strukturen.

Zusammenfassung und Ausblick

Was haben wir bisher von DataWeave gesehen? Wir können Formate (XML etc.) ineinander umwandeln, das Ergebnis in der Payload oder Variablen speichern sowie die Attributnamen beim Mapping ändern.

Das CSV-Format haben wir bisher ignoriert. Auf der einen Seite ist es einfacher als die anderen Formate, da es flach ist. Auf der anderen Seite sind Feinheiten wie Trenner und Spaltennamen einzustellen. No Big Deal.

Ein Test unter realen Bedingungen steht noch aus: Schließlich stimmen in der Realität die Strukturen von Quelle und Ziel nicht notwendigerweise überein: Statt der Liste in der Eingabe existiert in der Ausgabe beispielsweise nur noch eine Summe. Oder eine Liste muss in zwei Listen umgewandelt werden. Oder in der Eingabe unterscheiden sich Daten nur in einem Attribut, während in der Ausgabe verschiedene Klassen stehen. Auch XML-Namespaces, dynamische Attribute oder die Zusammenarbeit mit anderen Mule-Komponenten fehlt noch. Es bleiben also noch spannende Themen für weitere Posts…

Links

Roger Butenuth

Dr. Roger Butenuth hat in Karlsruhe Informatik studiert und anschließend in Paderborn promoviert (Kommunikation in Parallelrechnern). Er hat langjährige Erfahrung in der Projekt- und Produktentwicklung.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.