Projektumstellung Java von 8 auf 11 – Oder: Die Weld/t im Wandel
Wir möchten in unserem Projekt die neuen Features und Vorteile einer höheren Java-Version nutzen. Im ersten Schritt wollen wir hierzu von Java 8 auf 11 migrieren. Aber welch Hindernisse und Unerwartetes kommen auf einen zu, wenn man diese Umstellung macht?
Umstellung der Java-Version und weiter geht's?
"Klar, kein Problem. In meinem Microservice eben umgestellt, fünf Dependencies angepasst, Haken hinter :-)"
Ja, die Welt kann so einfach sein, wenn es nicht dann doch den Monolithen geben würde. Der Dependency-Tree erstreckt sich über mehrere Bildschirmseiten und ein Dependency-Update (mvn versions:display-dependency-updates
) zeigt uns auf, dass viele Libraries veraltet sind.
Wir wollen auch sicher und aktuell bleiben. Denn der IT-Sicherheitsbeauftragte sieht es nicht gerne, wenn wir die ISO-Vorgaben nicht entsprechend umsetzen. "Also los geht's, drauf mit den Versionen." Aber das ist nicht alles. Es gibt uralte Bibliotheken, zu denen es keine aktuelle Version gibt. Diese müssen identifiziert und Ersatz für sie gefunden werden.
Es existieren auch Bibliotheken, die extra angepasst worden sind, damit sie mit unserem System umgehen können. "Library aktualisieren und „Sonderlocken“ wieder rein, wenn es nicht per Standard geht…"
Wird dann zusätzlich Weld/CDI mit hochgezogen, ackert man sich durch die Namespaceumstellung von javax auf jarkata durch.
Nachdem alles nach und nach umgestellt ist, freut man sich. "Hurra, ES KOMPILIERT!"
"Dieses war der erste Streich, doch der zweite folgt sogleich."
Es wird ernst mit GUI und Tests
Man startet die Tests und bekommt den ersten Dämpfer in Form von Fehlermeldungen. Diese kann man aber skippen und sich später darum kümmern. Es wird die GUI gestartet und nach wenigen Millisekunden kommen Meldungen, wie:
- Klasse X oder Y kann nicht geladen werden, weil ...
- Classpath-Probleme
- Welche der Managed Beans benötigt welchen Qualifier, damit nicht zwei geladen werden?
AmbiguousResolutionException: Cannot resolve an ambiguous dependency between: - Managed Bean [class com.arjuna.ats.jta.cdi.NarayanaTransactionManager] with qualifiers [@Any @Default], - Producer Method [TransactionManager] with qualifiers [@Any @Default] declared as [[BackedAnnotatedMethod] @Produces public de.ordix.run.JTAProducer.getTransactionManager()]
Die ersten Fehlermeldungen klärten sich schnell anhand der Meldungstexte, sowie kurzer Recherche. Die Umstellung von CDI + Weld benötigt an einigen Stellen Fingerspitzengefühl. Z. B. gab es durch die neue Klasse NaravanaTransactionManager
Konflikte mit unseren Producer-Methoden für den TransactionManager-Bean
. Diese wurden mittels @Priority
und @Alternative
gelöst, um zu bestimmen, ob und in welcher Reihenfolge die Bean mit Weld geladen wird.
Hat man das geschafft, erstrahlt die Anwendung in altem Glanz und das Durchklicken der Oberfläche funktioniert einwandfrei.
Zurück zu den übersprungenen Tests:
"Wir wollten aufräumen, also JUnit4 raus, stattdessen JUnit5 rein. Nach ein paar Anpassungen an den 'wenigen' Test-Frameworks (Powermock, Mockito, JUnit-Params), laufen die Tests :-)"
Bis wir zu den Integrationstests kommen, schon wieder CDI/Weld, diesmal im Zusammenspiel mit Deltaspike (bietet eine Reihe von portablen CDI-Erweiterungen). Wir konnten Deltaspike für den Testaufbau nicht mehr nutzen und setzen daher auf Weld JUnit. Die Dendency-Injection möchte auch hier nicht. Eine Build-Pipeline nach der anderen scheitert. Ab Test zehn oder zwölf geht nichts mehr. Lokal nachgetestet, läuft ... Durch Markieren der Tests mit den neuen Weld-Annotationen, bestimmen wir nun, wann und wie oft sich Weld neu aufbauen muss, damit es mit der DI klappt. Ebenfalls wurde dadurch das Problem gelöst, dass der Datenbestand nicht zu häufig neu geladen wird und die Tests die alte Geschwindigkeit erreicht haben.
"Dieses war der zweite Streich, doch der dritte folgt sogleich."
Pipelines und Testsysteme für alle Branches
"Geschafft!", denkt man und bereitet die Testsysteme für die Systemtests vor. Develop-, Release- und Feature-Branches sollen mit Java 8 laufen. Der Branch „feature-Java11“ dementsprechend mit Java 11. "In den nächtlichen Tests voreinstellen und los!" Durch Änderungen an den Branches gibt es auch passende Systemtest-Branches dazu. Aber nicht alle Jobs sind einstellbar, sodass es dann (beim Nachtesten) vorkommen kann, dass der Systemtest-Java8 gegen den Systemtest-Java11 unserer Applikation läuft und andersherum.
Wenn dann noch ein weiterer Branch mit größeren Änderungen kommt, z. B. ein Elasticsearch-Update, haben wir einen kleinen Haufen Durcheinander. Hier sind gute Absprachen im Team gefordert, welche zum Erfolg führen.
"Dieses war der dritte Streich, doch der vierte folgt sogleich."
Es geht in Richtung Produktion
Mit Vorfreude wird schon die Party vorbereitet, endlich auf Java 11 zu feiern.
Eben noch nachschauen, ob man am Release-Termin noch Urlaub bekommt, falls doch was durchgerutscht ist. Und dann ist es so weit, der große Tag des Ausrollens.
Gespannt wartet man auf Errors im Log oder Fehlertickets, aber nein, nichts kommt. Also fast nichts ...
Durch die Modularisierung in Jigsaw hat sich das Einbinden von einigen Dependencies wie Apache Groovy geändert. War die groovy-all Bibliothek unter 2.x noch eine JAR, ist sie nun ein BOM und bestimmt die einzelnen Versionen der JARs. Leider ist hier nicht aufgefallen, dass diese mit dem Scope-Test vordefiniert waren. Im Test wurden die Bibliotheken noch angezeigt, aber im Deployment fehlten die JARs. Kurzerhand werden diese ins Lib-Verzeichnis gelegt und alle sind glücklich.
"Und die Moral von der Geschicht, Lifecycle-Maßnahmen macht man oder eben nicht."
Fazit und Ergebnis
Nachdem man die veralteten Pipelines, Testsystemeinstellungen und Branches entfernt hat, kann man sagen, dass es sich gelohnt hat.
Das nachfolgende Entwickeln fühlt sich nicht nur gut an, es macht wieder mehr Spaß. Die Entwickler können nicht nur die neuen Features von Java nutzen, sondern auch die der neusten Bibliotheken wie z. B. JUnit 5. Viel Umständliches kann nun schnell und sauber programmiert werden.
Uns liegen (Stand heute) zwar keine objektiven Messergebnisse vor, aber subjektiv ist das System durch die Weiterentwicklung des Compilers und der Garbage Collection schneller geworden.
Was muss oder sollte man beachten? Lifecycle-Maßnahmen sollten als kontinuierlicher Teil des Software-Entwicklungsprozesses durchgeführt werden. Dependencies sollten auf einem aktuellen Stand gehalten werden, damit nicht wieder alles auf einmal kommt. Denn die reine Umstellung von Java 8 auf Java 11 hätte weniger Zeit in Anspruch genommen, als eine komplette LC-Maßnahme.
Zum Schluss ist noch zu sagen, dass wir uns schon auf die Umstellung auf die Java LTS 17 und 21 freuen, um die weiteren Features der Sprache zu nutzen. Aktuelle Hindernisse sind noch Bibliotheken von Drittanbietern, aber der Schritt auf Java 17 wird jetzt viel leichter sein.
Seminarempfehlungen
SEMINARE IM ENTWICKLUNGSUMFELD
Zur ÜbersichtJAVA PROGRAMMIERUNG AUFBAU P-JAVA-03
Zum SeminarSenior Consultant bei ORDIX
Bei Updates im Blog, informieren wir per E-Mail.
Kommentare