19 January 2017

Datenflüsse sind ja momentan bei uns ein wichtiges Thema. Bereits heute schieben wir tagein tagaus viele Daten umher. Importieren sie und unsere Datenbank, bauen sie um und exportieren sie auch wieder. Und in Zukunft werden Datenflüsse für uns noch zentraler werden. Aus diesem Grund wollen und brauchen wir etwas Generisches und Rock-solides.

Schaut man sich unsere heutigen Anwendungsfälle an, sieht man viele Gemeinsamkeiten:

  • Herunterladen / Umherkopieren von Dateien (INTERLIS, Shapefiles, CSV)

  • Import der Dateien in die PostgreSQL-Datenbank

  • Umbauen der Daten mit einer SQL-Query in der Datenbank

  • Daten aus - juhee - einer Oracle-Datenbank in unsere PostgreSQL-Datenbank importieren.

Das heisst, wir brauchen nicht die eierlegende Wollmilchsau, sondern können uns beschränken auf:

  • eine geringe Anzahl an zu unterstützenden Datenformaten.

  • einen Datenumbau in der Datenbank (SQL to the rescue).

Weil wir in Zukunft verschiedene Datenbanken für die verschiedenen Zwecke verwenden wollen, kann der Datenumbau zwar mit SQL in der Quell-Datenbank stattfinden, trotzdem müssen anschliessend die Daten in die Ziel-Datenbank transportiert werden.

An einer höchst konstruktiven Brainstormingsitzung mit Claude Eisenhut haben wir über diese ganze generische Datenfluss/-integrationsgeschichte noch weiter räsoniert und versucht solche Prozesse weiter «auseinanderzubeinlen»:

Der ganze Prozess ist ein Job. Ein Job kann aus mehreren Steps bestehen. Ein Step ist z.B. das Herunterladen von Dateien oder das Importieren einer Datei in die Datenbank etc. Ziel sollte es sein, möglichst wiederverwendbare Steps zu haben, die man nur noch aneinanderzureihen hat, um eine zuverlässige Datenintegration inkl. Datenumbau zu erhalten. Der AGI-Mitarbeiter muss also nicht mehr das hundertste Individualskript mit php4 oder Python schreiben, sondern muss nur noch Steps zusammenstöpseln und die gewünschten Parameter eintragen.

Für den Datenimport bräuchten wir als Steps ein paar robuste 1:1-Transformatoren, wie z.B. CSV in die Datenbank oder INTERLIS in die Datenbank. Für den Datenumbau braucht es einen DB2DB-Step, dem man einfach SQL übergeben kann und anschliessend das Resultat der Query in die Ziel-Datenbank schreibt.

Es war schon Aufbruchstimmung als die Diskussion auf das Thema «Ausführen eines Jobs» kam. Und da fiel das Zauberwort von Claude: «Ihr könnt auch ant verwenden.» Ein Build-Tool. Das macht ja genau das, was wir wollen. Es hat (voneinander abhängige) einzelne Schritte, die ausgeführt werden, z.B.:

  1. Quellcode kompilieren

  2. Kompilierte Klassen zu einem Paket/Programm schnüren.

  3. Das Programm irgendwo hinkopieren.

Falls das Kompilieren Fehler wirft, wird das Paketieren und Kopieren gar nicht erst ausgeführt. Analog dazu soll bei uns der Importschritt nicht stattfinden, falls beim Herunterladen der Datei ein Fehler auftaucht ist.

Anstelle von ant habe ich mich zum Ausprobieren aber für Gradle entschieden. Was bekommt sonst noch «gratis»:

  • Steps (Tasks in Gradle) können einzeln angestossen werden.

  • Ein Job (Project) - also ein Build - kann auch innerhalb eines Groovy-Skript ausgeführt werden und nicht nur auf der Konsole.

  • Eigene Tasks sind einfach selber in Groovy oder Java programmierbar.

Als kleiner Teaser hier ein Build-Skript, das eine INTERLIS-Datei kopiert und anschliessend mit ilivalidator prüft:

import ch.ehi.basics.settings.Settings
import org.interlis2.validator.Validator
import ch.ehi.basics.logging.EhiLogger

buildscript {
    repositories {
        mavenCentral()
        maven {
            url "http://www.catais.org/maven/repository/release/"
        }
    }
    dependencies {
        classpath group: 'org.interlis2', name: 'ilivalidator', version: '0.9.0'
    }
}

// Copy file
task copyFile(type: Copy) {
    from("/Users/stefan/Projekte/agi-data-integrator/data/ch_254900.itf")
    into("/opt/tmp/")
}

// Validate INTERLIS file
task validate(type: IlivalidatorTask, dependsOn: 'copyFile') {
    fileName = "/opt/tmp/ch_254900.itf"
}

// Custom Task
class IlivalidatorTask extends DefaultTask {
    @Input String fileName

    Boolean success

    @TaskAction
    def validate() {
        def settings = new ch.ehi.basics.settings.Settings()
        settings.setValue(Validator.SETTING_ILIDIRS, Validator.SETTING_DEFAULT_ILIDIRS)
        success = Validator.runValidation(fileName, settings)
        if (!success) {
            throw new GradleException("INTERLIS validation failed.")
        }
    }
}

Das Beispiel ist natürlich nicht sonderlich sinnvoll, aber es zeigt ein paar Grundprinzipien ganz gut. Ausführen muss man den Build auf der Konsole mit gradle validate, um das ITF zuerst zu kopieren und anschliessend zu validieren. Will man nur validieren ohne vorgänging zu kopieren, kann man den copy-Task auch ausschliessen: gradle validate -x copyFile.

Falls die INTERLIS-Datei fehlerfrei ist, wird der Build erfolgreich beendet: BUILD SUCCESSFUL. Falls sich Fehler in der INTERLIS-Datei eingeschlichen haben, wird der Build mit BUILD FAILED und weiteren Fehlermeldungen beendet:

BUILD FAILED

Die selber geschriebenen Tasks (hier IlivalidatorTask) kann man natürlich noch auf verschiedene Weisen auslagern und so wiederverwenden. So würden dann auch noch ein Zeilen zu Beginn des Skripts wegfallen und es blieben die beiden Tasks übrig (7 Zeilen).

Das Ganze ist momentan noch nicht mehr als eine Spielerei. Ob weitere unserer Anforderungen so einfach abgedeckt werden können, muss sich noch zeigen.

Stay tuned for more gradle data integration magic.

Posted by Stefan Ziegler. | KGDI , GDI , Gradle , Groovy , Java , Datenintegration , know-your-gdi