Spring-Boot im Legacy-Kontext

Für die Weiterentwicklung von Legacy-Anwendungen sind oftmals zusätzliche Werkzeuge und Frameworks erforderlich. Der Artikel zeigt auf, wie sich in diesem Zusammenhang Spring-Boot sinnvoll einsetzen lässt.

 

Spring[1] [2]  ist ein nützliches Open-Source-Framework für die Java-Plattform. Das Kernziel des Projekts besteht darin, die Java-/Java-EE-Entwicklung durch die Entkopplung von Applikationskomponenten zu vereinfachen. Die zentralen Mechanismen sind dabei Dependency-Injection[3] und Aspektorientierte Programmierung[4]. Ein wesentlicher Baustein für die Implementierung dieser Konzepte ist die sogenannte Bean, ein Objekt, das von Spring instanziiert, zusammengestellt und verwaltet wird[5]. Die Eigenschaften der Beans können sowohl in XML-Dateien, als auch durch Annotationen im Code, gekennzeichnet durch @ Tags, festgelegt werden. Die erste Variante, also die Nutzung von XML-Dateien, hat den Vorteil, dass die Anwendung dadurch auch zur Laufzeit flexibel konfigurierbar ist. Sie hat aber dafür den Nachteil, dass Definitionen außerhalb des gewöhnlichen Java-Codes stehen, was zum Beispiel das Debuggen erschwert. Zudem werden die XML-Konfigurationen auch bei einer geringen Anzahl von Bean-Definitionen recht schnell komplex und unübersichtlich. Deswegen ist es i.d.R. besser, den annotationsbasierten Ansatz zu verwenden.

Spring-Boot greift den annotationsbasierten Ansatz auf und stellt weitere Werkzeuge zur Vereinfachung der Konfiguration, bzw. Anwendungsüberwachung bereit. Konzeptionell entspricht Spring-Boot dem Spring-Framework inklusive eingebettetem HTTP-Server (Tomcat, Jetty) und einer vereinfachten Bean-Konfiguration (in Form von Annotationen)[6]. Die Bean-Konfiguration via XML wird nur zwecks der Abwärtskompatibilität unterstützt. Das ist aber gerade für Legacy-Anwendungen nützlich, weil dadurch die in den XML-Dateien definierten Elemente nicht zwingendermaßen in Java-Code umgeschrieben werden müssen. Konkret besteht SpringBoot aus den folgenden Komponenten (Abb. 1):

  • Starter: Unterstützt beim Zusammenstellen von Library-Abhängigkeiten
  • AutoConfiguration: Automatische Konfiguration der Anwendung
  • Command-Line-Interface (CLI): Kommandozeile zum Laufen und Testen der Anwendung
  • Actuator: Aufrufbare Laufzeitinformationen (Metriken, Health, usw.)

Die Komponenten in einer Anordnung gemäß dem Einsatz-Zeitpunkt im Entwicklungsprozss (Abb.1)

Die Idee von Spring-Boot-Starter besteht darin, dass Spring-Boot für typische Anwendungskomponenten (z.B. web, jdbc, ldap, logging, test) die wesentlichen Java-Libraries zusammenstellt und auch die Abstimmung der Library-Versionen übernimmt[7] [8]. In der Praxis führt das zu folgender Vorgehensweise: Soll eine Spring-Boot-Anwendung erstellt werden, wird zunächst einmal die Spring-Initialize-Webseite aufgerufen[9]. Dort lässt sich eine Maven-/Gradle-Projektdatei anhand der benötigten Abhängigkeiten, wie z.B. Web, Security, JPA etc., erzeugen. Das resultierende Build-Konfigurations-Template ist lauffähig und stellt das wesentliche Gerüst für die Anwendung dar, von dem aus die konkreten Anforderungen implementiert werden können.

Der Vorteil dieses Vorgehens lässt sich anhand eines Beispiels illustrieren: Wird eine Web-Applikation mit Tomcat auf konventionelle Art und Weise, also ohne Sprint-Boot entwickelt, sind mehrere Libraries einzubinden (Abb. 2). In einem Spring-Boot-Projekt reicht es dagegen aus, die Spring-Boot-Starter–Abhängigkeit einzubinden. Das Versionsmanagement der damit verbundenen Libraries wird komplett von Spring-Boot übernommen. Damit ist auch automatisch sichergestellt, dass die Versionen der einzelnen Libraries zueinander passen. Übergreifend wird dadurch erreicht, dass auf der Komponenten-Ebene Spring-Boot-Projekte, z.B. für Webservices, stets die gleichen Voraussetzungen haben – was letztlich zur Standardisierung von Java-Projekten beiträgt.

In Spring-Boot werden mehrere Libraries zu einer einzigen Abhängigkeit zusammengefasst. (Abb.2)

