- OpenRewrite
- Software-Entwicklung
- Software-Modernisierung
Automatisierte Java-Modernisierung mit dem Transformer-Ansatz: Jakarta EE Migration mit OpenRewrite
Der Transformer-Ansatz automatisiert technische Modernisierung wie Framework- oder Sprachupgrades, indem ein eigens gebautes Tool („Transformer“) den Code umschreibt, statt dass Entwickler:innen händisch eingreifen. So lässt sich die Featureentwicklung parallel fortsetzen, während im Hintergrund z. B. auf Jakarta EE oder eine neue JBoss-EAP-Version migriert wird. Mit OpenRewrite lässt sich dieser Ansatz für Java-Projekte gut umsetzen.
Technische Modernisierung: Framework-Upgrades ohne Feature-Freeze
Ein großes Feld der Softwaremodernisierung ist die technische Modernisierung. Dabei geht es um den reinen Austausch tiefliegender Bestandteile unserer Applikationen. Wenn du technisch modernisierst, tauschst du üblicherweise Frameworks aus, führst Versionssprünge von Abhängigkeiten mit vielen API Änderungen durch oder wechselst vielleicht sogar auf eine andere Programmiersprache. Ein typisches Beispiel: das Upgrade von Jakarta EE 8 auf Jakarta EE 10 oder der Sprung von JBoss EAP 7 auf EAP 8.
Solche Vorhaben greifen häufig großflächig in die betroffene Applikation ein. Oftmals bedeutet das Änderungen im überwiegenden Anteil der Quelldateien. Auch gut designter Code bewahrt dich davor nicht.
Warum manuelle Framework-Migration teuer und fehleranfällig ist
Vielleicht hast du schon einmal technische Modernisierung betrieben? Dann ist dir bereits aufgefallen, dass das viel Handarbeit ist. Die einzelnen Codestellen zu finden und auszubessern ist mühsam, repetitiv und vor allem durch die menschliche Komponente fehleranfällig. Klarerweise kann man sich mit Find and Replace, Regex und anderen Hilfsmitteln unterstützen. Allerdings ist Code ein komplexes Dateiformat – hier wird man spätestens bei den semantischen Aspekten von Java an seine Grenzen stoßen.
Viel schwerwiegender ist aber: Wenn du den Großteil deiner Codebasis anpasst, ist die Featureentwicklung beeinträchtigt. Du hast hier grundsätzlich zwei Möglichkeiten:

Upgrade verläuft parallel zur Featureentwicklung: Hier wird die Applikation laufend weiterentwickelt, während du beispielsweise Jakarta EE 8 auf 10 umstellst. Bei diesem Vorhaben greifst du ca. jedes zweite Source-File an. Am Ende riskierst du einen gröberen Merge-Konflikt und musst die neu entwickelten Features ebenfalls migrieren.

Feature-Freeze: Die Applikation kann nicht weiterentwickelt werden, während du den Code migrierst. Zwar vermeidest du Merge-Konflikte, allerdings ist die pausierte Feature-Entwicklung teuer und vor Stakeholdern nicht immer rechtfertigbar.
Eventuell besitzt deine Organisation mehrere Applikationen, die auf derselben Plattform aufbauen? Dann wirst du die Handarbeit noch öfter wiederholen und dich öfter mit dem Team der Featureentwicklung streiten müssen.
Der Transformer-Ansatz: Code automatisiert migrieren statt manuell anpassen
Eine dritte Möglichkeit gibt es auch noch: Statt den Code direkt anzupassen, baust du einen Automatismus, der die notwendigen Änderungen durchführt: deinen Transformer. Du änderst also nichts an den Sourcen direkt, sondern verwendest sie nur als Test für eine Komponente, die Code von A nach B umformen kann. Sobald der Transformer fertiggestellt ist, kannst du ihn dann auch für weitere Applikationen, die dieselbe Umstellung vor sich haben, nutzen und ggf. anpassen.
Der größte Sinn dahinter ist aber, dass dir der Transformer erlaubt, zu modernisieren, ohne die Featureentwicklung zu stören. Während der Entwicklungsarbeit kannst du regelmäßig neue Änderungen übernehmen und gleich bei der Transformation mittesten. Anders als beim händischen Ansatz machst du beim Merge nichts kaputt, da du Neu und Alt nie vermischt.

