Migration Engineering mit OpenRewrite – Teil 1

Migrationen automatisiert ausführen

Migrationen automatisiert ausführen

Migration Engineering mit OpenRewrite – Teil 1

Migrationen automatisiert ausführen


Große Freude im Team – endlich darf die Framework-Version angepasst und endlich dürfen die ganzen neuen Features genutzt werden! Doch wo muss überall etwas getan werden? Mit OpenRewrite gelingt auch die Migration der großen Monolithen. Aber wo anfangen? Hier, mit dem ersten Teil dieser Serie.

Egal, ob Spring Boot, Quarkus oder ein anderes Application-Framework, viele Organisationen schrecken vor einem Major-Update lange zurück. Damit werden hohe Kosten für eine spätere Migration, steigende Gefahr von Sicherheitslücken und verpasste Gelegenheiten durch langsame Entwicklung akzeptiert oder ignoriert. Wie beim Deployment von Anwendungen kann Automatisierung helfen, diese Gefahren zu adressieren und eine effiziente Migration durchzuführen. Eines der leistungsstärksten Werkzeuge in diesem Umfeld ist OpenRewrite. Dieser Artikel beschäftigt sich mit dem Einsatz von OpenRewrite als Werkzeug bei Migrationen in allen Größenordnungen. In weiteren Artikeln wird es um die Implementierung eigener Migrationen, die Anwendung in Organisationen und die Kombination mit AI-Werkzeugen gehen.

Warum Migrationen automatisieren?

Migrationen sind eine Reihe von Codeanpassungen in einer vorhandenen Codebasis, um ein definiertes Ziel zu erreichen. Diese Ziele können divers sein, zum Beispiel:

  • Migration von Log4J zu SLF4J

  • Verwendung von Multiline Strings

  • Spring Boot 3.4 Upgrade und vieles mehr.

Viele dieser Migrationen werden immer noch manuell durch mehr oder weniger erfahrene Entwickler:innen durchgeführt. Häufig wird versucht, mit Suchen und Ersetzen zu arbeiten oder die Möglichkeiten der IDE zu beanspruchen. Denn allen Migrationen ist unter anderem gemein:

  • Anpassungen verteilen sich über das ganze Projekt.

  • Sie bestehen aus kleinen Schritten, wiederholt angewandt.

  • Unterschiedliche Projekte besitzen das gleiche Vorgehen.

Wie bei Deployments oder Builds qualifizieren diese Eigenschaften Migrationen zur Automatisierung und Wiederverwendung. Und wie bei DevOps reichen hier auch schon kleinste Wiederholungen, um eine Automatisierung zu rechtfertigen, hierzu ein kleines Rechenbeispiel:

Mit Version 3.4 hat Spring Boot sein Verhalten bei der Bean Validation in @ConfigurationProperties angepasst und folgt nun der Bean Validation Specification [1]. Um dasselbe Verhalten wie in vorherigen Versionen zu erhalten, müssen eingebettete Properties mit @Valid annotiert werden. Das Hinzufügen einer Annotation ist eine Sache von wenigen Sekunden, wird aber proportional umfangreicher mit der Anzahl an eingebetteten Properties in einer Datei. Weitere Klassen mit @ConfigurationProperties sind zwar schnell gefunden, aber müssen wieder komplett betrachtet werden. Der gesamte Aufwand wächst schnell, exponentiell und ist fehleranfällig. Mit etwas Übung ist eine solche Migration mit OpenRewrite in unter einer Stunde entwickelt und mit Tests abgesichert. In derselben Zeit schafft es ein/e Entwickler:in vielleicht, die Konfiguration eines mittleren Services zu migrieren. Die automatisierte Migration kann hingegen ohne großen weiteren Aufwand auf alle vorhandenen Services angewandt werden. Dieses sehr einfache Rechenbeispiel zeigt eindrucksvoll, wie schnell sich der Einsatz von automatisierten Migrationen lohnt. Oft schon nach der ersten Anwendung. Auch ohne große Einarbeitung in die Schnittstellen von OpenRewrite ist es möglich, schnell Wert für das eigene Projekt zu schaffen. Hierfür bieten sich die vorhandenen Migrationen an.

Die passenden Migrationen finden

Die Firma Moderne Inc. kümmert sich um die Weiterentwicklung und die Community von OpenRewrite. In dieser Zusammenarbeit sind viele grundlegende Bausteine entstanden, die zu wertstiftenden Migrationen kombiniert werden. Darunter Migrationen von Major-Versionen für Spring Boot, Quarkus, Hibernate, Maven und Gradle, aber auch kleine Helfer wie das Anwenden von Best Practices oder das Beheben der gängigsten Issues von statischer Codeanalyse.

