Renewal einer Legacy-UI mit Vaadin und HTML5 postMessage

Bestehenden „Legacy“-Anwendungen lässt sich neue Frische einhauchen, indem man sie Stück für Stück in eine neue Struktur überführt. Damit lässt sich einer immer weiter sinkenden Effizienz in der Wartung begegnen. Allerdings ist das per se vor allem in der Benutzeroberfläche schwierig. Mit HTML5 postMessage steht ein neuer, pragmatischer und einfacher Weg bereit, um dennoch genau dieses Ziel Wirklichkeit werden zu lassen. Dieser Artikel zeigt an einem Beispiel, wie die schrittweise Erneuerung einer Anwendung mit Hilfe von postMessage gelingen kann.

In bestehenden Anwendungen – sprich solchen, die seit Jahren produktiv sind und punktuell im Rahmen der Wartung weiter entwickelt werden – nimmt die Effektivität der Entwicklung zunehmend ab: die Anzahl der pro implementierter Funktionalität gemachten Fehler nimmt zu[1] während gleichzeitig auch der Aufwand pro implementierter Funktionalität steigt. Zusätzlich nimmt die Stabilität tendenziell ab.

Mit diesen Faktoren steigt zunehmend die Notwendigkeit, die bestehende Struktur grundsätzlich zu überarbeiten und zu renovieren. Leider ist ein vollständiges Ersetzen dabei in den wenigsten Fällen eine Option – sowohl aus Aufwands- wie auch aus Risiko-Überlegungen heraus. Damit entsteht das Dilemma, dass – zumindest augenscheinlich – eine immer aufwändiger werdende „Flickschusterei“ am bestehenden Code keine wirkliche Alternative hat.

Entsprechend charmant sind also Überlegungen, die auf eine schrittweise Erneuerung hinaus laufen. Steve McConnel skizziert in Code Complete einen Ansatz dergestalt[2], dass eine neue Ziel-Struktur aufgebaut wird, die a.) von der alten Struktur klar abgegrenzt ist und in die b.) immer mehr Funktionen überführt werden, bis sich (fast) alles in der neuen, guten, „sauberen“ Struktur befindet. Der Ansatz ist sicherlich valide – wenn auch mit einigen Herausforderungen in der konkreten Umsetzung behaftet, ganz speziell in der Benutzeroberfläche.

Will man einen Teil der Benutzeroberfläche ersetzen, dann bedeutet dies ja (so die Änderung nicht marginal ist), dass ZWEI Oberflächen-Technologien GLEICHZEITIG auf dem Bildschirm des Nutzers präsent sind – und das noch in einer Form, die OHNE BRÜCHE auskommt. Dabei gibt es zwei Aspekte:

  • Beide Oberflächen werden gleichzeitig angezeigt. Das ist der einfache Teil: Browser-Controls oder Frames / IFrames bieten die technische Möglichkeit dafür, und das schon recht lange.
  • Beide Oberflächen agieren zusammen. Das bedeutet z.B. dass das Klicken auf eine Schaltfläche im „neuen“ Teil der Oberfläche sich auf den „alten“ Teil auswirkt oder umgekehrt. Das ist nicht mehr ganz so trivial.

Betrachtet man (was dieser Artikel macht) Web-Anwendungen im Browser, dann bietet das postMessage-Feature[3] eine interessante, neue, pragmatische und einfache Möglichkeit.

postMessage erlaubt das Senden von Nachrichten zwischen zwei Frames oder IFrames, die von VERSCHIEDENEN Servern kommen.

Damit verschmelzen zwei Web-Anwendungen in den Augen der Benutzer zu tatsächlich einer, da auch im Verhalten kein Bruch mehr zu erkennen ist.

