Suche
Power Tools Woche

Flyway ist ein einfaches Tool, das die Migration von Datenbanken zum Kinderspiel macht

Christian Grobmeier

Ein Update steht an. Kein Problem: Projekt gebaut, auf den Server hochgeschoben, und in Windeseile ist auch der Application Container neu gestartet. Alles schon tausendmal gemacht. Daher kann man ja auch den Integration-Test-Server außen vorlassen und direkt in Produktion deployen. Ah, das Wochenende! Das wird toll… aber hoppla, der eigentlich nur kurz dauernde Test fördert eine SQLException zu Tage. Oh nein… irgendwer hat doch tatsächlich eine neue Spalte eingeführt… wo ist nur das *!%$! SQL-Skript?

Flyway wurde mir ursprünglich von einem Kollegen empfohlen. Er musste mich dreimal mit der Nase darauf stoßen: Es sei leicht zu integrieren und helfe unglaublich. Na, wer’s glaubt! Ich kannte bereits ein paar solche Werkzeuge und verließ mich lieber auf Migrationsskripte, die ich per Hand ausführte. Wichtigster Helfer: der Release-Plan. Ich habe das Skript fast nie vergessen. Liquibase ist eines dieser Tools, die ich mir mal angesehen habe. Es soll helfen, die Datenbank „versioniert“ zu entwickeln. Es erzeugt ein großes XML-Dokument aus dem DB-Schema. Wer etwas verändern möchte, verändert das XML, und die Änderung wird dann auf die Datenbank reflektiert. Das Konzept ist sehr mächtig, aber es ist nicht gerade trivial, dieses Werkzeug einzusetzen. Ich wünschte mir ein triviales Tool, das im Prinzip genau das macht, was ich auch mache. Flyway ist genau dieses Tool.

Flyway

Das Prinzip hinter Flyway ist zunächst einfach: Man hinterlegt ein SQL-Skript (funktioniert auch mit Java-Klassen) mit einem bestimmten Namensmuster in einen bestimmten Ordner. Dann kann man mittels Maven, Ant oder ein wenig Java-Magie die Datenbank auf den aktuellsten Stand bringen. Selbst wenn die Datenbank leer ist: Flyway fängt mit der kleinsten Versionsnummer an und arbeitet sich nach oben. „Vollinstallationen“ sind also kein Problem. Das Tool erkennt die aktuelle Version der Datenbank, indem es eine eigene Tabelle unterhält. Dort wird der gegenwärtige Zustand gespeichert. Der Start mit Flyway kann auch zu einem späteren Zeitpunkt erfolgen. Ich war schon weit jenseits der Version 2.2.x, als ich damit begann. Flyway kann relativ einfach eine Datenbank als „Version x.y.z“ markieren, und fängt dann mit der Versionierung eben dort an.

Flyway in Action

Flyway kann standalone, also z. B. mittels Shell-File oder Ant, verwendet werden. Mein Ziel dagegen war es, etwas näher an das so genannte Continuous Deployment heranzukommen. Der letzte Schritt wäre es, ein Hook aufzurufen, wenn ein Commit erfolgt. Mein Server sollte sich dann die letzten Änderungen holen, bauen, Flyway ausführen und anschließend einen Restart meiner Web-App durchführen. Bisher war eben die Datenbank das größte Problem. Letztlich habe ich mich entschieden, beim Start meiner Web-App die Datenbankversion zu prüfen. Denn man kann natürlich auch vergessen, Flyway auszuführen, wenn man doch einmal manuell deployen möchte.

Listing 1 zeigt zwei neue Abhängigkeiten, die in meinem Projekt hinzugekommen sind. Zum einen eben Flyway, zum anderen commons-dbcp. Eigentlich verwende ich Apache Cayenne als ORM, allerdings kann ich die Datenbankverbindung von Cayenne an dieser Stelle noch nicht gebrauchen. Im Anschluss besteht bereits die Möglichkeit, Flyway zu konfigurieren und von der Kommandozeile auszuführen. Eine Beispielkonfiguration zeigt Listing 2. Die Parameter hierzu kommen in meinem Fall aus meinem Maven-Profil. An dieser Stellen könnte dann mit

Listing 1
commons-dbcpcommons-dbcp1.4com.googlecode.flywayflyway-core1.7
Listing 2
com.googlecode.flywayflyway-maven-plugin1.7${db.username}${db.password}${db.driver}${db.url}
mvn flyway:migrate

bereits die erste Migration auf der Kommandozeile durchgeführt werden. Weitere Optionen stehen natürlich zur Verfügung. Beispielsweise kann Flyway einfach alle Datenobjekte aus der Datenbank entfernen, jedoch das Schema intakt lassen. Allerdings wollte ich mehr als nur Kommandozeilen-Flyway.

