Asynchroner Aufruf eines REST Services mit Spring aus einer JavaFX Applikation

Heutzutage sind ja REST Services sehr beliebt. Normalerweise läuft dieser Aufruf synchron ab und der Thread wartet schön, bis die Antwort zurückkommt. Dies ist aber auch einfach asynchron mittels asyncRestTemplate möglich (Welches nur bis Spring 4 funktioniert und in Spring 5 von WebClient abgelöst wird, oje…) Somit bleibt der aktuelle Thread nicht blockiert, sondern kann weiterarbeiten und irgendwann trudelt dann die Antwort des REST Services ein.

Statt nach dem REST Call mit .get() auf die Antwort zu warten, gibt man ihm zwei Callbacks mit: Einen SuccessCallback und ein FailureCallback. Der SuccessCallback wird bei erfolgreichem Aufruf aktiviert und enthält das Ergebnis des Services. Der FailureCallback wird aufgerufen, wenn beim Aufruf des REST Services oder im Service selber eine Exception auftritt. Das Objekt, dass wir dabei für die Callbacks verwenden, ist ein ListenableFuture vom Package „org.springframework.util.concurrent“.

Macht man das ganze in einer JavaFX Applikation, muss man noch etwas bestimmtes berücksichtigen. Ich will jetzt hier aber nichts spoilern.

Aufruf des Rest Services

Der Aufruf des Services ist simpel. Wir sagen, welchen Mediatype wir verwenden, bauen uns ein AsyncRestTemplate und rufen die exchange Methode auf. In diesem Beispiel wird die Antwort ein Objekt des Typs User sein. Obwohl der REST Service JSON zurückliefert, kann uns das egal sein, da Spring die Antwort automatisch nach JSON serialisiert und in das Objekt deserialisiert. Natürlich ist es dabei von enormen Vorteil, wenn die Serverseite das gleiche Objekt/die gleiche Klasse verwendet (In diesem Beispiel die Klasse User).

String url = "http://localhost:8089/meineresturl/user/11");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity request = new HttpEntity<>("params", headers);
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();

ListenableFuture> response = asyncRestTemplate.exchange(url, HttpMethod.GET, request,
User.class);

response.addCallback(successCallback, ex -> onFailure(ex));

Beim addCallback fällt auf, dass SuccessCallback und FailureCallback verschieden hinzugefügt werden. Das kann beliebig gemacht werden. In diesem Beispiel wurde der SuccessCallback in einer anderen Klasse erstellt (nämlich im JavaFX ViewModel, das Zugriff auf alle GUI Objekte hat) und hier hinzugefügt. So landet das Ergebnis am Ende praktischerweise in der anderen Klasse.

FailureCallback direkt in einer Methode

Der einfachere Weg, mit dem Ergebnis bzw. in diesem Fall einem Fehler umzugehen, ist es, mittels eines Lambdas direkt auf eine Methode zuweisen. In dieser Methode kann das Ergebnis wie gewünscht behandelt werden.

private void onFailure(Throwable ex) {
LOGGER.error("Fehler beim Aufruf des Services: ", ex);
}

Direktes Hinzufügen des SuccessCallbacks plus Wechseln in JavaFX Thread

Damit der SuccessCallback hinzugefügt werden kann, muss er irgendwo definiert werden. Wie gesagt, das kann in irgendeiner Klasse geschehen und dann der Klasse, die für den oben stehenden REST Call zuständig ist, übergeben werden.

Der Body der Response entspricht dabei dem angegebenen Objekt User und die Werte werden automatisch gesetzt. User muss dabei ein POJO mit Gettern und Settern sein, damit die Werte von Spring gesetzt werden können.

Mit .getStatusCode() kann der HTTP Statuscode abgefragt werden. Die gesamte Liste findet man auch in dieser Klasse org.springframework.http.HttpStatus.

