31. Mai 2024 von Merlin Bögershausen
Automatisierte Modernisierung - Teil 1
Open-Rewrite-Grundlagen
Modernisierungen sind aus technischer Sicht sehr spannend. Aus der Sicht der Projekt- oder Produktverantwortlichen sieht es jedoch anders aus. Hier trüben Aufwand und mögliche Risiken das Bild. Mit Open Rewrite wird in diesem Beitrag ein Werkzeug für reproduzierbare, testbare und effiziente Modernisierungen vorgestellt. Durch den Einsatz von Open Rewrite werden große Modernisierungen elegant und kostengünstig durchführbar. Der Blog-Beitrag gibt einen allgemeinen Überblick über Open Rewrite. In zwei folgenden Beiträgen wird der Schwerpunkt auf die Entwicklung eigener Migrationen und den Einsatz auf organisatorischer Ebene gelegt.
Warum die Migrationen automatisieren?
Martin Fowler beschreibt in seinem Buch Refactoring viele kleine Anpassungen an bestehendem Quellcode mit dem Ziel, die Qualität zu verbessern. Er selbst beschreibt sie als „small [...] behavior preserving [...] canges, each too small to be worth doing”. Ein Beispiel für ein Refactoring nach Fowler ist das Entfernen der Annotation SuppressWarnings aus einer Methode. Durch das Entfernen einer einzigen Zeile werden Compiler-Warnungen sichtbar, ohne dass sich das Verhalten ändert. Der potentielle Nutzen für das Produkt ist gering und bleibt es auch, wenn diese Änderung auf die gesamte Codebasis angewendet wird. Der Aufwand steigt jedoch erheblich, so dass die Motivation, diese Anpassung durchzuführen, nie besonders groß sein wird.
Noch stärker ist der Effekt bei der Aktualisierung von verwendeten Frameworks wie Spring Boot. Hier beschränken sich die notwendigen Anpassungen in der Regel auf das Austauschen von Annotationen, das Umbenennen von Parametern und das Aktualisieren von Abhängigkeiten. Da sich das Verhalten des Produkts im Idealfall nicht ändert, ist der Nutzen wiederum gering. Zudem steigt durch die Vielzahl der Anpassungen an den unterschiedlichsten Stellen das Risiko von Mehraufwand. Die Abneigung gegen eine Modernisierung des Frameworks wächst damit weiter.
In einem ähnlichen Spannungsfeld bewegt sich die Erstellung und Produktivsetzung von Software-Artefakten. Hier hat sich Continuos Integration/Continuos Deployment mit automatisierten Pipelines als Lösung etabliert. Ausgangspunkt für diese Lösung war die Entwicklung von Werkzeugen zur Automatisierung der Aufgaben. Im Kontext der Softwaremodernisierung stellt Open Rewrite eine Open-Source-Automatisierungslösung für Migrationen dar, die einen ähnlichen Ansatz ermöglicht.
Open Rewrite ist ein Open-Source-Werkzeug für groß angelegte automatisierte Quellcode-Migrationen. Mit Open Rewrite können Anpassungen an bestehendem Quellcode definiert und effizient auf großen Codebasen ausgeführt werden. Durch die Automatisierung werden Anpassungen wiederholbar und können auf andere Produkte übertragen werden. Der testgetriebene Entwicklungsansatz reduziert zusätzlich das Risiko von fehlerhaften Migrationen. Die aktive Community bietet umfangreiche Unterstützung bei Problemen und sorgt für eine schnelle Weiterentwicklung. Die Firma Moderne steht hinter dem Open Source Produkt und bietet weitere Dienstleistungen für Unternehmen an. Für die meisten Anwendungsfälle sind die Open-Source-Ressourcen ausreichend.
Technologisch ist Open Rewrite ein Werkzeug aus dem Java-Ökosystem, die Anzahl der unterstützten Sprachen und Ökosysteme wächst in den letzten Jahren rasant. Das Werkzeug liest den vorhandenen Quellcode wie eine Entwicklerin ode ein Entwickler und berücksichtigt dabei neben der Syntax auch die Semantik. Auf Basis der eingelesenen Informationen werden vordefinierte Änderungen durchgeführt. Die Definition dieser Änderungen wird als Rezepte bezeichnet. Im Rahmen der Transformationen werden nur die notwendigen Änderungen am Quellcode vorgenommen und der definierte Stil eingehalten. Ganz so, wie es erfahrene Entwicklerinnen beziehungsweise Entwickler auch machen.
Wie finde ich die passenden Migrationen?
Auf der Projektseite von Open Rewrite sind unter dem Punkt Recipe Catalog alle verfügbaren Rezepte dokumentiert. Die Rezepte sind dabei thematisch, nach Programmiersprachen und Frameworks gruppiert. Zu jedem Rezept sind die möglichen Konfigurationen angegeben. Bei Rezepten, die aus mehreren Schritten bestehen, werden alle verwendeten Rezepte aufgelistet und die Möglichkeiten zur Ausführung mit Maven oder Gradle direkt angegeben. Für unser nächstes Beispiel suchen wir in der Kategorie Spring.
Beispielmigration auf Spring Boot 3.2
Um Open Rewrite in Aktion zu sehen und sich ein eigenes Bild von seinen Fähigkeiten zu machen, benötigen wir eine Codebase. In diesem Beispiel wird Spring Boot Pet Clinic verwendet. Dieses Beispielprojekt der Spring Boot Community ist vielen bekannt, wird ständig aktualisiert und deckt technologisch viele grundlegende Bausteine ab. Um einen realistischen Migrationsaufwand zu haben, wird das Projekt auf Spring Boot 2.7 zurückgesetzt (01.09.2022, dgcd, 276880ed). Ziel dieses Beispiels ist die Migration auf Spring Boot 3.2.
Die Migration von Spring Boot 2.7 auf Spring Boot 3.2 besteht aus vielen Einzelschritten. Diese Schritte werden vom Spring Boot Team regelmäßig in den Migrationsanleitungen beschrieben. Zusätzlich unterstützt das Team bei der Entwicklung der Migrationsskripte für Open Rewrite. Die so entstandenen Migrationen sind im Rezeptkatalog in der Kategorie Java/Spring/Boot3 zusammengefasst.
Für unsere Migration ist vor allem das Rezept Migrate to Spring Boot 3.2 interessant. Neben einigen Klassenwechseln fallen vor allem die Migrationen zu String Cloud, Spring Security und Virtual Threads auf. Dies lässt den tatsächlichen Umfang der Migration erahnen. Um den Aufwand für die Entwickler:innen gering zu halten, stellt das Rezept sicher, dass die Applikation zuerst auf Spring Boot 3.1 migriert wird. Diese Migrationskette kann bis zu Spring Boot 2.0 zurückverfolgt werden. Dadurch werden auch Migrationen durchgeführt, die zu einem früheren Zeitpunkt vergessen wurden. Um die Migration zu starten, wird die CLI-Variante von Maven verwendet.
$ mvn -o org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=\
org.openrewrite.recipe:rewrite-spring:RELEASE \
-Drewrite.activeRecipes=\
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2
Diese Variante erfordert die wenigsten Anpassungen am Projekt und ist besonders kompakt. Nach wenigen Minuten ist die automatische Migration abgeschlossen und 18 Dateien sind migriert. Zu diesen Dateien gehören neben den Klassen der Anwendung auch die pom.xml sowie die enthaltenen GitHub Actions. Ein erster Build des Projekts schlägt jedoch fehl.
Bei der Entwicklung von Software treten immer wieder Probleme auf, von denen auch Open Rewrite trotz hoher Testabdeckung nicht frei ist. Teilweise sind diese Probleme jedoch durch die Anwender:innen bedingt. So scheitert unter anderem der erste Build nach der Migration daran, dass die Formatierung nicht stimmt. Dank des Plugins spring-javaformat-maven lässt sich dieses Problem mit dem Goal apply schnell beheben. Alternativ hätte ein passender Style in Open Rewrite konfiguriert werden können.
Ein erneuter Build-Versuch schlägt aufgrund eines Bugs in Open Rewrite fehl. Die Dependency jakarta.xml.bind:jakarta.xml.bind-api
wird zwar verwendet, ist aber in der pom.xml nicht deklariert. Wenn die Dependency hinzugefügt wird, kann das Projekt erfolgreich gebaut und getestet werden.
Fazit
Diese beiden Herausforderungen sollten von jedem Entwicklerin beziehungsweise jedem Entwickler mit Spring Boot Erfahrung in kurzer Zeit gelöst werden können. Durch den Einsatz von Open Rewrite konnte der Aufwand für die Migration von Spring Boot 2.7 auf Spring Boot 3.2 auf weniger als eine Stunde reduziert werden. Bei einer manuellen Migration wäre der Aufwand deutlich höher gewesen. Neben den großen Java Frameworks Spring, Quarkus und Jakarta EE unterstützt Open Rewrite unter anderem auch Javascript und Node.js, Python, Terraform und viele weitere Technologien aus modernen und Legacy Softwaresystemen. Durch den großen Fundus an existierenden Rezepten können viele große Migrationen mit Open Rewrite effizient durchgeführt werden. Sollten Anpassungen oder neue Rezepte notwendig sein, können diese testgetrieben und maßgeschneidert entwickelt werden.
Diese weiterführenden Themen werden in zwei weiteren Blog-Beiträgen näher behandelt.