Um das Thema deutlich zu machen, verwenden wir in diesem Artikel ein kleines Beispiel[4], das folgenermaßen aufgebaut ist:

  • Es existiert eine Legacy-Anwendung, die auf eine Adress-Tabelle zugreift. Hier ist es ein mit Bonita Open Solution[5] umgesetzter Geschäftsprozess, der sowohl die Engine wie auch die Endnutzer-Oberfläche von Bonita nutzt. Genauso gut könnte es jede andere Web-Anwendung sein, egal ob in ASP, JSP, JSF, Ruby on Rails, PHP u.v.a.m. umgesetzt.
    Speziell bei der Auswahl aus Tabellen stößt Bonita in unserem Beispiel an seine Grenzen, weshalb das der Bereich ist, der als erstes erneuert werden soll. Die folgende Abbildung zeigt die bisherige Listen-Auswahl (es ist evident, dass hier einiges verbessert werden kann):
    postmessage1
  • Um das zu erreichen wurde eine neue Anwendung in Vaadin[6] aufgesetzt – hier mit Hilfe des Vaadinator[7], um einen schnellen, effizienten Start in eine saubere Architektur zu ermöglichen. Diese Anwendung implementiert die Listen-Auswahl der Adressen auf Basis der bestehenden Tabelle – sprich den (kleinen) Teil, der in Bonita ersetzt werden soll. Bonita bindet diese Anwendung als IFrame ein; das Verhalten wird über postMessage zusammengebunden.
  • Es existiert eine Datenbank für die Adressen. Diese wird (natürlicherweise) von beiden Anwendungen verwendet. Der Datenbank wurde eine weitere Tabelle hinzugefügt, um Informationen zwischen der Legacy- und der neuen Anwendung auszutauschen. Die Datenbank ist hier in PostgreSQL[8]

Das folgende Schaubild verdeutlicht den Aufbau nochmals schematisch:
postmessage2

Der unscheinbare grüne Pfeil (=postMessage) ist dabei das entscheidende Element: durch ihn wird im Browser aus zwei Ansichten eine. Das Ergebnis sieht dann wie folgt aus (incl. schicker Features wie Quickfilter in Vaadin):
postmessage3

Dabei ist entscheidend zu verstehen, dass alles innerhalb des orangen Rechtecks in Vaadin umgesetzt ist (und auf Tomcat läuft) während alles außerhalb in Bonita umgesetzt ist und auch in der Bonita Runtime läuft.

Noch viel entscheidender ist: Klickt der Benutzer nun auf den Button „Confirm“ (siehe oranger Kreis), dann läuft der Prozess in Bonita (!) weiter.

Umsetzung 1 – Definition des Frame

Hier gibt es zwei Teile: der Zusammenbau der URL und das Auswerten in Vaadin:

Betrachtet man zunächst den Frame in Bonita, so sollte dieser dem IFrame einen Key in der Transfer-Tabelle mitgeben – beispielsweise folgendermaßen:

http://localhost:8080/Html5Postmessage/#bonitaProcess=Bestandsauskunft--1.0--1

Voraussetzung hier ist, dass mit diesem Key vorher ein Eintrag in die Transfer-Tabelle geschrieben wurde. Im Beispiel ist das der Name des aktuellen Nutzers in Bonita:

Dies geschieht via PostgreSQL-Connector für Bonita
postmessage4

und folgendem Statement:

delete from transfer where topic='kundenAuswahl' and procid='${processInstance.getProcessInstanceUUID()}';
insert into transfer (topic, procid, attribute, val) values ('kundenAuswahl', '${processInstance.getProcessInstanceUUID()}', 'userid', '${loggedUser}');

Auf der Vaadin-Seite gilt es nun, den übergebenen Key wieder auszulesen (de.akquinet.engineering.vaadin.postmessage.Html5PostmessageUI.obtainPresenterFactory, aufgerufen von der UI-Initialisierung de.akquinet.engineering.vaadin.postmessage.Html5PostmessageUI.init): hier wird über

Page.getCurrent().getLocation().getFragment()

der Hash-Wert ausgelesen. Damit kann in de.akquinet.engineering.vaadin.postmessage.ui.std.presenter.AddressListPresenterImplEx.startPresenting() der Nutzer aus der Transfer-Tabelle ermittelt und in den Kontext gepackt / an de.akquinet.engineering.vaadin.postmessage.ui.std.view.AddressListViewEx.setUserName übergeben werden.

Wichtig dabei ist, dass die Übertragung via Transfer-Table erfolgt und dem Grundsatz „Never trust the UI“ folgt.

Umsetzung 2 – Button „Confirm“