AutoConfiguration hat das Ziel, die (Bean-) Konfiguration zu minimieren. Dazu analysiert die Anwendung die Library-Abhängigkeiten und konfiguriert die entsprechenden Komponenten, soweit diese nicht explizit manuell konfiguriert wurden. Findet zum Beispiel die AutoConfiguration-Komponente eine HSQLDB im Classpath, wird automatisch eine In-Memory-Datenbank erzeugt, falls keine dementsprechende Bean-Konfiguration vorhanden ist[10]. Für Spring-Boot-Starter-Web-Projekte werden automatisch Standardendpunkte generiert, die im Browser aufrufbar sind, z.B. eine Error-Page[11]. Den Einsprungspunkt für die AutoConfiguration kennzeichnet man im Code, indem an eine Klasse die @SpringBootApplication Annotation anfügt wird. Diese steht formal für folgende Spring-Annotationen[12]:

@SpringBootApplication = @Configuration + @ComponentScan + @EnableAutoConfiguration

  • @Configuration: kennzeichnet Konfigurations-Klassen für Beans
  • @ComponentScan: suche nach Konfigurationen und Beans im gleichen Package (und darunter)
  • @EnableAutoConfiguration: fügt Beans gemäß den Classpath-Abhängigkeiten automatisch hinzu

Daraus ergeben sich auch Anforderungen an die Projektstruktur: Um den größten Nutzen aus Spring-Boot zu erhalten, sollte der Einsprungspunkt möglichst auf der Root-Ebene der Paketstruktur definiert werden.

Die letzte wichtige Spring-Boot-Komponente ist der Actuator. Dieser liefert Laufzeitinformationen über die Anwendung und kann somit für das Monitoring von großem Nutzen sein. Das Prinzip ist folgendes: Im Browser oder JMX-Console wird ein eindeutiger Endpunkt aufgerufen, z. B. localhost:8080/actuator/health. Die damit zusammenhängenden Informationen werden von der Anwendung zusammengestellt und dann zurückgegeben. Daher werden beispielsweise im Browser als JSON-Objekt für health Informationen zum Health-Status angezeigt. Spring-Boot verfügt über einige Standard-Endpunkte[13], die je nach Einsatzszenario nützlich oder schädlich sein können. Bei einem öffentlichen Web-Service beispielsweise ist vermutlich nicht erwünscht, dass sich dieser über shutdown herunterfahren lässt. Aus diesem Grund ist dieser konkrete Endpunkt auch default-mäßig deaktiviert.

 

Spring-Boot im Legacy-Kontext

Kann Spring-Boot auch für Legacy-Anwendungen eingesetzt werden? Wie ersichtlich geworden ist, handelt es sich bei Spring-Boot um ein Framework, das die Anwendungsentwicklung erleichtert. Dementsprechend eignet es sich vor allem für Neuimplementierungen. Sämtliche Mechanismen können dort problemlos genutzt werden. Dabei ist zu bedenken, dass der Hauptfokus des Spring-Frameworks im Enterprise-Applications-Bereich liegt. Dementsprechend wird Spring-Boot typischerweise für die Realisierung von (Web-) Service-Komponenten verwendet. Für die Entwicklung von Desktop-Anwendungen bringt es keine nennenswerten Vorteile, wobei der entstehende Overhead, z.B. der in Spring-Boot enthaltene Tomcat, berücksichtigt werden sollte. Trotzdem ist erwähnenswert, dass Spring-Boot grundsätzlich auch gängige GUI-Frameworks wie z.B. JavaFX unterstützt[14].

Für bestehende Anwendungen kommt es vor allem auf die Migrationsstrategie an. Eine reine Umstellung auf Spring-Boot – daher ohne strukturellen Umbau – ist in den meisten Fällen nicht zu empfehlen. Hauptsächlich liegt das daran, dass der Einsatz von Spring-Boot eben einer fundamentalen Design-Entscheidung entspricht, nämlich die Festlegung einer Codestruktur bei der die Applikationskomponenten möglichst voneinander entkoppelt sind. Diese Philosophie kann in einem vorhandenen Java-Projekt ohne grundlegende Änderungen praktisch nicht umgesetzt werden. Zusätzlich gibt es grundlegende technische Herausforderungen, zum Beispiel die Frage nach der Umstellung von im Code vorhandenen Design-Patterns wie Singleton[15] auf die passende Spring-Lösung. Die Umstellung vereinfacht sich, wenn die Altanwendung bereits Spring verwendet. Wie bereits erwähnt, kann Spring-Boot auch XML-Konfigurationen (wie z.B. Bean-Definitionen) verarbeiten[16] [17]. Es gibt dafür eigene Annotationen (@Import, @ImportResource), die sich an die jeweiligen Konfigurationsklassen anheften lassen. Ein möglicher Migrations-Workflow ist in (Abb. 3) dargestellt: Zunächst sollte in der Altanwendung die Spring-Version auf die Zielversion der Spring-Boot-Anwendung aktualisiert werden. Nach einem erfolgreichen Regressionstest kann dann die Codebasis (Code und Konfigurationen) in ein neues Spring-Boot-Projekt übernommen werden. Im nächsten Schritt erfolgt die Anpassung des Source-Codes beziehungsweise der dazugehörigen Konfigurationen, wie Bean-Definitionen, Resource- und Property-Dateien. Daraufhin kann die XML-basierten Bean-Definitionen auf äquivalente annotationsbasierte Pendants umgestellt werden. Ist das erfolgreich abgeschlossen, ist noch die jeweilige Build-Konfiguration (pom.xml, build.gradle) zu überarbeiten. Dabei sollte insbesondere versucht werden, wo immer möglich die Abhängigkeiten von Third-Party-Libraries auf dementsprechende Spring-Boot-Starters umzustellen, welche die jeweiligen Libraries enthalten. Damit wird erreicht, dass Spring-Boot das Versionsmanagement für diese Bibliotheken übernimmt.

