single.php
< Beitrag von Frank Winter

Liquibase – das Tool für agiles Database Deployment

Moderne und agile Softwareentwicklungsprojekte zeichnen sich u.a. durch hochfrequente Auslieferungen aktualisierter Softwarestände auf Entwicklungs-, Test- und Produktivsysteme aus. Während es vergleichsweise unproblematisch ist, neue Versionen einer Software zu paketieren und auszuliefern, ist dies bei Datenbankänderungen deutlich schwieriger. Die sich hierbei stellenden Herausforderungen und die zugehörigen Lösungsansätze, die das Open Source Tool „Liquibase“ hierbei liefert, werden im Folgenden dargestellt. Dieser Blog-Beitrag beruht auf einem Artikel, den ich Ende Mai 2014 in den DOAG-News veröffentlicht habe.

Bei dem regelmäßigen Deployment von Datenbankänderungen (DDL und DML) stellen sich in den meisten Entwicklungsprojekten folgende Herausforderungen:

  • Es sollen mit jeder Auslieferung nur Änderungen zu dem jeweils letzten Release eingespielt werden. Separate Installationsskripte sind bei kurzen Auslieferungszyklen jedoch aufwändig und fehleranfällig.
  • Wenn in verschiedenen Entwicklungssträngen parallel gearbeitet wird, ist die „Version“ eines Datenbankschemas nicht immer eindeutig identifizierbar und es ist schwer, parallel entstandene Schemaänderungen zusammenzuführen.
  • Datenbankänderungen werden oft gänzlich unabhängig von Code-Änderungen eingespielt. Das ist aber gefährlich. Code und Datenbankänderungen gehören insbesondere bei einer Continuous Delivery in eine gemeinsame Auslieferung.
  • Änderungen von Daten und Strukturen lassen sich nicht ohne Weiteres rückgängig machen. Wie geht man z.B. mit einem umfangreichen Änderungsskript um, das mittendrin abbricht? Welchen Zustand hat das Datenbankschema?

Diese Herausforderungen lassen sich weitgehend mit der Open Source Library Liquibase in den Griff bekommen.

Was ist Liquibase?

Liquibase ist ein datenbankunabhängiges Database Change Management Tool und dient der Durchführung und Verwaltung aller Arten von Datenbankänderungen (DML und DDL). Änderungen, die über Liquibase-Skripte durchgeführt werden, lassen sich einfach verwalten und bei Bedarf weitgehend rückgängig machen. Liquibase benötigt in der aktuellen Version nur eine Java-Laufzeitumgebung ab Version 1.6. Es eignet sich hervorragend für den Einsatz in agilen Projekten, da es sich sehr gut in eine Umgebung mit Continuous Integration bzw. Continuous Delivery integriert.

Wie arbeitet Liquibase?

Liquibase baut über JDBC eine Verbindung zu einem bestimmten Datenbankschema auf und führt dort eine beliebige Anzahl von Liquibase-Skripten (sog. ChangeLogs) aus. Diese ChangeLogs rufen entweder weitere ChangeLogs auf oder enthalten eine oder mehrere logisch in sich abgeschlossene Datenbankänderungen (sog. ChangeSets). Solche ChangeSets enthalten zumeist Daten- oder Struktur­änderungen, die entweder in einer Liquibase-spezifischen, datenbankunabhängigen Syntax (siehe unten) oder in datenbankspezifischem Code (wie z.B. PL/SQL-Blöcke) formuliert sind.

Liquibase DOAG-Abbildung

Sämtliche bereits durchgeführten Änderungen werden in dem betroffenen Datenbankschema in der Tabelle DATABASECHANGELOG festgehalten, die zu jedem ChangeSet einen separaten Datensatz enthält. Damit ist Liquibase bekannt, welche Änderung erfolgreich durchgeführt wurde.

Die Tabelle DATABASECHANGELOG enthält folgende Informationen:

Spalte Beschreibung
ID Frei definierbare ID des ChangeSet. Muss gemeinsam mit den Feldern AUTHOR und FILENAME eindeutig sein.
AUTHOR Autor des Skriptes
FILENAME Pfad und Name des Skriptes
DATEEXECUTED Zeitpunkt der Ausführung
ORDEREXECUTED Zähler für die Ausführungen
EXECTYPE Ausführungstyp; meist EXECUTED oder RERAN
MD5SUM MD5-Hash je ChangeSet
DESCRIPTION automatisch generierte Beschreibung des ChangeSet, z.B. „Custom SQL“, „Create View“ oder „Add Column“
COMMENTS Kommentar des Entwicklers zu dem ChangeSet
TAG optionale Markierung, z.B. für ein bestimmtes Release; kann im Rollback verwendet werden
LIQUIBASE verwendete Liquibase-Version

