Herausforderungen beim Anwenden von CI bei der Datenbankentwicklung
Full-Deployment vs. Delta-Deployment
Während beim Releasen einer Softwareapplikation der gesamte Code überschrieben werden kann (Full-Deployment), muss bei der Arbeit mit Datenbanksystemen beachtet werden, dass bei dem erneuten Deployen von Objekten die darin persistierten Daten in der Regel verloren gehen. Auch in der klassischen Softwareentwicklung werden Datenbanksysteme genutzt, doch zumeist lediglich als Backend einer Software- oder Webapplikation. Das Datenbanksystem wird dabei für kleinere Transaktionen optimiert, und Änderungen im Datenmodell kommen selten vor und sind meist rein additiv. Weiterhin arbeitet für gewöhnlich eine geringere Anzahl an Entwicklern auf dem Datenbanksystem. Die meisten Änderungen können mit einfachen Skripten oder DB-Diffs erstellt werden, und nur in Sonderfällen muss ein besonderes Augenmerk auf die Daten gelegt werden. In DWS hingegen entfällt ein Großteil der Arbeit auf die Datenbank, und strukturelle Änderungen, Datenmigrationen und -archivierungen oder Updates gehören zur Tagesordnung. Die Änderungen können daher nicht via DB-Diff vereinfacht erstellt und deployt werden. Für ein Release muss ein spezifisches Transitions-/Upgradeskript bereitgestellt werden, das neben den strukturellen Änderungen (DDL) auch den Datenänderungen (DML) Rechnung trägt. Dabei ist es essentiell, dass keine Daten verloren gehen oder verfälscht werden. Zusätzlich stellt die große Menge an Daten (im Vergleich zu transaktionalen Systemen) eine Herausforderung dar. Zur Reduktion der dadurch erhöhten Komplexität haben sich verschiedene Tools, wie Liquibase oder Flyway, etabliert. Eine Übersicht über mögliche Tools gibt es hier.
Ausschließlich Upgradeskripte zu erstellen, kann jedoch zu Problemen führen. Immer die gesamten Strukturänderungen und Konfigurationsdateien aller Upgradeskripte sequenziell auszuführen, um den aktuellen Entwicklungsstand auf einer leeren Umgebung aufzuspielen, führt schnell zu Engpässen, wodurch gegen SP 7 verstoßen wird. Rein aus dem Code ist weiterhin schwer ersichtlich, welche Objekte in welcher Form im aktuellen Entwicklungsstand vorliegen, da sie in verschiedenen Upgradeskripten verteilt liegen. Dies sind nur ein paar Gründe dafür, neben den Upgradeskripten einen Snapshot des aktuellen Entwicklungsstandes zu pflegen bzw. wenn möglich zu generieren. Aber Vorsicht: Werden zwei Codeartefakte gepflegt, muss sichergestellt werden (z. B. mithilfe von automatischen DB-Diffs), dass diese nicht auseinanderdriften.
Abhängigkeiten innerhalb des Builds
Innerhalb der Datenbank existieren Abhängigkeiten zwischen Objekten, die sich nicht immer vereinfacht auflösen lassen. So kann eine View auf eine Funktion zugreifen, aber auch umgekehrt. Besonders wenn aus den zuvor angesprochenen Gründen mit Snapshots gearbeitet wird und der letzte Entwicklungsstand auf einer leeren Umgebung ausgerollt werden soll, kann es zu Herausforderungen kommen. In der klassischen Softwareentwicklung wäre das Standardvorgehen an dieser Stelle, das System und seine Loader zu analysieren, was sich im Ergebnis ähnlich zu dem Deployment von Objektkategorien verhält. In DWS reicht es wegen des oben beschriebenen Sachverhalts jedoch unter Umständen nicht aus, einfach aufeinander aufbauende Objektkategorien zu deployen (erst alle Tabellen, dann alle Views usw.). Die wohl einfachste, aber weniger elegante Lösung wäre es, mit den Abhängigkeiten zu leben und diese mit Coding-Standards und Konventionen weitestgehend in den Griff zu bekommen. Eine elegantere, aber unter Umständen aufwändige Lösung wäre es, die Abhängigkeiten vor dem Deployment zu analysieren und einen optimalen Ablaufpfad zu erzeugen.
Heterogene Produktionsumgebung
Mithilfe von CI können viele Probleme frühzeitig erkannt und behoben werden, indem die einzelnen Schritte bis in die Produktivumgebung automatisiert und getestet werden. In der Softwareentwicklung spiegelt die CI-Umgebung für gewöhnlich die Produktion ziemlich genau wider. Aus folgenden Gründen ist dies bei der Datenbankentwicklung aber nur eingeschränkt praktikabel:
- Die notwendige Speicherkapazität für eine volle Kopie der Produktion kann gigantisch sein.
- Das Klonen (oder Einspielen des Backups) der Produktion kann viel Zeit kosten.
- Aufgrund der DSGVO können die Daten zumeist nicht 1:1 für Testzwecke genutzt werden.
- Aus Kostengründen können Hardware und Lizenzen nicht immer mehrfach bereitgestellt werden.
Der Drahtseilakt liegt an dieser Stelle darin, trotz der beschriebenen Einschränkungen eine möglichst realitätsnahe Testumgebung (vgl. SP 8) und gleichzeitig ein schnelles Build zu gewährleisten (vgl. SP 7). Besonders bei umfangreichen Produktionsumgebungen in DWS kann den beschriebenen Herausforderungen nur mit Kompromissen begegnet werden. So kann eine speziell für produktionsnahe Tests ausgelegte Umgebung in regelmäßigen Abständen mit einem älteren, anonymisierten Datenstand bespielt und von allen Entwicklern gemeinschaftlich genutzt werden (Pre-Production). Entwickler müssen sich dabei jedoch sehr genau abstimmen, sodass die Ressourcen optimal genutzt und Konflikte vermieden werden können. Durch die längere Dauer bis zur Aktualisierung der Umgebung sollten die Entwickler die Umgebung „sauber“ halten und gewissenhaft mit den Daten umgehen.
Automatisierte Tests
Im Gegensatz zur Anwendungsentwicklung, bei der einzelne Units ziemlich isoliert getestet und Schnittstellen vereinfacht simuliert werden können, gibt es bei DWS verschiedene Einschränkungen. Da für aussagekräftige Tests tatsächlich Daten mithilfe von Bewirtschaftungsprozessen in der Datenbank kopiert werden, lassen sich einzelne Tests nicht ohne weiteres parallelisieren. Sonst könnte ein beliebiger Test durch den parallelen Aufruf die Quelldaten eines anderen Tests überschreiben/manipulieren, bevor dieser abgeschlossen war. Neben Parallelität bei den Tests innerhalb eines Builds können aus denselben Gründen auch nicht mehrere Builds mit Tests gleichzeitig gestartet werden, wenn sie gegen dieselbe Umgebung deployen. Des Weiteren bedeutet die sequenzielle Ausführung einzelner Tests auch eine verlängerte Laufzeit. Die Laufzeit kann zwar dadurch beschleunigt werden, dass Ablaufskripte für die optimale Reihenfolge bei der Testausführung aus den Abhängigkeiten generiert werden (zuerst Test1, dann Test2 und Test3 parallel, dann Test4 usw.). Jedoch kann das Build dabei nur zu einem gewissen Maß beschleunigt werden, und weiterhin ist eine vollständige Abdeckung nur bedingt möglich, da nicht alle Abhängigkeiten ohne weiteres automatisiert herauszulesen sind. Aus diesen Gründen können alle Tests nicht ohne weiteres bei jedem Build ausgeführt werden. Eine aufwändigere Lösungsmöglichkeit wäre es nun, nur diejenigen Tests auszuführen, zu denen sich Objekte auch tatsächlich geändert haben. Alleine schon durch dynamisches SQL wäre jedoch eine vollständige Abdeckung dabei nur schwer zu realisieren, sodass auch hier wieder eine Annäherung durch das Eingehen von Kompromissen angezielt wird. Für die automatisierten Tests könnten beispielsweise sogenannte „Nightly Builds“ konfiguriert werden, die zu festgesetzten Zeiten laufen. Weiterhin könnten die Tests auch separiert werden, sodass beispielsweise leichtgewichtige Unittests bei jedem Build mitlaufen und umfangreichere und zeitintensive Tests in „Nightly Builds“ ausgelagert werden.