Schematische Migrationsstrategie einer alten Spring-Anwendung auf Spring-Boot. (Abb.3)

Ob sich die Spring-Boot-Migration einer bestehenden Java-/Spring-Anwendung lohnt, muss für den Einzelfall kritisch geprüft werden. Im Legacy-Kontext kommt Spring-Boot hauptsächlich für Umbau-Maßnahmen zum Einsatz. In diesem Zusammenhang ist es das ideale Werkzeug, um einen Service für Schnittstellenpartner bereitzustellen, der Änderungen ausgleichen kann. Konkret gibt es folgende Anwendungsfälle:

 

Schnittstellenänderungen, die nach außen sichtbar sind

Dieser Fall kann eintreten, wenn sich zum Beispiel die Schnittstelle ändert (Feldnamen in der übergebenden JSON-/XML-Datei) oder die übergebenen Informationen mit Daten aus einer weiteren Quelle angereichert werden sollen (Abb. 3). In diesen Situationen übernimmt der Service die Funktion eines Adapters[18], der die Daten aus der ursprünglichen Anwendung in ein anderes Format umwandelt. Der Vorteil ist dabei, dass die Anwendung selbst nicht geändert werden muss, was z B. aufgrund von fest vorgegebenen Release-Zyklen gar nicht möglich sein kann.

 

Änderungen an der Anwendung, die nach außen nicht sichtbar sein sollen

Dieser Fall entspricht im Wesentlichen der umgekehrten Situation, bzw. dem schrittweisen Umbau der monolithischen Altanwendung zu einer Microservice-Architektur (Abb. 4). Die Idee dabei: Eine bestimmte Funktionalität der Altanwendung wird durch einen (Spring-Boot-) Service ersetzt und dann die dementsprechende Komponente in der Altanwendung abgeschaltet. Dieses Verfahren wird solange wiederholt, bis schließlich nur noch die neuen Service-Komponenten übrigbleiben. Da die einzelnen fachlichen Module der Altanwendung dabei sukzessive abgewürgt werden, bezeichnet man diese Vorgehensweise auch als Strangler-Pattern[19] [20].

Ein  Spring-Boot-Service kann als Adapter eingesetzt werden. (Abb. 4)

Mit der Umstellung der Altanwendung auf eine Spring-Boot-Microservice-Architektur ist auch ein wichtiger Schritt zur Cloud-Readiness der Anwendung getan. Darauf aufbauend lässt sich mit dem Spring-Cloud-Framework die Orchestrierung der einzelnen Service-Komponenten, z.B. die Erkennung von Service-Komponenten, Load-Balancing, usw. umsetzen[21] [22].

Das Strangler-Pattern. (Abb. 5)

 

Fazit:

Spring Boot ist ein Framework, das Entwickler insbesondere bei der Implementation des Projektgerüsts unterstützt. Dementsprechend kommt es hauptsächlich bei der Neuentwicklung von Anwendungen zum Einsatz. Aber auch im Legacy-Kontext ist es ein nützliches Werkzeug, da man damit relativ einfach neue Service-Komponenten erstellen kann, die bei Umbau-Maßnahmen Schnittstellenänderungen ausgleichen.

 

Die Autoren sind bei Avision in Oberhaching bei München angestellt, einem Sepzialisten für Software Revival. Beide sind promovierte Physiker.

Dr. Gernot Pfanner ist in der Softwareentwicklung aktiv.

Dr. Rainer Brückner leitet das Test- und Quality-Management-Team.

 

https://www.avision-it.de

 

Quellen:

[1] https://spring.io/

[2] http://bit.ly/2ZUDAni

[3] http://bit.ly/2RK4MSL

[4] http://bit.ly/2Yf5Oc0

[5] http://bit.ly/2LoGkoU

[6] http://bit.ly/2xhdrm6

[7] http://bit.ly/2XeCfKZ

[8] http://bit.ly/2FDxwrA

[9] https://start.spring.io/

[10] http://bit.ly/31XwTTg

[11] http://bit.ly/2FFL5Xk

[12] https://www.journaldev.com/7989/key-components-and-internals-of-spring-boot-framework

[13] http://bit.ly/2X7zp5B

[14] http://bit.ly/2FADLMz

[15] http://bit.ly/2ISjfJE

[16] http://bit.ly/2XBA8jH

[17] http://bit.ly/2FAN1Ra

[18] http://bit.ly/2XuuvEf

[19] http://bit.ly/2ISWwgu

[20] http://bit.ly/2LqoDVY

[21] http://bit.ly/2IVUWKR

[22] http://bit.ly/2J9PtPm

Redaktion


Leave a Reply