Im Normalfall wird ein ChangeSet nur einmal ausgeführt. Es kann jedoch definiert werden, dass ein bestimmtes ChangeSet bei jeder Ausführung von Liquibase neu gestartet wird (z.B. für die Durchführung von Grants zu neuen Datenbank-Objekten) oder dass es bei einer inhaltlichen Änderung erneut auszuführen ist (sinnvoll z.B. bei Views oder Stored Procedures).

Wie sehen Liquibase-Skripte aus?

Liquibase-ChangeLogs lassen sich in verschiedenen Formaten definieren. Neben dem meist verwendeten XML-Format werden YAML, JSON und SQL unterstützt. Ein Liquibase-Skript besteht üblicherweise aus folgenden Bestandteilen:

  • ChangeLog: der gesamte Inhalt des Liquibase-Skriptes
  • ChangeSet: ein Satz logisch zusammenhängender Statements. ChangeSets werden nach der Ausführung in die Tabelle DATABASECHANGELOG eingetragen (Kombinierter Primärschlüssel aus ID, AUTHOR und FILENAME). Ein Change Sets kann seinerseits aus mehreren Changes bestehen.
  • Precondition: Vorbedingung, die entweder für den ChangeLog oder ein ChangeSet gilt.

Hier ein einfaches Beispiel:

Das Beispiel verwendet die Liquibase-spezifische Syntax für die Anlage einer neuen Spalte in einer Tabelle. Bei der Verwendung dieser Syntax kann Liquibase in vielen Fällen bei Bedarf automatisch das passende Rollback-Statement generieren (in diesem Falle also ein DROP COLUMN), um die Änderung wieder rückgängig zu machen.

Liquibase bietet eine Vielzahl von Tags für die Durchführung von Datenbankänderungen. Die Dokumentation auf www.liquibase.org ist gut verständlich und führt zahlreiche Syntaxbeispiele auf. Nachfolgend sei ein aus nur einem ChangeSet bestehendes Beispiel gezeigt, in dem Oracle-spezifischer Code ausgeführt wird. Das Beispiel enthält zudem die Verwendung einer Precondition.

Die Precondition prüft in diesem sehr einfachen Beispiel, ob es einen bestimmten Datensatz schon gibt. Ist dies der Fall, wird der ChangeSet nicht ausgeführt, aber als erfolgreich gelaufen markiert (onFail = „MARK_RAN“). Es wäre natürlich ebenso leicht möglich gewesen, das Skript kontrolliert abzubrechen (onFail = „HALT“).

Bei der Verwendung von SQL-Tags (<sql>) reicht Liquibase die enthaltenen SQL-Statements an die Datenbank durch und kann daher automatisch kein Rollback der Änderung generieren. Da die Verfügbarkeit eines Rollbacks dringend zu empfehlen ist, muss ein solches bei der Verwendung von SQL-Tags, wie in dem obigen Beispiel, explizit angegeben werden.

Nur zur Klarstellung: Das Rollback (ob nun automatisch generiert oder manuell programmiert) ist nicht Bestandteil einer Fehlerbehandlung, und der Abbruch eines Skriptes bewirkt nicht das Ausführen des Rollback-Teils. Ein Rollback dient dem nachträglichen Zurückrollen aller Änderungen eines ChangeSets, um das Datenbankschema wieder auf einen älteren Stand zurückzusetzen.

Wie werden Liquibase-Skripte aufgerufen?

Liquibase-Skripte lassen sich beliebig kaskadierend aufrufen. Wird Liquibase über die Kommandozeile aufgerufen, muss ein ChangeLogFile angegeben werden (z.B. update.xml). Es handelt sich hierbei um einen gewöhnlichen ChangeLog, nur dass dieses meist keine ChangeSets enthält, sondern den Aufruf weiterer ChangeLogFiles.

Hier ein einfaches Beispiel für den Aufruf weiterer Liquibase-Skripte aus dem zentralen ChangeLogFile:

Man kann sich die Arbeit erleichtern, indem man eine Datei namens liquibase.properties (keine XML-Datei) anlegt, die alle für die JDBC-Connection notwendigen Attribute enthält. Auch hierzu ein Beispiel:

Im einfachsten Fall kann Liquibase nun über die Kommandozeile wie folgt aufgerufen werden:

