Transformieren von Nachrichten mit Mule DataWeave – Teil 3: Schleifen und Gruppierungen

Keine Kommentare

In den ersten beiden Teilen dieser Serie habe ich bereits Collections – und damit implizit – Schleifen verwendet. In diesem Beitrag werde ich das Thema vertiefen und dabei genauer auf das Mapping mit Lambdas eingehen. Zum Abschluss werde ich Gruppierungsfunktionen erläutern.

Bevor ich tiefer in das Thema einsteige, noch eine Ergänzung zum ersten Teil. Dort hatte ich geschrieben, dass es bei DataWeave (im Gegensatz zum alten DataMapper) kein grafisches Mapping mit Drag and Drop gibt. Stimmt nicht (mehr): Mitte Dezember kam mit einem Update des AnypointStudio das grafische Mapping für DataWeave. Der Editor hat dazu eine weitere Spalte erhalten, in der die Zuordnung grafisch durch Linien dargestellt wird:DataWeave-Editor mit grafischem Mapping

Was mir an der Lösung gefällt: Die textuelle DSL hat sich nicht geändert, sie bleibt weiterhin kurz und prägnant. Die grafische Darstellung mit Drag and Drop ist nur eine Ergänzung. Wem es nicht gefällt, der kann weiter mit der Tastatur arbeiten und sich die Mappings durch die Linien anzeigen lassen. Wem das immer noch zu viel ist: Die Spalte mit den Linien lässt sich auch ausblenden. Umschalten kann man die Darstellung mit den Icons oben rechts (neben dem Button „Preview“).

Dem Editor fehlt aktuell (Stand: Dezember 2015) noch die nötige Reife: Er kommt beim Umschalten zwischen Drag and Drop und textuellem Editieren schnell durcheinander. Es empfiehlt sich, ihn nach Drag-and-Drop-Operationen zu schließen, bevor man textuell Änderungen vornimmt.

Flaches Remapping

Nun zum angekündigten Thema. Starten wir mit einem einfachen Beispiel: Eine Datenbanktabelle soll als CSV-Datei exportiert werden. Der Datenbank-Connector von Mule produziert eine Liste von Zeilen. In jeder Zeile befindet sich eine Map von Spaltenname auf Spalteninhalt. Bei gängigen Datenbanken (MySQL, Oracle etc.) ist AnypointStudio in der Lage, die Metadaten aus der Datenbank zu lesen. Name und Typ der Spalten zeigt der Editor direkt an.

Wie exportiert man nun eine Tabelle als CSV-Datei? Die Lösung ist trivial, wenn die Tabelle 1:1 exportiert werden soll und DataWeave nur die Formatumwandlung nach CSV durchführen muss:

%dw 1.0
%output application/csv

payload

Hier wird ausgenutzt, dass die Payload eine Liste enthält, die auch für CSV benötigt wird. Die Maps in der Liste legen dabei auch direkt die Spaltennamen für die CSV-Datei fest.

Über das Schlüsselwort „map“ und einen Lambda-Ausdruck lassen sich die Spalten auswählen und bei Bedarf umbenennen:
%dw 1.0
%output application/csv header=true, separator=‚;‘

payload map ({
    „gewicht“: $.gewicht_in_kg,
    „groesse“: $.groesse_in_m * 100,
    „bmi“: $.bmi
  }
)
Das Beispiel arbeitet mit der Kurzform, in der kein Name für die Schleifenvariable vergeben wird. Auf sie kann stattdessen mit $ zugegriffen werden. Da es sich um eine Map handelt, greift man auf einzelnen Elemente per Punktnation zu.

In der nicht anonymen Variante sieht man klarer, was hinter den Kulissen passiert:
%dw 1.0
%output application/csv

payload map ((a) -> {
    „gewicht“: a.gewicht_in_kg,
    „groesse“: a.groesse_in_m
  }
)
Hinter „map“ steht der Lambda-Ausdruck mit einem Parameter in runden Klammern: (a), gefolgt von einem Pfeil und dem gemappten Ausdruck.

Strukturelle Änderungen