Wer mit Hibernate und Spring arbeitet, hat hier leichtes Spiel: Flyway kann als Spring Bean definiert werden. Üblicherweise wird auch eine Session Factory als Spring Bean deklariert. Wenn diese von der Flyway Bean abhängt, ist sichergestellt, dass Hibernate immer nur mit dem frischen Schema arbeitet. Die Flyway nimmt als Parameter eine DataSource an. Die kann auch über Spring definiert werden.

Nun ist es mit Cayenne so, dass ich keine DataSource im Spring-Kontext brauche. Cayenne kümmert sich selbst darum, und Entsprechendes wird im Web-Filter erledigt. Würde ich also eine solche Bean definieren, wäre diese für alle Zeiten verfügbar, auch wenn ich sie nie brauchen würde. Daher wollte ich Flyway erweitern, mich dort selbst um die Verbindung kümmern und diese im Anschluss wieder schließen. Da Flyway sehr leicht zu erweitern ist, kann man das eher übersichtliche Ergebnis im Listing 3 bewundern. Hier geschieht nicht gerade schwarze Magie. Der Code sagt alles aus: Ich konfiguriere und setze die Datenquelle, rufe migrate() auf und gebe das Ergebnis zurück. Fertig. Der dazugehörige Spring-Kontext, der seine Parameter aus einem Maven-Profil erhält, ist dann in Listing 4 zu sehen.

Listing 3
public class FlywayBean extends com.googlecode.flyway.core.Flyway {
    public int migrate() throws FlywayException {
            BasicDataSource dataSource = new BasicDataSource();
            // Datenquelle konfigurieren
            this.setDataSource(dataSource);
            int result = super.migrate();
            // Datenquelle schließen
            return result;
        }
        return 0;
    }
    // Getter, Setter
}
Listing 4
${db.driver}

Dort müssen wir als Init-Methode migrate angeben. Und Lazy-Init steht auf false. Die Bean wird demnach sofort erstellt, wenn der Context geladen wird. Da es die einzige Bean mit dieser Einstellung ist und alles andere sowieso später (im Web-Filter) geladen wird, ist dies für mich in Ordnung.

Die Properties zu den Datenbankparametern dürften kein Problem darstellen. Interessant ist hier der Parameter baseDir, der vorgibt, wo meine SQL-Skripte liegen. In meinem Fall liegen diese im Ordner:

/src/main/resources/db/migration

Jeder Skriptname beginnt mit einem großen V. Diese Konvention ist unantastbar. Im Anschluss folgt die Versionsnummer, getrennt mit Unterstrichen. Weitere Zeichen sind bei Bedarf erlaubt. In meinem Fall sieht also ein Versionsskript so aus:

V2_2_7_timeandbill

Neue Skripte würden dann einfach hochgezählt werden, wie man es eben kennt. Wer in einem bestehenden Projekt Flyway einführen will, sollte zusehen, dass seine Datenbank sauber ist, und ein initiales Skript machen. Dann kann die Datenbank selbst mit einer Versionsnummer versehen werden. Das sieht dann etwas so aus:

mvn flyway:init -Dflyway.initialVersion=2.2.7.timeandbill -Dflyway.initialDescription="Base version"

Die Datenbank wird dann mit jener Version geführt, und nur spätere Änderungen werden durchgeführt.

Fazit

Flyway ist einfach und kann trotzdem so einiges. Mit Kommandozeile, Ant und Maven-Unterstützung kann man das Tool in den meisten Umgebungen unterbringen. Auch werden die meisten üblichen Datenbanken (die relationalen) unterstützt. Die Dokumentation ist sehr gut, und die Entwicklung geht beständig weiter. Erst im Juli ist Version 1.7 erschienen, die neben zahlreichen Bugs auch Abhängigkeiten reduziert hat (so ist nun Spring optional). Wer also eher die einfache Lösung für seine Datenbankmigrationen sucht, der ist mit Flyway bestens beraten. Mit den SQL- und Java-Migrations werden wohl die wichtigsten Anwendungsfälle abgedeckt. Und wer mehr braucht: Flyway ist natürlich Open Source und steht unter der Apache License 2.0 zur Verfügung. Erweiterungen und Bugfixes werden auch in diesem Projekt gerne gesehen.

Geschrieben von
Christian Grobmeier
Christian Grobmeier ist freier Softwareentwickler und Trainer. Er interessiert sich für effizientes Programmieren und arbeitet gerade an Time & Bill. Als Apache Software Foundation Member hinterlässt er seine Spuren in verschiedenen Open-Source-Projekten, unter anderem im Apache-Logging-Projekt. Mehr Grobmeier?
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.