public SuccessCallback> createDurchfahrtenSuccessCallback() {
return response -> {
HttpStatus httpStatusCode = response.getStatusCode();
if (HttpStatus.NOT_FOUND.equals(httpStatusCode)) {
LOGGER.info("HTTP Status Code 404 erhalten - Ressource nicht verfuegbar");
} else if (HttpStatus.OK.equals(httpStatusCode)) {
// Wieder in den JavaFX Hauptthread gehen
Platform.runLater(() -> {
User user = response.getBody();
speichereUser(user);
});
} else {
LOGGER.info("HTTP Status Code " + httpStatusCode + " erhalten"); }
};
}

In der Mitte des folgenden Codestücks sieht man auch den Teil, den man bei einem Aufruf aus einer JavaFX Applikation berücksichtigen muss. Die Antwort des REST Services kommt asynchron und läuft dort in irgendeinem Thread, aber nicht im JavaFX Hauptthread (der Code kann übrigens gedebuggt werden, einfach Breakpoint im Callback setzen).

Damit nun die Verarbeitung und somit der Zugriff auf die GUI Elemente klappt, muss man mittels Platform.runlater() in den JavaFX Thread wechseln, bevor man das Ergebnis verwendet.

Simulieren des REST Services in einem jUnit Test mittels WireMock

Programmiert man gleichzeitig die Client- wie auch Serverseite eines REST Services kann es durchaus vorkommen, dass der Code für den Aufruf des Services vor dem eigentlichen Service-Code fertig ist.

In diesem Fall kann man mittels des Frameworks WireMock einfach einen REST Service für Testzwecke simulieren.

Dazu erstellt man wie gewohnt einen jUnit Test und verwendet WireMock mit den gewünschten Parametern. Natürlich muss das WireMock JAR vorher noch dazugeladen werden, etwa im Maven POM File:

groupId: com.github.tomakehurst
artifactId: wiremock
version: 2.19.0
scope: test

Als erstes erstellt man eine sogeannnte WireMockRule und gibt den gewünschten Port an (dieser muss dann natürlich in der URL beim REST Service Aufruf auch drin sein).

import com.github.tomakehurst.wiremock.junit.WireMockRule;

@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
// No-args constructor defaults to port 8080

Das eigentliche Simulieren des REST Services geschieht dann tatsächlich in nur einer einzigen Zeile Code, in der man sagt, welchen HTTP Statustyp man zurückgeben will (200 = Alles OK), ob es in JSON sein soll und welchen Body man senden will.

String url = "/meineresturl/user/11");  // URL ohne Host

stubFor(get(urlEqualTo(url)).willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(userJson)));

Der Body kann ein einfacher String „Hallo Welt“ sein. Ich habe in meinem Fall ein Test User erstellt, ein paar Beispielwerte reingepflanzt und dann das ganze Objekt mittels Jackson ObjectMapper von Hand serialisiert.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