Auch hier gibt es zwei Teile: das Absenden in Vaadin und das Empfangen und weiter verarbeiten in Bonita.

Betrachtet man zunächst den Vaadin-Teil ist folgende Code-Stelle (de.akquinet.engineering.vaadin.postmessage.ui.std.view.AddressListViewImplEx.postChosenAddress) entscheidend:

@Override
public void postChosenAddress(Address address) {
	try {
		JSONObject outputJson = new JSONObject();
		JSONObject addressJson = new JSONObject();
		addressJson.put("id", address.getId());
		addressJson.put("contactFirstName", address.getVorname());
		addressJson.put("contactLastName", address.getNachname());
		outputJson.put("addressTransfer", addressJson);
		JavaScript.getCurrent().execute(
		"parent.postMessage(JSON.stringify("
			+ outputJson.toString() + "), '" + counterpart + "');");
	} catch (JSONException e) {
		throw new TechnicalException("Error during postMessage", e);
	}
}

Im Kern wird auf dem parent-Frame (sprich: dem Bonita-Frame) die Methode postMessage mit zwei Parametern aufgerufen: ein String mit den zu übergebenden Daten und der Adressangabe des Bonita-Servers.

Auf den ersten Blick ist dabei erstaunlich, dass auf dem parent-Frame überhaupt Aktionen ausgeführt werden dürfen (schließlich kommt dieser von einem anderen Server). Doch genau darin liegt der Charme und die Möglichkeit von postMessage – es ist die Ausnahme, die eine Kommunikation erlaubt.

Zu den einzelnen Parametern:

  • Der String mit den zu übergebenden Daten ist beliebig – das Format muss zwischen Sender und Empfänger entsprechend abgestimmt sein
  • Die Adresse des Bonita-Servers in der Variablen counterpart (vgl. auch unten) ist auf den ersten Blick verwirrend: Sie dient einer Absicherung der Kommunikation, so gewünscht (andernfalls ist auch ein * (Asterisk) möglich für „alle“).

Die Absicherung durch Angabe der URL des Ziel-Servers ist per se sinnvoll, aber bei weitem nicht ausreichend. Auch hier gilt der Grundsatz: never trust the UI! Alles, was vom Browser kommt, ist potenziell manipuliert und falsch (F12 oder Shift+CMD+I öffnen z.B. den Inspector, der einiges an Manipulation erlaubt). Zwar betont die Dokumentation[9] die Wichtigkeit der Angabe (und auch das Beispiel setzt sie um), allerdings gilt der Grundsatz unverändert, Daten auf dem Server vorzuhalten und auch dort zu übergeben. Damit relativiert sich der Wert dieser Angabe (und es wird deutlich, warum ein * / Asterisk, also der Verzicht auf Absicherung auf der Client-Seite, valide sein kann). Gleichzeitig erhöht sich die Bedeutung der Übergabe aller (!!!) auch nur potenziell gefährlichen Angaben via Transfer Table.

Auf der Bonita-Seite ist ein Listener zu etablieren, der auf „message“ hört – und der vom Browser immer dann ausgeführt wird, wenn ein postMessage auf diesen Frame aufgerufen wurde. Das „HTML“-Form-Element in Bonita bietet sich dafür an (direkte Eingabe des HTML-Codes ist im Reiter „Daten“ möglich). Der Code ist dann dieser:

<script language="JavaScript">
		window.addEventListener( "message",
			function (e) {
				if(json.addressTransfer && e.origin=="http://localhost:8080"){
					document.getElementById("kundennummer_in").value=json.addressTransfer.id;
					document.getElementById("vorname_in").value=json.addressTransfer.contactFirstName;
					document.getElementById("nachname_in").value=json.addressTransfer.contactLastName;
					document.getElementById("senden_btn").click();
				}
			},
		false);
</script>

Die Prüfung der origin-URL folgt dabei den gleichen Kriterien wie die Angabe in Vaadin: es gibt andere und wichtigere Absicherungs-Maßnahmen. Wir nutzen dann Hidden-Fields in Bonita, um die Daten zurück in den Prozess zu bringen und eine (per CSS ebenfalls versteckte) Schaltfläche, um den Prozess weiter auszuführen.