Mit Lambdas lassen sich nicht nur Attribute umbenennen, sondern auch die Struktur eines Datensatzes ändern. Für das nächste Beispiel besteht die Eingabedatei aus einem JSON-Array von Objekten mit den Attributen gewicht und groesse, Beispiel:

[ { „groesse“: 1.88, „gewicht“: 90 }, { „groesse“: 2.00, „gewicht“: 88 } ]

Wie gehen wir vor, wenn wir stattdessen ein Objekt mit Arrays für Größe und Gewicht haben wollen? Wir schreiben einfach ein JSON-Objekt mit den beiden Attributen hin und iterieren darin jeweils über die Eingabedaten. Die Projektion (SQL lässt grüßen) erledigen wir jeweils mit einem Lambda:

%dw 1.0
%output application/json

{
    „massen: payload map ((p) -> p.gewicht),
    „laengen: payload map ((p) -> p.groesse)
}
Bei diesem Beispiel fehlen auf der rechten Seite die geschweiften Klammern: Es soll schließlich kein Objekt erzeugt werden, in den Arrays massen und laengen stehen nur die Zahlen.

Lasst uns mit den Zahlen etwas rechnen! Wie wäre es mit einer Summenbildung? Geht, einfach reduce verwenden:
%dw 1.0
%output application/json

{
    „massen: payload map ((p) -> p.gewichtreduce ((val, acc = 0) -> acc + val),
    „laengen: payload map ((p) -> p.groessereduce ((val, acc = 0) -> acc + val)
}
Der reduce-Operator arbeitet mit zwei Argumenten: Das erste enthält das aktuelle Array-Element, das zweite das Zwischenergebnis (Akkumulator). Es lässt sich explizit initialisieren (im Beispiel mit 0) oder enthält standardmäßig das erste Arrayelement.

Group By

Nachdem wir im letzten Beispiel ein Array von Objekten in zwei Arrays zerlegt haben, versuchen wir uns jetzt an der umgekehrten Richtung: Die Daten sollen nach dem Gewicht gruppiert werden. Aber nicht für jedes Gewicht eine Gruppe, sondern alle > 100 in die Kategorie „schwer“, der Rest in die Kategorie „leicht“.

Für Gruppierung existiert – wie in SQL – der groupBy-Operator. Aber wie erhalten wir das Mapping in die beiden Kategorien „leicht“ und „schwer“? Bei wenigen Kategorien (die dazu vorab bekannt sind) kann man Filter benutzen (siehe Teil 2 der Serie). Im anderen Fall muss eine Funktion her, mit der sich die Kategorie bestimmen lässt. Ich habe hier mittels lookup einen Flow aufgerufen, der die Funktion umsetzt:

%dw 1.0
%output application/json

{
    „data“: payload groupBy lookup(kategorie, $.gewicht)
}

Der erste Parameter enthält den Namen des Flows, der zweite die Payload. Der Flow selbst ist im Beispiel recht simpel, abhängig vom Gewicht gibt er eine der beiden Kategorien zurück:

<flow name=„kategorie“>
  <choice>
    <when expression=„#[payload &gt; 100]“>
      <set-payload value=„schwer“ />
    </when>
    <otherwise>
      <set-payload value=„leicht“ />
    </otherwise>
  </choice>
</flow>

Der Flow könnte auch noch komplexere Transformationen enthalten, Services aufrufen oder Datenbanklookups durchführen. Eben alles, was Mule sonst noch bietet.

Zusammenfassung

Nachdem ich mit DataWeave einige Zeit herumexperimentiert habe, bin ich immer noch begeistert. Viele Dinge sind damit deutlich einfacher, als wenn man zuerst mit JAXB/Jackson alles nach Java umwandelt, dort das Mapping umwandelt und dann wieder nach XML/Json zurückkonvertiert. Messungen habe ich noch nicht durchgeführt, aber es ist vermutlich auch schneller. Vom Speicherbedarf ganz zu schweigen: Da DataWeave, wo immer es möglich ist, mit Streaming arbeitet, kann man in vielen Fällen auch Datenstrukturen bearbeiten, die größer als der verfügbare Hauptspeicher sind.

Mal sehen, eventuell gibt es noch einen weiteren Teil mit Praxisberichten aus dem Projekteinsatz. Stay tuned.

 

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.