Transformer in der Praxis: Von COBOL zu Java
In vergangenen Projekten haben unsere Applikationen viele automatisierte Transformationen durchlebt. Sehr aufwändig war unter Anderem ein großer Monolith, der auf einer proprietäre COBOL-Erweiterung aufbaute. Die Wartung dieser Erweiterung fiel auf meine Organisation zurück, nachdem das zuvor betreibende Unternehmen in Konkurs ging. Kombiniert mit der Aktualität von COBOL waren wir somit in einer aus Wartungssicht misslichen Lage.
Also schrieb man einen Transformer, der den alten COBOL-Code nach Java übersetzte. Die erwähnte Erweiterung brachte einige zusätzliche Funktionalitäten mit sich, daher mussten viele unterschiedliche Konzepte in Java Logik übersetzt werden. Es entstand eine Java-Bibliothek, die diese Funktionalitäten abdeckt. Ein größeres Programm übersetzte dann den imperativen Code in Klassen, die diese Bibliothek nutzen.
Dieses Programm, der Transformer, ist dabei die einzige Stelle, an der Änderungen durchgeführt wurden. Der eigentliche Code wurde während der gesamten Entwicklung nicht angepasst und diente nur als Testbasis für den Transformer. So konnte die Applikation weiterentwickelt werden, während parallel die gesamte Programmiersprache umgestellt wurde. Bereits damals hatte diese Applikation eine beträchtliche Größe, ein etwaiger Entwicklungsstopp hätte sich über einen langen Zeitraum auf die Releases von Features ausgewirkt.
Mit Java befinden wir uns heute immer noch auf einer zukunftsfähigen Grundlage. Die wenigsten technischen Modernisierungsprojekte erfordern das gänzliche Austauschen der Implementierungssprache. Wenn wir beispielsweise über das schlichte Austauschen oder Upgraden von Frameworks wie Spring, Quarkus oder Jakarta EE sprechen, dann eignet sich das Tool „OpenRewrite“ recht gut für die Implementierung eines geeigneten Transformers.
Was ist OpenRewrite? Automatisiertes Refactoring für Java
OpenRewrite [Link neu, extern zur offiziellen Doku] ist ein Tool für Code-Refactorings, welches von der Moderne Inc. entwickelt wird. Es ist in der Java-Welt zuhause, aber du kannst es mittlerweile auch für andere Sprachen wie JavaScript verwenden. OpenRewrite erzeugt einen Abstract Syntax Tree, also eine logische Baumdarstellung deines Codes. Über diesen Baum iteriert das Tool einmal. Sogenannte Visitors werden hier an unterschiedlichen syntaktischen Knoten (z.B. Klassendeklarationen, Methodenaufrufe, …) eingebunden, um auf diese Veränderungen auszuwirken.
Ein oder mehrere Visitors bilden in OpenRewrite ein Rezept. Ein Rezept kann beispielsweise eine Klasse umbenennen, oder eine Maven-Dependency ändern. Aufgrund der Nutzung des Visitor-Patterns kannst du dein Repertoire leicht um eigene Rezepte erweitern. Die Stärke des Tools liegt allerdings vor allem in der großen Auswahl an bestehenden Rezepten. Mehrere Rezepte kannst du nämlich wieder in ein sogenanntes „Composite Recipe“, also ein Sammelrezept, zusammenführen. Diese Composite Recipes eignen sich gut, um den Transformer-Ansatz zu implementieren.
JBoss EAP 8 Upgrade: Von Java EE zu Jakarta EE mit OpenRewrite
Wir konnten viele positive Erfahrungen mit OpenRewrite im Java-Bereich sammeln. Gerade das Upgrade von JBoss EAP-7 auf EAP-8 war ein exzellenter Anwendungsfall für einen Transformer auf Basis von OpenRewrite. JBoss ist ein Applikationsserver, der unter anderem Implementierungen für Standards in der Anwendungsentwicklung bereitstellt, vor allem den Java EE Standard. Bestimmt hast du mitbekommen, dass Java EE von Oracle zur Eclipse Foundation übergegangen ist. Dabei wurde es in Jakarta EE umbenannt. Technisch wurden alle zugehörigen Packages von javax.* nach jakarta.* verschoben. Diese Änderung wurde im Sprung von JBoss EAP-7 auf JBoss EAP-8 abgebildet.
Das bedeutet einen großen Mehraufwand, weil die Bestandteile von Java EE üblicherweise über den gesamten Code hinweg benutzt werden. Des Weiteren sind viele Abhängigkeiten inkompatibel geworden und mussten ebenfalls aktualisiert oder gar ersetzt werden. Schwierig war das vor allem in Applikationen, die seine eingebundenen Libraries nicht am aktuellen Stand halten. Wenn du ein ähnliches Projekt hinter dir hast, hast du wahrscheinlich über veraltete JSF-Bibliotheken, Codegenerierungs-Werkzeuge und ClassNotFoundExceptions geflucht. Sicher allerdings über Hibernate, welches uns im selben Versionssprung mit vielen weiteren Breaking Changes überraschen konnte.
Mit OpenRewrite konnten wir einen Transformer bauen, der ursprünglich eine spezifische Applikation von Java EE nach Jakarta EE umgewandelt hat. Durch die Strukturierung in Rezepte konnten wir viele Komponenten gleich für andere Applikationen übernehmen, die dasselbe Upgrade überstehen mussten. OpenRewrite bietet mit einem „JakartaEE10“ [Link neu, extern] Rezept schon viel gelöste Vorarbeit an, somit mussten wir beispielsweise die regulären Package-Änderungen sowieso nicht selbst umsetzen. Damit haben wir uns viel wiederholten Aufwand erspart, die Featureentwicklung nicht gestört und inhärent eine Basis für eine Dokumentation der durchgeführten Änderungen erlangt.
Transformer entwickeln: Iteratives Vorgehen in Meilensteinen
Aus bestehenden und vielleicht selbst geschriebenen Rezepten kannst du dann deine gesamte Migration als Transformer abbilden. Es lohnt sich, diesen Transformer in unterschiedlichen Meilensteinen zu entwickeln. Du kannst diesen so definieren, dass nach der Transformation die Applikation in der neuen Umgebung:
- kompiliert
- erfolgreich bei Unit-Tests ist
- deployed
- erfolgreich bei Smoke-Tests ist
Damit strukturierst du die Entwicklung deines Transformers in einem iterativen Prozess: Wahrscheinlich wirst du im Transformer zuerst mal das Framework auf die gewünschte Version heben. Transformierst du die Applikation, wird diese Änderung übernommen. Sehr sicher kompiliert dein Code dann noch nicht. Daher baust du im nächsten Schritt ein weiteres Rezept in deinen Transformer ein, das den ersten Fehler, auf den du stößt, behebt. Oft sind das bei der Kompilierung inkompatible Abhängigkeiten. Das wiederholst du, bis die gesamte Applikation kompilierfähig ist. Dann machst du das erneut, bis alle Unit-Tests erfolgreich sind, usw. So tastest du dich Meilenstein für Meilenstein an einen vollständigen Transformer heran, der am Ende die gesamte Codebasis in einem Lauf migriert. Zwischendurch kannst du natürlich jederzeit neue Änderungen der Featureentwicklung übernehmen, da du den tatsächlichen Code nicht angreifst.
Wo nutze ich einen Transformer / wo nicht?
Der Transformer-Ansatz verhält sich bei jenen Upgrades am besten, die rein technischer Natur sind. Gut geeignet sind Anforderungen, die sich oft wiederholende Änderungen inkludieren. Je mehr Fälle du mit einer einzelnen kleinen Erweiterung des Transformers abdecken kannst, desto effektiver funktioniert er. Dabei müssen die technischen Änderungen auch nicht zwangsweise durch externe Libraries oder Sprachen ausgelöst werden. Für uns hat sich der Transformer auch dort gelohnt, wo wir hauseigene Schnittstellenobjekte eines alten Binärdatenprotokolls in eine neue Web-Schnitstelle übersetzen mussten. Die Rezepte dafür waren eigens implementiert, aber in der Anwendung recht effektiv.
Je mehr Logik das Problem umfasst, desto ineffektiver wird ein Transformer. Benötigt er viele Informationen aus unterschiedlichen Stellen des Codes, ist dieser Ansatz komplex. Auch ist er nicht sinnvoll bei Vorhaben, in denen ein Transformer mit einer Vielzahl an unterschiedlichen Fällen umgehen können muss. Wenn sich viele Baustellen nicht wiederholen, sondern nur einmalig vorkommen, erzeugt der Transformer-Ansatz einen übergroßen Overhead.
Du kannst dich auch für eine Kompromisslösung entscheiden, bei der der Transformer nur die 90% an allgemeinen Fälle übernimmt und du die 10% an Spezialfällen in einem verkürzten Entwicklungsstopp im zuvor transformierten Code händisch migrierst. Somit erntest du viele Vorteile des Transformers, ohne viel Zeit in der Automatisierung der Spezialfälle zu verlieren.
Fazit: Automatisierte Migration als Werkzeug der Softwaremodernisierung
Der Transformer ist ein mächtiges Pattern, mit dem du flache, technische Migrationsprojekten wie Frameworkupgrades, oder den Umstieg auf eine andere Programmiersprache, abwickeln kannst. OpenRewrite bietet dir für ersteren Fall ein gutes, modernes Tooling für die Implementierung dieses Ansatzes. Mit einem strukturierten Entwicklungsprozess kannst du schnell einen Transformer schaffen, der vielleicht sogar mehrere Applikationen migrieren kann. Bei Upgrades logischer Natur oder solchen, die viele einmalige Änderungen beinhalten, solltest du eventuell vom Transformer absehen oder ihn nur teilweise verwenden.
Du planst ein Framework- oder Sprachupgrade in deiner Organisation und willst die Featureentwicklung dabei nicht stoppen? Wir unterstützen dich bei der technischen Modernisierung deiner Java-Applikation – von der ersten Analyse bis zum fertigen Transformer. Mehr zu unserem automatisierten Update-Ansatz findest du auch im Artikel „Automatisierte Updates mit Renovate & OpenRewrite“.
Der Autor
Geboren 2003 in Wien. Simon arbeitet bei Gepardec IT Services GmbH, wo er bereits einige Modernisierungsprojekte absolviert hat. Er konnte hier sowohl in Legacy-Projekten, als auch im Cloud-Umfeld Erfahrungen sammeln. Der Transformer-Ansatz ist hier eines der maßgeblichen Werkzeuge, die ihn seit Beginn seiner Karriere begleiten.


