Flüssiger Übergang
Ein Versicherungskonzern migriert einen Teil seiner unternehmenskritischen Software schrittweise von .Net zu Java. Um zwischen beiden Plattformen eine optimale Kommunikation zu gewährleisten und stets releasefähig zu sein, hat das Projektteam Wege abseits von SOAP und Webservices beschritten.
- Dogan Dridi
- Manfred Hein
Typische Software-Migrationsszenarien basieren meist auf ganzheitlichen Ansätzen. Hierbei wird die gesamte Software von der einen auf die andere Plattform migriert und danach als Gesamtsystem produktiv eingesetzt. Komplizierter wird es, wenn eine Software so umfangreich und komplex ist, dass die Migration nicht in einem Zug erfolgen kann, da eine Weiterentwicklung während dieser Zeit nicht möglich wäre.
Vor diesem Problem stand die Basler Securitas Versicherungs-AG, die ein Tarifierungssystem für ihre Außendienstmitarbeiter entwickelt hat und permanent in der Lage sein muss, neue Funktionen zu integrieren. Der Versicherungskonzern betreibt zudem eine Reihe Host-basierter Applikationen, die die Weiterverarbeitung von Daten aus diesem System übernimmt. Hierbei gibt es einige algorithmische Überschneidungen, da beispielsweise Preisberechnungen sowohl auf der Host- als auch auf der Außendienstplattform implementiert sind.
Eine Quelle für alle Orte
Um das Single-Source-Prinzip zu gewährleisten und etwaige Berechnungsdifferenzen auszuschließen, entschied das Unternehmen, die Entwicklung der Außendienstapplikation schrittweise von .Net nach Java umzustellen, da Java-Programme im Gegensatz zu .Net-Implementierungen relativ einfach auch auf der Host-Plattform einsetzbar sind. Außerdem wollte man die Anzahl der unterschiedlichen Programmiersprachen im Unternehmen reduzieren und hat sich primär für Java entschieden.
Die Architektur der Applikation ist so aufgesetzt, dass eine zentrale Komponente, die als Rumpfapplikation dient, hauptsächlich Funktionen zur Oberflächengenerierung, Druckaufbereitung sowie weitere übergreifende Services enthält. Sie integriert die Tarifierungskomponenten für die einzelnen Versicherungssparten (Abbildung 1). Der Oberflächengenerator analysiert die Objekte, die die Spartensoftware für ihre Berechnungen benutzt beziehungsweise zurückliefert, und stellt sie strukturiert dar.
Im Interesse einer nahtlosen Migration entschied man sich, zunächst nach und nach die einzelnen Sparten zu portieren und im Anschluss die Rumpfapplikation schrittweise in Java zu implementieren. Um dies zu bewerkstelligen musste eine Möglichkeit gefunden werden, eine einzelne Spartensoftware nach Java zu portieren und dabei das bisherige .Net-Interface der Rumpfapplikation beizubehalten. Im Ergebnis erhält man nach jedem Schritt eine voll funktionsfähige und installierbare Applikation bei der jeweils ein Stück .Net- durch Java-Code ersetzt ist.
Lose Kopplung war keine Option
Eine typische Lösung für die Interoperabilität zwischen .Net und Java ist der Einsatz von Webservices. Dieser Ansatz birgt den Vorteil, dass die Kommunikation verteilt über ein Intranet oder gar das Internet erfolgen kann. Allerdings hat diese Flexibilität ihren Preis: Der Overhead, der durch die Verwendung klassischer Webservices auf Basis von HTTP entsteht, ist relativ hoch. Dies ist vor allem dadurch begründet, dass die einzelnen Nachrichten in das SOAP-Protokoll umgewandelt werden müssen und zudem der TCP/IP-Verbindungsaufbau sowie eine eventuell begrenzte Verbindungsbandbreite die Kommunikation verlangsamen.
Onlinequellen
[a] ActiveMQ
[b] MS-MessageQueue
[c] CORBA
[d] JNBridge Pro
[e] Juggernet
[f] Java Invocation API
[g] Caffeine
Eine weitere Form der Kopplung zwischen .Net und Java ist die Verwendung betriebssystemabhängiger Message Queues (siehe „Onlinequellen“ [a] und [b]), die sich von beiden Sprachen aus ansprechen lassen. Da die Datentypen innerhalb der Betriebssystemplattform naturgemäß gleich sind, hält sich der Konvertierungs-Overhead zwar in Grenzen, allerdings muss eine Serialisierung und Deserialisierung der zu übertragenden Objekte stattfinden. CORBA (Common Object Request Broker, [c]) ist ein anderer altbekannter Vertreter im Integrationsumfeld, der auf einem binären Protokoll basiert und relativ komfortable, aber auch schwergewichtige Kommunikations- und Verwaltungswerkzeuge zur Verfügung stellt.
Die drei Ansätze funktionieren gut, wenn es um das Absetzen einiger weniger und eher großer Anfragen geht, jedoch nimmt der Overhead-Anteil an der Gesamtkommunikation zu, wenn die Anfragen weniger Nutzlast enthalten und häufig zwischen den Kommunikationspartnern ausgetauscht werden müssen, wie es typischerweise bei einer interaktiven Anwendung der Fall ist. Zudem sind in allen Fällen weitere Infrastrukturkomponenten vonnöten, die Speicher und CPU zusätzlich beanspruchen und vor allem auch installiert und administriert werden müssen. Bei circa 5000 Installationen in einem heterogenen Umfeld bedeutet das einen hohen Aufwand.
Es gibt eine Reihe kommerzieller Produkte zur .Net/Java-Integration (siehe [1]), die keine IP-Protokolle verwenden. Hierzu gehören unter anderem JNBridge Pro [d] und Juggernet [e]. Beide verfolgen unterschiedliche Ansätze zur Erzeugung einer .Net/Java-Bridge, die zur Laufzeit eine Schnittstelle zwischen Java und .Net zur Verfügung stellt. Man kann sowohl eine direkte Kopplung über gemeinsam benutzten Speicher als auch eine lose Kopplung per TCP/IP generieren. Gemeinsam ist beiden Produkten, dass die Lizenzkosten nicht pro Entwicklungslizenz, sondern jeweils pro Laufzeit-Installation entstehen, was sich bei einer Anzahl von über 5000 Installationen im vorliegenden Fall schnell auf weit über 100 000 Euro summiert.
Die Java Invocation API als Klebstoff
Da die durch die Verwendung eines Produkts anfallenden hohen Kosten eine Portierung infrage stellten, musste eine andere Lösung gefunden werden. Der Schlüssel liegt darin, dass lediglich Aufrufe von .Net nach Java erfolgen und nicht zusätzlich der umgekehrte Weg implementiert werden muss.
Die hierfür von Sun vorgesehene Technik der Java Invocation API [f] stellt einen Mechanismus zur Integration einer Java VM in eine native Applikation zur Verfügung. Sie ist quasi das Gegenstück zum häufig verwendeten Java Native Interface (JNI), das es ermöglicht, nativen Code von Java aus aufzurufen. Im Wesentlichen basiert das Prinzip der Nutzung von Java aus .Net heraus darauf, einen laufenden Java-Prozess über die Java Invocation API anzusprechen, etwa von C oder C++ aus. Dieser Zugriff erfolgt über eine speziell hierfür zur Verfügung gestellte Programmierschnittstelle in C. Jede Distribution der virtuellen Java-Maschine verfügt über eine für die jeweilige Systemplattform (OS/390, Windows, Linux et cetera) erstellte Bibliothek.
Was für die „Fernsteuerung“ eines Java-Programms aus .Net noch fehlt, ist der strukturierte Zugriff auf die Java Invocation API (Wrapper-Schicht) sowie eine Spiegelung der in Java vorhandenen Klassen samt Aufruf der Wrapper-Schicht und der Datentypkonvertierung von Java nach .Net beziehungsweise umgekehrt. Eine strukturierte Zugriffsschicht lässt sich relativ einfach erzeugen, da die Struktur der Java Invocation API recht flach gehalten ist. Klassen, Methoden und Attribute kann man namentlich referenzieren und über an deren Modifizierer angepasste Funktionen aufrufen. Das Erzeugen .Net-äquivalenter Zugriffsklassen, die ihre Funktion innerhalb der Java VM ausüben, erfordert einen Generator, der den Java-Code analysiert und in .Net entsprechende Zugriffsmechanismen auf die Wrapper-Schicht bereitstellt. Abbildung 2 zeigt das Gesamtschema.
Caffeine erzeugt die Wrapper-Schicht
Um die Wrapper-Schicht nicht vollständig händisch implementieren zu müssen, suchte das Projektteam nach einem freien Werkzeug, das den Code generiert. Dafür kam lediglich das Open-Source-Projekt-Caffeine [g] infrage. Allerdings wird das von Bruno Fernandez-Ruiz konzipierte System seit 2004 nicht mehr aktiv weiterentwickelt. Im Wesentlichen besteht Caffeine aus drei Komponenten:
- einem Interface, das die Java Invocation API steuert (jninet.dll),
- einer Schnittstelle für die Kommunikation zwischen .Net und der jninet.dll (Caffeine.jni.dll) sowie
- einem Codegenerator, der aus bestehenden Java-Quellen Steuerungscode (.Net-Proxy-Klassen) für die jninet.dll erzeugt.
Der Mechanismus ist in Abbildung 3 dargestellt.
Caffeine wurde zuletzt für Java 1.4 und die .Net-Version 1.1 optimiert. Sprachkonstrukte wie Enumeration oder Generics existierten hier noch nicht. Zudem haben die Caffeine-Entwickler nicht vorgesehen, dass Methoden- und Klassennamen Umlaute enthalten können. Nachdem das Projektteam das System um diese Funktionen erweitert hatte, konnte es Caffeine für die beschriebene Aufgabe einsetzen.
Die Software verfügt über ein kleines Kommandozeilenwerkzeug, das bestehende Jar-Dateien durchsuchen und die Signaturen der einzelnen Klassen und Methoden als XML-Datei ausgeben kann (JavaApiXmlGenerator). Diese XML-Datei dient vor allem als Eingabe für den C#-Codegenerator (CsJniNetWrapperGenerator), der die Zugriffe über die C-Zwischenschicht generiert.
Drei Schritte im Build-Management
Die Integration in typische Java-Build-Systeme wie Ant oder Maven erfolgt im Wesentlichen in drei Schritten:
- das Generieren einer XML-Darstellung des bestehenden Java-Codes sowie das
- des C#-Quellcodes für den Zugriff auf die Wrapper-Schicht und
- das Kompilieren des C#-Quellcodes in eine eigenständige DLL.
Der Auszug aus einer Ant-Build-Datei in Listing 1 zeigt dieses Vorgehen.
Listing 1: buildfileteil.xml
Die buildfileteil.xml erzeugt eine .Net-Wrapper-DLL auf Basis bestehenden Java-Codes.
<target name="dll" depends="jar" description="creates dotNet
interface-dll from Java classes">
<java classname="com.olympum.tools.JavaApiXmlGenerator"
classpathref="build.classpath"
args="${dist}/${jar} ${build}/${jar}.xml" fork="true"/>
<java classname="com.olympum.tools.CsJniNetWrapperGenerator"
classpathref="build.classpath"
args="${build}/${jar}.xml ${build.generatedsrc}" fork="true"/>
<csc optimize="false" debug="true" targetType="library" incremental="false"
destFile="${dist}/${jar}.dll"
references="${lib}/javart.dll,${lib}/Caffeine.Jni.dll">
<src dir="${build.generatedsrc}" includes="*.cs"/>
</csc>
</target>
Nachdem man mit den .Net-Proxy-Klassen eine DLL erzeugt hat, kann man sie in einen .Net-Projekt verwenden. Abbildung 4 zeigt ein typisches Beispiel in C#.
Das Projekt besteht aus einer C#-Quellcode-Datei, mit der man auf die Proxy-Klassen zugreifen kann. Zudem befinden sich die Proxy-Klassen für die generelle Java Runtime (javart.jar.dll) und den Applikationscode (in diesem Fall basis.jar.dll) als Referenzen im Projekt. Des Weiteren muss gewährleistet sein, dass sich die Java-Bibliothek, auf die der Proxy-Code aufbaut (basis.jar) sowie die allgemeine in C implementierte Wrapper-Schicht (jninet.dll) im Ausgabeverzeichnis befinden.
Die Konfiguration der Bridge erfolgt über die Applikationskonfiguration (in diesem Fall JavaDotNetTest.exe.config) unter Angabe der zu verwendenden virtuellen Java-Maschine (in Form der jvm.dll unter Windows oder jvm.so unter Unix). Zusätzlich muss der Klassenpfad für die von .Net aufzurufende Java VM gesetzt werden. Hier hinein gehört in jedem Fall die Jar-Datei, für die die .Net-Proxy-Klassen erzeugt wurden. Wie in Listing 2 illustriert, kann der Entwickler weitere Optionen für die JVM angeben.
Listing 2: JavaDotNetTest.exe.config
In JavaDotNetTest.exe.config konfiguriert der Entwickler die .Net/Java-Bridge.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="caffeine">
<section name="jni.net" type="Caffeine.Jni.JNISectionHandler,Caffeine.Jni"/>
</sectionGroup>
</configSections>
<appSettings>
</appSettings>
<caffeine>
<jni.net>
<jvm.dll value="c:\Programme\Java\jdk1.5.0_15\jre\bin\client\jvm.dll"/>
<java.class.path value="basis.jar"/>
<java.option value="-verbose:gc"/>
<java.option value="-Xmx512M"/>
</jni.net>
</caffeine>
</configuration>
Im Rahmen dieses Projekts sind keine applikationsübergreifenden Performance-Tests erfolgt. Subjektiv waren jedoch keine negativen Auswirkungen der .Net/Java-Bridge auf die Performance des Systems festzustellen.
Nichtsdestotrotz steigt die Arbeitsspeichernutzung auf dem Zielsystem um rund 150 MByte an, da es neben der .Net CLI eine JVM starten muss, um eine Applikation auszuführen. Dies kann in puncto Performance zu negativen Begleiterscheinungen führen, wenn der Speicher der Zielmaschine knapp bemessen ist und sie deshalb beginnen muss, Speicherseiten auszulagern.
Fazit
Das Beispiel zeigt, dass es auch ohne hohe Lizenzkosten und komplexe Infrastrukturkomponenten möglich ist, über die Java Invocation API Java-Komponenten nahtlos und performant in die .Net-Plattform zu integrieren. Allerdings müssen die Entwickler vor Beginn des Projekts definieren, welche Komponenten sie integrieren wollen. Zudem wird das in diesem Projekt verwendete Caffeine als Open-Source-Werkzeug seit einiger Zeit nicht mehr aktiv weiterentwickelt, sodass das Team fehlende Features zweckgebunden selbst implementieren musste.
Deutlich erhöht hat sich während der Entwicklung die Komplexität des Debugging. Im Wesentlichen ist das auf die Tatsache zurückzuführen, dass das Debug-Setup nun zwei Entwicklungsplattformen und eine Wrapper-Schicht enthält und die Entwickler alle Schichten zur Fehlersuche in Betracht ziehen müssen.
Dipl.-Wi.-Inform. (FH) Dogan Dridi
ist bei der Basler Securitas Versicherungs-AG beschäftigt und dort Leiter der Anwendungsentwicklung Leben.
Dipl.-Inform. Manfred Hein
ist geschäftsführender Gesellschafter der ESKALON Technology Consulting GmbH in Hamburg.
Literatur
[1] Markus Eisele; Grenzen überwinden; Mit .Net auch für JEE entwickeln
[2] Holger Schwichtenberg, Christian Weyer; Keine Monokultur; Portabilität und Interoperabilität von Microsofts .Net und Novells Mono
iX-TRACT
- Die Basler Securitas Versicherungs-AG hat ihr interaktives Tarifierungssystem für Außendienstmitarbeiter schrittweise von .Net nach Java migriert.
- Eine .Net/Java-Bridge stellte zu jedem Zeitpunkt sicher, dass das System voll funktionsfähig und permanent erweiterbar war.
- Der Zugriff auf die neuen Java-Klassen aus .Net heraus erfolgt über die Java Invocation API, die dafür nötige Wrapper-Schicht wurde weitgehend durch das Open-Source-Werkzeug Caffeine generiert.
(ka)