Nutzung einer domänenspezifischen Sprache (DSL) zur Transformation von Shopdaten

Beim Export von Shopdaten an Preissuchmaschinen machen die Betreiber genaue Vorgaben, in welchem Format die Produktdaten eingereicht werden müssen. Ein Betreiber möchte z.B. den Artikelpreis als Zahl in Cent, der andere als String mit Komma und Eurozeichen. Auch für die Angabe von Lieferbarkeit, Versandkosten oder Bildern sind sehr unterschiedliche Vorgaben zu beachten.
Die Software, die bei OTTO die Transformation und Datenbelieferung übernimmt, heißt ProPHET (Produktdaten Partnerfeed Handling & Export Tool). Dieser Artikel zeigt die Vorteile der Umstellung auf eine domänenspezifische Sprache (DSL) zur Beschreibung der Transformation dieser Daten.

Bisher war hier der Arbeitsablauf so strukturiert, dass die Aufbereitung der Daten in Ketten von Funktionen erfolgte. Zunächst wurde, um obiges Beispiel aufzugreifen, eine Funktion erzeugt, die Sonderzeichen entfernte. Diese Funktion hatte ein Ausgabeattribut, welches wiederum als Eingabeattribut für die nächste Funktion verwendet werden konnte, die aus einer Zahl (die Eurocent, z. B. 2999) eine Dezimalzahl erzeugt hat (29.99). Eine weitere Funktion erzeugte hieraus einen String nach deutschem Format (“29,99”). Dann gab es eine weitere Funktion, die das Eurozeichen angefügt hat (“29,99€”).
Bei der ursprünglichen Planung lag der Fokus auf der Wiederverwendbarkeit der einzelnen Schritte dieses Prozesses. Jede Funktion erzeugte aus einem Eingabeattribut ein Ausgabeattribut, das für alle weiteren Funktionen ebenfalls verfügbar war.

Das System hatte aber ein paar Schwächen:

  1. Es entsteht eine Unmenge an Attributen, die eigentlich nur Zwischenschritte repräsentieren und nur für eine einzige Prozesskette überhaupt benutzt werden.
  2. Die Zahl der Funktionen steigt im Laufe der Zeit immer mehr an. Dadurch leidet natürlich die Übersichtlichkeit und es entstehen leicht immer mehr Funktionen, die sich nur minimal unterscheiden und der Überblick, selbst für den geübten Fachanwender, geht verloren.
  3. Die Pflege und Verkettung dieser Funktionen miteinander musste nacheinander in einem UI erfolgen, was die Bearbeitung sehr mühsam gestaltete (Funktion1 erstellen, Funktion2 erstellen, dann Funktionen verknüpfen…).
  4. Sämtliche Zwischenfunktionen müssen nacheinander berechnet werden (für das OTTO-Sortiment handelt es sich um eine Funktion auf  mehrere hunderttausend Artikel). Da die Funktionen/Schritte einfach in ihrer Berechnung sind, benötigen sie zwar nicht viel Rechenzeit, das Persistieren der Zwischenergebnisse benötigt aber sehr wohl Zeit und Speicherplatz. Insbesondere steigt der Bedarf an Arbeitsspeicher immer weiter, da die Daten nach Möglichkeit komplett im RAM vorgehalten werden müssen um noch langsamere Zugriffe auf den Massenspeicher zu reduzieren.
Funktionsausführung vor Einführung der DSL

Funktionsausführung vor Einführung der DSL

 

Bisherige ProPHET Funktionsberechnung Ergebnis

Bisherige ProPHET Funktionsberechnung Ergebnis

Dem dritten Punkt hätte man mit mehr Hardware sicher noch eine ganze Weile entgegentreten können. Aber Übersichtlichkeit und Bedienbarkeit sind auch grundsätzliche Anforderungen an ein System und so kam im Team die Idee auf, den Mitarbeitern, die mit der Pflege der Exporte zu den einzelnen Partnern beschäftigt sind, eine andere Herangehensweise zu ermöglichen.

Hier ein Beispiel für die Funktionserstellung nach dem alten System. Für jeden einzelnen Schritt in der Transformation muss eine eigene Funktion angelegt werden, gespeichert und dann mit den anderen Funktionen über Ein- und Ausgabeattribute verknüpft werden. Jede dieser Aktionen erfordert wiederum roundtrips zum Server für Speicherung und Bearbeitung und das Heraussuchen der angelegten Attribute für den nächsten Schritt.

Funktionserstellung nach bisheriger Logik

Funktionserstellung nach bisheriger Logik

Als Lösung kam aus dem Team der Vorschlag zu einer domänenspezifischen Sprache (DSL), die dem Fachbereich in einem übersichtlichen Editor die Umformung der Daten ermöglichen sollte. Nach ersten Vorüberlegungen entschieden wir uns, die Idee frühzeitig dem Fachbereich vorzustellen um Feedback zu erhalten.
Der anfänglichen Skepsis dort, als es darum ging, so etwas wie eine Programmiersprache zu verwenden, begegneten wir mit dem Hinweis, dass die Dinge die sie dort bereits mit Excel tagtäglich vollbrachten, weit über einfache Programmierung hinausgingen. Und wir keinerlei Zweifel daran hatten, dass sie damit zurechtkommen würden.