Das wirkt auf den ersten Blick etwas krude, folgt aber streng dem Ansatz „Never trust the UI“ (im Übrigen sind die Felder für Vorname und Nachname hier genau nicht vertrauenswürdig – und für alle kritischen Vorhaben sind sie neu per ID zu laden). Außerdem zeigt es auf, wie selbst mit schwierigen Frameworks (Stichwort: JSF) erfolgreich umgegangen werden kann. Der Klick auf die Schaltfläche setzt den Prozess fort (das Beispiel zeigt dann das Gesamt-Engagement des Kunden an).

Umsetzung 3 – Quicksearch

Über der Auswahl (in obiger Abbildung das orange Rechteck) gibt es die Möglichkeit, direkt nach häufigen Namen zu filtern. Hier wird das Prinzip genau umgekehrt angewandt: von Bonita wird ein postMessage auf den Kind-Frame (Vaadin) abgesetzt; in Vaadin existiert ein Listener (eingehangen über JavaScript.getCurrent().addFunction in de.akquinet.engineering.vaadin.postmessage.ui.std.view.AddressListViewImplEx.initTypeaheadCallback), der auf das Ereignis reagiert und eine Filterung anstößt.

Installieren / Testen des Beispiels

Das Beispiel steht in Github bereit[10] und kann von dort geklont oder als ZIP heruntergeladen werden. Voraussetzung dafür ist, dass zunächst der Vaadinator[11] in gleicher Weise beschafft und via

mvn install

im lokalen Maven-Repository installiert wurde. Anschließend kann die Vaadin-Anwendung entweder mit

mvn package

gebaut (und als WAR in Tomcat ausgeführt werden) – oder man bereitet die Anwendung mit

mvn eclipse:eclipse

für Eclipse vor und wählt „Run on Server“ zur Ausführung.

In beiden Fällen muss die Datenbank (steht als Skript unter sql/create-tables.sql bereit) eingerichtet und als jdbc/vaadin in Tomcat eingerichtet worden sein.

Die Datei bonita/Bestandsauskunft–1.0.bar enthält den Prozess für Bonita (hier wurde Version 5.8 verwendet). Dieser muss also in Bonita importiert werden – anschließend gilt es, den Prozess via „Ausführen“
postmessage5
in Bonita zu starten.

Fazit

Mit postMessage steht eine interessante Möglichkeit bereit, um eine (Web-)Anwendung realistisch Schritt für Schritt auf neue Beine zu stellen. Diese neuen Beine können Vaadin oder eine andere, aktuelle, Technologie sein – in jedem Fall eröffnet sich die Möglichkeit, an der dringlichsten Stelle einen ersten und vor allem kleinen Schritt zu unternehmen.

In diesem Sinn: Viel Erfolg damit!

Lassen Sie uns Ihre Meinung / Ihre Anregungen wissen – wir freuen uns auf Ihr Feedback im Kasten unten…

[1] Siehe dazu z.B. http://swreflections.blogspot.de/2011/08/bugs-and-numbers-how-many-bugs-do-you.html

[2] Steve McConnell, Code Complete (2. Ausgabe), Microsoft Press 2005, Kapitel 24 (Refactoring)

[3] Für technische Doku siehe z.B. https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage

[4] Ist auch verfügbar auf GitHub: https://github.com/akquinet/html5-postmessage

[5] http://www.bonitasoft.org

[6] Vaadin bietet sich als effiziente(re) (und optisch attraktive(re)) Alternative zu JSF im JEE-Stack ohnehin an – siehe z.B. http://www.sigs-datacom.de/fachzeitschriften/javaspektrum/archiv/artikelansicht.html?tx_mwjournals_pi1%5Bpointer%5D=0&tx_mwjournals_pi1%5Bmode%5D=1&tx_mwjournals_pi1%5BshowUid%5D=7663 und https://blog-de.akquinet.de/2013/09/20/impulsabend-web-anwendungen-mit-vaadin/

[7] https://github.com/akquinet/vaadinator

[8] http://www.postgresql.org/

[9] z.B.  von Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage

[10] https://github.com/akquinet/html5-postmessage

[11] https://github.com/akquinet/vaadinator

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s