Alle anwendbaren Rezepte sind im „Recipe Catalog“ [2] in der OpenRewrite-Dokumentation zu finden. Um das passende Rezept für den eigenen Anwendungsfall zu finden, kann die Suche oben links (Tastenkürzel: Strg/CMD+K) verwendet werden. Diese Suche indiziert nicht nur die Namen, sondern auch Teile der Dokumentation einer Migration, um besser zugeschnittene Ergebnisse zu ermöglichen. Noch bessere Ergebnisse liefert die Suche in der SaaS Moderne [3], diese liefert auch passende Ergebnisse bei ungenauen Anfragen. Alternativ sind die Migrationen auch ihren Themengebieten entsprechend getaggt und anhand dieser Tags gruppiert. Diese Gruppen ermöglichen eine explorative Suche, die besonders bei den ersten Anwendungen hilfreich sind.

jax_boegershausen_open_1_1

Abb. 1: Screenshot von einer Dokumentationsseite einer Migration

Unser Team könnte so auf die Migration von JUnit Asserts zu AssertJ aufmerksam werden. Die zugehörige Dokumentationsseite [4] ist automatisch erzeugt und für alle Migrationen gleich aufgebaut, siehe auch Abbildung 1. Unter der Überschrift ist der Fully Qualified Name angegeben, dieser ist eindeutig und wird für die weitere Verwendung benötigt. Weiterhin sind die Tags, ein Link zu den Ressourcen sowie die Lizenzinformationen gegeben. Bei den Lizenzen gibt es hauptsächlich drei verschiedene Arten:

  • Apache License 2.0, das Core-Framework, ist Apache-lizenziert, damit Framework-Autoren Migrationen für ihre Kunden anbieten können. Einige tun dies, wie Micronaut und Quarkus. In den Fällen, in denen die Framework-Autoren solche Migrationen nicht bereitstellen, gibt es einen Marktplatz für Migrationen von Drittanbietern, darunter auch die von Moderne.

  • Moderne Source Available License sind Rezepte, die frei verfügbar sind, aber nicht als Teil eines anderen Service bereitgestellt werden dürfen. In der Vergangenheit haben große Unternehmen OpenRewrite ausgenutzt, um ihre kommerziellen AI-Migrationslösungen zuverlässiger zu gestalten. Um Produkte wie Amazon Q [5], GitHub Copilot [6] und IBM Watson [7] zu einer Kooperation zu bewegen, veröffentlicht Moderne seine Rezepte nun unter der MSAL. So ist es Projekten weiterhin möglich, die Rezepte in vollem Umfang auf sich selbst anzuwenden, jedoch verboten, sie kommerziell weiter zu verwerten.

  • Moderne Proprietary sind Rezepte, die nur mit einer Lizenz von Moderne verwendet werden dürfen. Es handelt sich hier um besonders wertschöpfende Rezepte, die mit großem Investment durch Moderne in Verbindung stehen. Auf Open-Source-Projekte sind auch diese Rezepte anwendbar.

Die unterschiedlichen Lizenzen dienen dazu, Dritten zu untersagen, die Rezepte in ihren kommerziellen Services zu verwenden und weiter zu vermarkten, ohne dem Projekt etwas zurückzugeben. Eine genaue und aktuelle Aufstellung findet sich in der Moderne-Dokumentation [8]. Zum Zeitpunkt der Veröffentlichung suchen Moderne Inc. und die OpenRewrite-Community noch nach einer endgültigen Lösung. Ziel ist es weiterhin, Open-Source-Projekten Zugriff auf die Modernisierungsfähigkeiten von OpenRewrite und die Moderne-Produkte zu ermöglichen.

Nach den rechtlichen Anmerkungen führt die Dokumentation die durchzuführenden Migrationsschritte auf. Jeder Schritt ist ebenfalls eine Migration mit einer gleich aufgebauten Dokumentationsseite. Der zweite Teil dieser Serie behandelt, wie solche Migrationen für die eigenen Bedürfnisse erstellt und über YAML-Dateien konfiguriert werden können. In der Dokumentation folgt eine Aufzählung der Möglichkeiten, wie diese Migration angewendet werden kann. Migrationen können Daten zur späteren Analyse bereitstellen, die DataTable. Gängige Informationen sind solche zu Laufzeiten, geänderten Dateien oder Analyseinformationen zur Verwendung von Konstrukten. Mit diesen Informationen werden wir uns im dritten Teil dieser Artikelserie noch genauer beschäftigen. Nun soll die Migration angewandt werden.

Migrationen auf Projekte anwenden

Nach der Entscheidung für eine Migration kann der oben erwähnte Absatz Usage verwendet werden, um die Anwendung eines Rezeptes zu studieren. Grundsätzlich gibt es drei Möglichkeiten, eine Migration zur Ausführung zu bringen. 

  • Maven, über das CLI oder das Rewrite-Maven-Plug-in

  • Gradle, Rewrite-Plug-in oder ein Init-Script

  • Moderne CLI, das Stand-alone-Power-User-Tool