Nach einigen Vorüberlegungen fiel die Entscheidung auf eine Implementierung mit Groovy, da benutzerdefinierte Parser hier besonders leicht erzeugt werden können und es sich sehr gut in das restliche System, welches in der JVM arbeitet, integriert.

Im Frontend wird CodeMirror benutzt, ein flexibler, in JavaScript geschriebener Code-Editor, der sich gut konfigurieren und erweitern lässt. Er bietet Codevervollständigung, Syntax-Highlighting und Syntaxfehler können im bearbeiteten Code leicht markiert und mit einer aussagekräftigen Fehlermeldung versehen werden.

Für die Berechnung der Funktionen werden diese zunächst einmal vorkompiliert und für die Ausführung am jeweiligen Produkt an einen neuen Kontext gebunden, in dem alle Variablen zur Verfügung stehen, die sie für die Berechnung brauchen. Jedes DSL-Skript gibt hierbei exakt ein Ergebnis zurück.

Die DSL-Skripte verwenden nun die Kontrollstrukturen, die auch Groovy bereits zur Verfügung stellt, wie if, else, etc. Bis auf String- und Rechenoperationen sind alle nicht benötigten Befehle und Bibliotheken auf einer Blacklist und hierdurch auch gar nicht erst verfügbar. Weiterhin werden eigene DSL-Funktionen definiert, die für die Transformation der Daten fachlich notwendig sind, wie
GREATER_THAN, EXTRACT_ALL, SUBLIST, GET_LIST_ITEM, MATCHES, REPLACE, etc. Diese sind namentlich an die Schreibweise der Excel-Makrofunktionen angelehnt.

Den in der DSL geschriebenen Funktionen wird nun für die Verarbeitung ein Objekt übergeben, in dem alle relevanten Informationen zu einem Produkt gespeichert sind. Auf die Informationen kann in den Skripten ganz normal über Dot-Notation zugegriffen werden (Bsp.: attr.var_description um die Beschreibung des Produkts zu verwenden).

Die DSL-Funktionen innerhalb des Skripts nutzen automatisch das Ergebnis der vorherigen Funktion als Eingabewert für sich selbst. Nur in Fällen, in denen dies nicht gewünscht ist, wird entweder eine Konstante definiert, oder ein Wert aus dem übergebenen Objekt mit den Produktattributen benutzt. Dies erhöht die Lesbarkeit und verdeutlicht den Programmfluss.

CONCAT(“Hallo ”, “Welt”) // “Hallo Welt” wird der aktuelle Zustand
REPLACE(“Hallo”, “Moin”) // “Moin Welt”

Innerhalb eines Skripts werden nun alle notwendigen Transformationen durchgeführt, um das Zielattribut zu erzeugen. Weitere Zwischenschritte sind hierdurch nicht mehr notwendig. Für die Effizienz in der Abarbeitung wird das Groovy-Shellobjekt wiederverwendet und die Funktionen werden kompiliert vorgehalten. So muss jeweils nur noch der Kontext übergeben und das Ergebnis abgegriffen werden.

function_apply

Funktionsausführung nach Einführung der DSL

Beispielhaft könnte ein DSL-Skript so aussehen:

// Hat das Produkt einen Namen?
if (NOT_EMPTY(attr.var_name)){  // "Testmarke® Notebook, Elektron™
    // Ersetze alle Zeichen ausser Wortzeichen, whitespaces, Punkten und Kommata.
    // Das Ergebnis des REPLACE wird der aktuelle Zustand
    REPLACE(attr.var_name, "[^\\w\\s,\\.]", "") // "Testmarke Notebook, Elektron
    // Splitte den aktuellen Zustandswert bei whitespaces in eine Liste
    EXTRACT_ALL("[^\\s]*") // "Testmarke|Notebook,|Elektron,"
    // Nimm den ersten Eintrag aus der Liste
    GET_LIST_ITEM(FIRST) // Testmarke
}

Sowohl für die Transparenz der Abläufe, als auch für die Flexibilität bedeutet dies einen enormen Fortschritt. Außerdem kann die Sprache auf diesem Fundament an den Anforderungen der Fachabteilung mitwachsen.

Neuer Funktionseditor

Jetzt wird gesamte Transformation in einem einzigen Skript im Editor konfiguriert

Diese Umstellung des Systems verhindert den Wildwuchs an Funktionen, die miteinander verkettet sind. Aktuell wird noch etwa ein Fünftel der Attribute berechnet, die vorher erzeugt wurden. Entsprechend weniger müssen im Arbeitsspeicher vorgehalten und im Storage der Datenbank persistiert werden.  Jede Transformation ist nun in sich geschlossen, wodurch auch unvorhergesehene Abhängigkeiten praktisch ausgeschlossen sind. Die Mitarbeiter haben einen komfortablen Editor, in dem sie ihre Transformation von Anfang bis Ende durchführen können und, mit einer Vorschaufunktion auf einen beliebigen Artikel, das Ergebnis sofort sehen können, was eine umfassende fachliche Testbarkeit ermöglicht.

Die Begeisterung des Fachbereichs bei der Vorstellung und Einweisung in die neue DSL war für uns dann der Beweis, dass das Projekt gelungen ist.

 

Tagged with: , , , ,
Veröffentlicht in Development

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: