XML-Webdienste sind nicht tot
Vor etwa einem Jahrzehnt waren SOAP und WSDL (XML Web Services) der letzte Schrei in der Unternehmenssoftware. Angetrieben von allen großen Anbietern, darunter IBM, Microsoft, Oracle und damals auch Sun Microsystems, sollte alles WSDL-Endpunkte bereitstellen und die Welt würde dadurch besser werden. Das hat sich nicht bewahrheitet: Das WS-*-Kartenhaus brach unter seinem eigenen Gewicht zusammen, XML wurde in der Gegenreaktion zu einem Schimpfwort und REST+JSON setzte sich als The One True Way™ der Darstellung von Webservices durch (nicht ohne eigene Probleme).
Als Data Engineer besteht ein großer Teil meiner Aufgabe darin, den Weg für Data Science zu ebnen. Das bedeutet, dass ich Daten einbringen muss. In manchen Fällen muss ich alte XML-Webdienste abfragen. Wenn es sich dabei um einfache WSDL mit einem guten XML-Schema für die Nutzdaten handelt und alle zusätzlichen Standards der WS-*-Ära vermieden werden, macht mir das gar nichts aus.
In Java ist die Standardmethode für den Zugriff auf XML-Webdienste (und deren Bereitstellung) JAX-WS.
Reaktive Programmierung
Functional Reactive Programming (FRP) ist eine relativ neue Entwicklung in der Art und Weise, wie wir Java-Backend-Dienste schreiben. Es würde den Rahmen dieses Artikels sprengen, wenn wir uns ausführlich damit befassen würden, was es ist und was es bedeutet. Dan Lew hat in seinem Blog eine gute Einführung in FRP geschrieben. Das FRP-Modell führt zu Programmcode, der seine Absichten sehr klar formuliert:
connectToOrderStream()
.flatMap(o -> addCustomerInformation(o.getCustomerId()))
.flatMap(o -> addShippingInformation(o.getShippingAddress()))
.doOnNext(this::sendOrderToFulfillmentCenter)
.doOnNext(this::sendOrderConfirmationEmail);
In traditionellem, prozeduralem Java-Code wären die fünf Hauptoperationen dieses Beispiels weit voneinander entfernt und von Unmengen von Protokollierungs-, Transformations- und Fehlerbehandlungscode umgeben. Dieser Code ist im reaktiven Modell immer noch vorhanden, aber er wurde in die Hilfsmethoden ausgelagert, um die oben gezeigte saubere Komposition zu erhalten. Die Komposition selbst kümmert sich um einige der Fehlerbehandlungspfade.
FRP lässt sich gut mit nicht-blockierender IO (auch bekannt als asynchrone IO) kombinieren, um Dienste zu erstellen, die nicht nur ihre Absicht klar zum Ausdruck bringen, sondern auch eine gute Leistung bieten, insbesondere was die Hardwareauslastung betrifft. Non-blocking IO ist keine neue Idee, aber es waren Nginx und Node.js, die sie im Java-Land wirklich bekannt gemacht haben, und es hat eine Weile gedauert, bis die Anwendungs-Frameworks aufgeholt haben.
Wie in Java üblich, gibt es mehr als eine Implementierung von FRP. Die ursprüngliche Implementierung auf der JVM war
Mein Kunde hat sich für Spring Framework entschieden, daher verwende ich für den Rest dieses Artikels Reactor. Die drei Frameworks sind sich ziemlich ähnlich, und das Codebeispiel sollte sich leicht übersetzen lassen.
JAX-WS und nicht-blockierende IO
Ich kann wsimport die WSDL-Datei und haben in wenigen Minuten einen funktionierenden Webdienst-Client. Blockierende IO (synchron) war der Standard in Java, als JAX-WS eingeführt wurde, und wsimport erzeugt nur einen blockierenden Client. Wir benötigen einen nicht-blockierenden Client.
JAX-WS erzeugt einen Nicht-Block-Client, wenn Sie eine Bindungsanpassung vornehmen:
<?xml version="1.0"?>
<jaxws:bindings
wsdlLocation="https://api.myservice.example.com/?wsdl"
xmlns_jaxws="http://java.sun.com/xml/ns/jaxws">
<!-- https://stackoverflow.com/a/43560882/49489 -->
<jaxws:enableAsyncMapping>true</jaxws:enableAsyncMapping>
</jaxws:bindings>
Wenn Sie das oben gezeigte Snippet als async_mapping.xml speichern, können Sie es wie folgt an wsimport im Parameter -b übergeben:
wsimport
-b async_mapping.xml
"https://api.myservice.example.com/?wsdl"
Der generierte Code enthält nun eine blockierende und zwei nicht blockierende Überladungen für jede WSDL-Operation. Der generierte Code folgt dieser Struktur:
public interface ExampleSoapPortType {
Output operation (Input request);
Response<Output> operationAsync (Input request);
Future<?> operationAsync (Input request, AsyncHandler<Output> asyncHandler);
}
Jede WSDL-Operation wird in drei Methoden umgewandelt:
- Die erste der drei Varianten ist die ursprüngliche, blockierende Implementierung.
- Die zweite Möglichkeit,
Response<type>zu verwenden, ist für unsere Bedürfnisse ungeeignet. Das liegt daran, dass die KlasseResponse<type>die Klassejava.util.concurrent.Future. Future ist zwar tatsächlich asynchron, aber eine blockierende API: Wenn Sie den Wertget()eingeben, muss der aufrufende Thread auf die Antwort warten. - Die dritte Variante, die den
AsyncHandlerCallback verwendet, ist sowohl asynchron als auch nicht-blockierend. Dies ist derjenige, den wir brauchen. Wir können seinen Rückgabewert ignorieren und uns ausschließlich aufverlassen. Dieser Callback-Handler wird auf einem JAX-WS-Worker-Thread aufgerufen, wenn die Antwort eingetroffen ist.
Überbrückung von nicht blockierenden JAX-WS und Project Reactor
Das letzte Teil des Puzzles besteht darin, ein AsyncHandler zu erstellen, das in das Reactor-Programmiermodell passt. Dazu verwenden Sie Mono.create() (oder Flux.create()) und einen Konsumenten von MonoSink (oder FluxSink), wie im Abschnitt Producer Create in den Reactor-Dokumenten erläutert.
Ein reaktiver JAX-WS Client-Aufruf sieht daher wie folgt aus:
class Demo {
public Mono<Output> operation(Input input) {
return Mono.create(sink ->
portType.operation(input, outputFuture -> {
try {
sink.success(outputFuture.get());
} catch (Exception e) {
sink.error(e);
}
})
);
}
}
Wenn wir eine Utility-Klasse für die Boilerplate verwenden, können wir das so vereinfachen, dass es wie folgt aussieht:
import static com.xebia.jaxws.reactive.example.ReactorAsyncHandler.into;
class Demo {
public Mono<Output> operation(Input input) {
return Mono.create(sink -> portType.operation(input, into(sink)));
}
}
Das Dienstprogramm ReactorAsyncHandler
Hier ist die Gesamtheit dieser Utility-Klasse:
package com.xebia.jaxws.reactive.example;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.ws.AsyncHandler;
import reactor.core.publisher.MonoSink;
/**
* Bridge JAX-WS Async API to Reactor.
*
* <h2>Usage:</h2>
*
* <pre>
* Mono.create(sink -> endpoint.method(argument, ReactorAsyncHandler.into(sink));
* </pre>
*/
public class ReactorAsyncHandler {
public static <T> AsyncHandler<T> into(MonoSink<T> sink) {
return res -> {
try {
sink.success(res.get(1, TimeUnit.MILLISECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
sink.error(e);
}
};
}
}
Die Flux API kann auf dieselbe Weise unterstützt werden.
Zusammenfassung
XML-Webdienste gibt es immer noch, auch wenn sie nicht mehr so populär sind wie in der Vergangenheit. JAX-WS ist das Standardwerkzeug, um sie in Java abzufragen. JAX-WS ist ein Vorläufer von Reactor, der Implementierung des funktionalen reaktiven Programmiermodells von Spring Framework. Die Lücke zwischen JAX-WS und Reactor kann mit dem asynchronen Modus von JAX-WS und einem einfachen Adapter überbrückt werden, der in diesem Artikel vorgestellt wird.
Verfasst von
Barend Garvelink
Unsere Ideen
Weitere Blogs
Contact