Über das Maven CLI kann eine Liste von OpenRewrite-Rezepten angegeben werden, die mit dem Rewrite Maven Plug-in ausgeführt werden. Dieser Modus eignet sich in erster Linie für die Verwendung von Einmalmigrationen wie Framework-Updates, dasselbe gilt für das Gradle Init Script [10]. Es empfiehlt sich bei dieser Art der Anwendung, die Beispiele aus der Dokumentation zu kopieren. Für unsere JUnit-Asserts-zu-AssertJ-Migration ist der Maven-CLI-Aufruf in Listing 1 gegeben. Es wird das Rewrite Maven Plug-in Goal run aufgerufen und mit rewrite.activeRecipes die Migration JUnitToAssertj angegeben. Da dieses nicht in OpenRewrite integriert ist, müssen die Artefaktkoordinaten für rewrite-testing-Frameworks unter rewrite.recipeArtifactCoordinates angegeben werden.

Listing 1

mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
 -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE \
 -Drewrite.activeRecipes=org.openrewrite.java.testing.assertj.JUnitToAssertj

Im Fall der JUnit-Asserts-zu-AssertJ-Migration ergibt es Sinn, das Rezept gelegentlich erneut auszuführen. Denn durch Unachtsamkeit könnte ein/e Entwickler:in neue JUnit Asserts schaffen, die erneut migriert werden müssen. Hierzu bietet es sich an, das Rezept als optionales Plug-in im Build einzusetzen. Dazu wird wie in Listing 2 das rewrite-mave-plugin eingebunden, das Rezept JUnitToAssertJ aktiviert und das Migrationsartefakt als Dependency hinzugefügt. Durch ein einfaches mvn rewrite:run kommt das Plug-in zur Ausführung, führt die konfigurierten Migrationen aus und wendet die notwendigen Änderungen auf den Code an. Bei jedem Durchlauf der Rewrite-Plug-ins meldet das Plug-in, welche Migration in welcher Datei eine Änderung vorgenommen hat. Zusätzlich erstellt es eine grobe Schätzung, wie viel Aufwand eine manuelle Bearbeitung bedeutet hätte. Von hier aus muss nur noch committet, gepusht und gereviewt werden.

Listing 2

<plugin>
  <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>6.0.0</version>
    <configuration>
      <activeRecipes>
        <recipe>org.openrewrite.java.testing.assertj.JUnitToAssertj</recipe>
      </activeRecipes>
    </configuration>
    <dependencies>
      <dependency>
      <groupId>org.openrewrite.recipe</groupId>
      <artifactId>rewrite-testing-frameworks</artifactId>
      <version>3.0.0</version>
    </dependency>
  </dependencies>
</plugin>

Als letzte Option bleibt noch das Moderne CLI als Powertool basierend auf OpenRewrite. Das Moderne CLI ist kostenlos einsetzbar für Projekte in öffentlichen Repositories, für Closed-Source-Projekte ist eine Lizenzierung notwendig. Im Gegensatz zu den Rewrite-Plug-ins kann das Moderne CLI auf mehrere Projekte gleichzeitig und parallel angewendet werden. Weiterhin kann das Moderne CLI die erzeugten Metadaten persistieren und weiterverwenden. Dadurch werden nicht bei jedem Durchlauf die kompletten Metadaten erzeugt und die Laufzeit verringert sich drastisch.

Das Moderne CLI wird als native Applikation installiert, für alle gängigen Betriebssysteme existieren Executables und über Maven Central wird zusätzlich eine reine JAR-Variante bereitgestellt. Nach der Installation ist das CLI im Terminal verfügbar. Vor der Ausführung einer Migration müssen die Metadaten erzeugt werden. Hierzu wird das Kommando mod build. genutzt, dieses sucht im aktuellen Verzeichnis nach Projekten und erzeugt die Metadaten. Zusätzlich muss mit mod sync die Liste der verfügbaren Migrationen heruntergeladen werden. Um ein Rezept auszuführen, wird mit dem mod run.-Kommando und dem Parameter recipe die auszuführende Migration gestartet. Nach der Migration könnten die Änderungen angewandt oder studiert werden. Grundsätzlich wird der Prozess verständlich durch das CLI geleitet. Hierzu meldet es nach jedem Durchlauf, welche nächsten Schritte sinnvoll wären. Eine Schritt-für-Schritt-Anleitung findet sich in der Dokumentation [9].

Mit diesen drei Möglichkeiten, Migrationen anzuwenden, gibt es für jeden Geschmack und fast jedes Projekt-Setup den passenden Weg, Migrationen anzuwenden.

Und was nun?

In diesem Beitrag wurde das Auffinden und Ausführen von OpenRewrite-Migrationen mit Maven, Gradle sowie dem Moderne CLI betrachtet. Der Umfang reicht nur für einen ersten Überblick, die gute Dokumentation rund um OpenRewrite ist nach diesem Beitrag eine Quelle. Bei Fragen bietet die Community im OpenRewrite-Slack oder auf Konferenzen Unterstützung an. Im nächsten Beitrag dieser Serie wird betrachtet, wie Migrationen für die eigenen Bedürfnisse angepasst werden können und warum in der Dokumentation immer von Rezepten gesprochen wird.

Merlin Bögershausen

Merlin ist leidenschaftlicher Java-Entwickler und als Softwareengineer bei der adesso SE tätig. In Vorträgen und Workshops auf Konferenzen oder bei Usergroups teilt er gerne sein Wissen aus über zehn Jahren professioneller Java-Entwicklung.