Es wird genau ein ChangeLogFile angegeben, das seinerseits kaskadierend beliebig weitere ChangeLogs aufruft. Liquibase überprüft nun anhand der Einträge in der Tabelle DATABASECHANGELOG, welche Änderungen aus den ggf. zahlreichen ChangeLogs noch anstehen und führt nur diese aus.

Über die Kommandozeile lassen sich viele verschiedene Kommandos absetzen. In folgender Tabelle sind die wichtigsten Kommandos aufgeführt:

Kommando Beschreibung
update Führt eine Aktualisierung des Datenbankschemas durch.
updateSQL Schreibt die SQL-Statements zur Aktualisierung des Datenbankschemas nach STDOUT. Diese SQL-Statements werden nicht direkt ausgeführt, sollten aber in eine Datei umgeleitet und später angewandt werden.
updateTestingRollback Führt eine Aktualisierung des Datenbankschemas durch, rollt diese Änderungen zurück, um sie dann wieder erneut auszuführen. Gut geeignet für den Test von Update und Rollback im Rahmen der Entwicklung.
rollback <tag> Führt ein Rollback aller Änderungen durch, die neuer sind als das ChangeSet mit einem bestimmten Tag (siehe Attribut Tag in Tabelle DATABASE­CHANGE­LOG)
rollbackToDate <date/time> Führt ein Rollback aller nach einem bestimmten Zeitpunkt durchgeführten Änderungen durch.
rollbackCount <x> Führt ein Rollback der letzten x ChangeSets durch.
clearCheckSums Entfernt die Checksummen aus dem Feld MD5SUM in Tabelle DATABASE­CHANGE­LOG.
diff \[diff parameters\] Erzeugt einen Report über die Differenzen zwischen zwei Datenbankschemen.
diffChangeLog \[diff parameters\] Erzeugt ein ChangeLogFile, dessen Ausführung die Differenzen zwischen zwei Datenbankschemen ausgleicht.
generateChangeLog Generierung eines initialen ChangeLogs aus einem bereits mit Datenbankobjekten gefüllten Datenbankschema.

Siehe hierzu www.liquibase.org/documentation/command_line.

Verwendung von Variablen

Interessant und in der Praxis meist auch notwendig ist die Verwendung von Variablen, die umgebungsspezifisch gesetzt werden. Solche Variablen werden meist in einer zentralen Datei definiert (z.B. einem ChangeLog-File namens my_variables.xml). Die Definition von Variablen sieht in einer solchen Datei etwa wie folgt aus:

Einmal (möglichst in einer zentralen Datei) bekannt gegeben, werden solche Variablen nach dem Aufruf dieser Datei in allen weiteren Liquibase-Skripten mit der Schreibweise ${variablenname} referenziert.

Dieses Feature ist besonders wichtig im Rahmen einer Continuous Delivery, da diese Variablen über Konfigurations-Tools wie z.B. Puppet oder Chef je nach Umgebung automatisch unterschiedlich gesetzt werden können, ohne dass hier weiteres manuelles Eingreifen notwendig ist.

Was kann Liquibase sonst noch?

Liquibase kann noch mehr. Für die Generierung eines initialen ChangeLogs aus einem bereits mit Datenbankobjekten gefüllten Datenbankschema bietet Liquibase z.B. die Option „generateChangeLog“ an. Das generierte ChangeLog enthält die Definitionen eines Großteils der bestehenden Datenbankobjekte (leider werden nicht alle Objekttypen unterstützt). Interessant ist u.a. auch die Generierung von Diff-Skripten oder Diff-Reports, die den Unterschied zwischen zwei Datenbankschemen beheben bzw. dokumentieren.

Fazit

Durch die eigenständige Protokollierung und Verwaltung, welche Datenbankänderungen im Rahmen einer Auslieferung bereits gelaufen sind und welche noch laufen müssen, ist Liquibase ein sehr nützliches Tool in agilen Projekten, bei denen häufige Auslieferungen stattfinden. Gerade in Entwicklungs- und Testumgebungen spielen darüber hinaus die Rollback-Möglichkeiten eine wichtige Rolle, da auf diese Weise auch der datenbankseitige Teil einer Applikation wieder in ein älteres Release zurückversetzt werden kann. Hierbei gibt es allerdings gewisse Einschränkungen (z.B. ist ein DROP COLUMN nicht einfach durch ein ADD COLUMN rückgängig zu machen). In einem Oracle-Umfeld sollte man daher zusätzlich über die Verwendung von sog. „Restore Points“ nachdenken.

Folgen
X

Folgen

E-mail : *
Kategorie: Oracle-Development | Schlagwörter: , , , , | Kommentare: 0

Beitrag kommentieren