private String userToJson(User user) {
String userJson = "";
ObjectMapper mapper = new ObjectMapper();
try {
userJson = mapper.writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return user;
}

Also zuerst definiert man im jUnit Test den REST Service mit stubFor und kann dann eine Instanz des REST Service Aufrufers verwenden, um den REST Service aufzurufen. Falls es zu Beginn nicht klappt muss man sicherstellen, dass die URL, die der Service Aufrufer verwendet, wirklich 1-zu-1 mit der URL übereinstimmt, die man dem stubFor mitgibt.

Als einfacher Test kann man also zum Beispiel einen User mit Namen „Peter“ erstellen, diesen in JSON Serialisieren und dort als Body angeben. Dann rufe ich den REST Service auf, erhalte automatisch das „User“ Objekt und prüfe mit jUnit

assertEquals("Peter", user.getName());

WireMock hat noch viele weitere Möglichkeiten, um das Resultat zu verifizieren. Siehe dazu http://wiremock.org/

PS: Muss man beim REST Service Aufruf wie ganz oben beschrieben den SuccessCallback mitgeben muss man im jUnit Test natürlich auch so einen definieren, damit man das Ergebnis bearbeiten kann. Oder man verwendet beim SuccessCallback einfach auch die direkte Methode wie beim FailureCallback.

Ich musste übrigens noch einen kleinen Sleep zwischen dem Aufruf des REST Services und dem anschliessenden assertEquals einbauen, damit der jUnit Test erfolgreich war.

private void warteAufCallback() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

So das wäre alles für heute. Ich hoffe, der Beitrag erleichtert jemandem das Leben 🙂

Werbeanzeigen

Tomcat oder Websphere?

Wie viele andere Firmen mussten wir uns an einem Punkt entscheiden, ob wir unsere Infrastruktur von IBM Websphere Applikationsserver auf Apache Tomcat wechseln sollten.

Beide Produkte sind zum Ende des letzten Jahrhunderts entstanden, haben sich aber in verschiedene Richtungen entwickelt. Während Tomcat ein Leichtgewicht geblieben ist und als Open-Source Produkt verwendet werden kann, wurde aus Websphere ein grosses Produkt einer ganzen Gruppe von IBM Produkten, die miteinander zusammenarbeiten.

Websphere ist ein Applikationsserver

Was man oftmals als technischen Unterschied liest ist, dass Websphere ein Applikationserver ist, während Tomcat nur als Servlet Container fungiert.

Ein Java Applikationsserver unterstützt alle Java EE Funktionalitäten. Diese Funktionalitäten erweitern die Standard-Java-Plattform für die Durchführung von internetbasierten Businesstransaktionen. Dazu gehören etwa JTA, EBJs oder JMS.

Bis Java Version 6 musste dabei ein Java EE Applikationsserver all diese Aspekte berücksichtigen. Dies ist heute nicht mehr so, dank dem Konzept von „profiles“ in Java 6. Applikationen werden in Java Applikationsservern im EAR (Enterprise Archive) Fromat ausgeliefert, wobei ein EAR mehrere WAR Dateien beinhalten kann (Eine WAR Datei ist quasi eine Webapplikatikon).

Und Tomcat ist ein Servlet Container

Ein Servlet Container muss nur die Java Servlet und JSP Spezifikation implementieren, welche die Installation von Webapplikationen definiert. Ein Servlet Container kann also genauso eine WAR Datei laufen lassen.

Heutzutage ist es so, dass die Servlet Spezifikation soviele Aspekte von Webtechnologien abdeckt, dass viele kommerzielle Firmen Tomcat als Applikationsserver verwenden.

Enterprise Java Beans sind kein Grund mehr

Lange Zeit war einer der Vorteile von Websphere die volle Unterstützung von Enterprise Java Beans (EJB). Diese Beans stellen einen Weg zur Verfügung, wie Businesslogik in ein einzelnes Objekt gekapselt werden kann. Dies hat sich allerdings mit dem kostenlosen OpenEJB plugin geändert.

EJBs wurden etwa eingesetzt, wenn die Applikation verteilt war, also Zugriff auf Ressourcen auf anderen Maschinen benötigte. Dies ist aber in Zeiten von Webservices oder REST Services kein Pluspunkt für EBJ mehr.

Skalierbarkeit war ebenfalls ein Thema bei EJBs, inklusive Load Balancing oder fail-over Szenarien. Hierfür gibt es aber mittlerweile eine ganze Reihe von Open Source Programmen, die man einsetzen könnte.

Man sieht: Viele der genannten Vorteile wie JMS, JTA, JPA etc. sprechen heute nicht mehr für EJB, wenn man Tomcat und Websphere vergleicht.

Die Framework Landschaft ist gewachsen

Ein weiterer Unterschied ist das Auftauchen von Frameworks wie Spirng, Seam, Hibernate, Grails, Guice oder JRuby on Rails. Mit diesen Frameworks kann man heutzutage die gleichen Ergebnisse erzielen wie mit Java EE. Diese Frameworks wurden oftmals aus dem Grund entwickelt, weil Entwickler unzufrieden waren mit der EJB oder Java EE Implementation und die Weiterentwicklung zu langsam voranschritt.

Ein Anbieter, Performanz und Unterstützung

Der technische Unterschied zwischen Tomcat und Websphere ist mit voranschreitender Zeit geringer und geringer geworden. Mit den richtigen Erweiterungen und Tools steht Tomcat dem Websphere praktisch in nichts mehr nach.

Bei Websphere muss man sich im Klaren sein, dass man auf einen einzigen Anbieter setzt, und dessen Launen vollends ausgesetzt ist. Dadurch, dass Websphere Teil eines grossen Gesamtsystems ist, kann durchaus auch die Performanz darunter leiden. Auch in meiner Erfahrung war besonders die Entwicklungsarbeit mit Tomcat enorm schneller als mit Websphere.

Was man aber nicht übersehen darf: Setzt man auf Websphere, kann man mit der Unterstützung von IBM rechnen sowie auch deren administrative Tools für den Umgang des Servers verwenden. Bei Tomcat muss man sich eher auf die Internet Community verlassen. Zudem gelten vorallem die Websphere Server als sehr stabil – Wobei ich noch nie gehört habe, dass Tomcats instabil sind.

Tomcat mit vielen Vorteilen

Warum setzen bereits über sechzig Prozent der Webentwickler auf Tomcat? Meistens hört oder liest man die gleichen Gründe: Bei Tomcat kann man seine eigene IDE wählen, dass die prorietäre IDE von Websphere zu verwenden. Ein Tomcat Server startet unglaublich schnell, was natürlich der generellen Entwicklungszeit zugute kommt.

Ein Punkt, bei dem Websphere die Nase vorn hat, sind die Möglichkeiten der grafischen Analyse und Verwaltung des Servers. Die Popularität von Tomcat hat aber dazu geführt, dass etwa Produkte wie Tcat diese Lücke füllen.

Natürlich wichtig: Der Preis

Websphere verursacht astronomische Kosten während Tomcat kostenlos und Open Source ist. Das ist der Grund, den ich jeweils am häufigsten gehört habe, um eine Migration zu rechtfertigen. Wenn man zwei Produkte hat, die ähnliche Funktionen anbieten beziehungsweise ein Prjekt zum Ziel führen könnten, ist es schwer, sich für eine sehr teure Lösung zu entscheiden – teuer beim initialen Aufwand sowie jährlichen Lizenzkosten.

Bei Tomcat benötigt es gerade mal die Hardware, auf der der ganze Spass laufen kann und die Kosten für das Einrichten und den Betrieb des Servers.

Der Apple Effekt

Was dazukommt ist ein ähnliche Effekt wie bei Apple: Hat man erst einmal ein Produkt von IBM gekauft, wird man an diesen Anbieter gebunden und kauft bei weiteren Anwendungsfällen höchst wahrscheinlich auch wieder Software von demselben Anbieter – Etwa weil die verschiedenen Programme von IBM gut miteinander harmonieren.

Bei Tomcat hat man schon eine höhere Flexibilität und die Freiheit, die Tools zu nehmen, die man selber für richtig hält. Dann wiederum kann man auch argumentieren, dass die Suche und Einarbeit in neue Tools ebenfalls Aufwand erzeugt, während man bei IBM auf eine eingelebte Produktgruppe zugreifen kann und von IBM sogar noch bei der Einrichtung unterstützt wird.

Mein Fazit: Tomcat = Flexibilität

Wie ich bereits geschrieben habe machte ich sehr gute Erfahrungen bei der Entwicklung mit Tomcat im Vergleich zu Websphere – Besonders beim Punkt Schnelligkeit. Bei einem Websphere Server musste ich jeweils 1-2 Minuten warten, bis er oben war, während es bei Tomcat vielleicht 10-20 Sekunden waren.

Was mich persönlich ebenfalls beeindruckt, ist der eingebettete Tomcat in Produkten, etwa in Spring Boot. Einer Spring Boot Webapplikation kann als einfache JAR Datei erstellt werden und Spring Boot kann diese JAR Datei automatisch auf einer (magischen?) Tomcat Instanz laufen lassen – Ohne dass ich irgendetwas konfigurieren muss (ich bin ein heimlicher Spring Fanboy).

Für Tomcat spricht sicherlich auch die Flexibilität, wenn jemals ein weiteres Produkt auf den Markt kommen würde. Die Hürde, auf das neue Produkt zu wechseln, wäre viel geringer als bei der eingefleischten IBM Familie.

Was ist eine Singlepage Applikation?

Bei einer Singlepage Applikation (SPA) handelt es sich um eine Webapplikation, die aus einer einzigen Seite besteht. Der Inhalt wird jeweils, je nachdem, was gerade angezeigt werden soll, dynamisch geladen.

Im Gegensatz dazu bestanden klassische Homepage aus mehreren Seiten (Multi Page Application – MPA), die untereinander verlinkt waren. Nun ja, in Zeiten von Cascading Style Sheets ist das Design nicht mehr so mühsam, selbst bei klassischen Homepages – früher jedoch musste man natürlich in jeder Html Seite neben dem eigentlichen Inhalt auch das Design der Seite einbauen. Hatte man dann eine Änderung, durfte man alle Seiten anpassen.

Ich erinnere mich noch an ein Projekt, welches ich vor langer Zeit in PHP gemacht hatte. Dabei hatte ich nur eine einzige Datei namens index.hmtl und habe den anzuzeigenden Inhalt jeweils über einen oder mehrere Parameter mitgegeben. Dies hatte den Vorteil, dass das Design und auch alle anderen querschnittlichen Aufgaben (Cross-Cutting Concerns) wie das Überprüfen des Benutzerlogins oder das Anzeigen des Headers und Footers in einer Datei gemacht wurde.

Ein weiterer Grund für mich war, dass man eine einzige grosse Seite hatte, die man von oben nach unten durchscrollen konnte, im Gegensatz zu einer Seite bestehend aus vielen Teilen (Frames).

Bei Multi Page Applikationen war es dann auch der Fall, dass jede Änderung zu einem Request zum Server geführt hat. Dies konnte zwar mit AJAX (Asynchronous Java And XML) ein wenig optimiert werden, so dass nur noch der Teil der Seite geladen wurde, welcher auch tatsächlich geändert hat. Beim Anzeigen einer komplett neuen Seite wird aber immer der gesamte Inhalt geladen.

Wie funktioniert denn nun eine Singlepage Applikation?

Eine Singlepage Applikation ist quasi die Fortführung der Multi Page Applikation + AJAX. Der Inhalt wird auf dem Server zusammengesucht, während die gesamte Benutzeroberfläche mit Javascript zusammengesetzt wird. Das Laden der Seiten wird so zwar schneller, allerdings bedeutet dies natürlich auch mehr Code, der auf der Clientseite ausgeführt wird.

Was ist der Vorteil einer Singlepage Applikation?

Ein grosser Vorteil ist, dass der Seitenkontext nicht immer neu geladen werden muss. Bei Applikationen mit mehreren Seiten wird jeweils die gesamte clientseitige Präsentationslogik beendet und auf der nächsten Seite neu geladen – Dies entfällt bei Singlepage.

Ein weiterer Vorteil ist es, dass das Backend einfach auf eine andere Plattform wie etwa Mobile übertragen werden kann.

Was ist der Nachteil von Singlepage Applikationen?

Ein Nachteil ist, dass jede Navigation des Benutzers in einem Request an den Server resultiert. Klickt der Benutzer zum Beispiel den „Zurück“ Knopf, erwartet er grundsätzlich ein sofortiges Ergebnis, da die Seite von vorhin ja schonmal geladen wurde und sich eigentlich nichts geändert hat. Bei einer Singlepage Applikation macht er jedoch einen neuen Request zum Server, um sich den Inhalt zu laden.

Der Grund dafür ist, wie gesagt, dass bei einer Singlepage Applikation das eigentliche Browsen mittels Javascript gemacht wird. Was ist nun, wenn der Benutzer Javascript deaktiviert hat? Dann wird er die Seite nicht verwenden können. Dieser Punkt bedeutet auch immer ein Zusammenspiel zwischen dem Backend und einer Javascript Komponente.

Wie kann eine Singlepage heutzutage gemacht werden?

Eine Singlepage Webapplikation kann heute etwa mit AngularJS und dem Spring Framework gemacht werden. Dabei übernimmt das Framework Aufgaben wie das Authentifizieren des Benutzers oder die Gewährleistung der Sicherheit.