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.
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 ProPHETProduktdatenPartnerfeedHandlingExportTool). 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:
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.
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 (Bspattr.var_descriptionum 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.
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.
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.
We have received your feedback.