Webservices mit Java EE 6: JAX-WS und RESTful Services

Seite 2: SOAP vs. REST

Inhaltsverzeichnis

Da für SOAP-Webservices schon mit Java EE 5 die wesentlichsten Neuerungen eingeführt wurden, geht der Artikel nur kurz darauf ein. Mit einfachen Java-Klassen und -Interfaces sowie Metadaten (Annotationen) übernimmt JAX-WS das Bereitstellen und Anbinden klassischer Webservices und die Java Architecture for XML Binding (JAXB) das Mapping zwischen XML-Elementen und Java-Klassen beziehungsweise deren Attributen. Soll ein Entwickler zum Beispiel einen Warenkorb in Form eines SOAP-Webservices bereitstellen, muss er ein JAX-WS-Endpoint und beliebig viele JAXB-Datenobjekte formulieren.

Das folgende Listing zeigt den JAX-WS-Warenkorb, der durch die Statuslosigkeit der Webservice-Aufrufe in den Business-Methoden einen Parameter zur Identifikation des adressierten Warenkorbs aufweist.

@WebService
public class ShoppingCartEndpoint implements ShoppingCart {

@WebMethod
public void addItem(@WebParam(name="cartId") String identifier,
@WebParam(name="item") Item item,
@WebParam(name="quantity") int quantity) {...}

}

Die im Listing gezeigte @WebMethod-Annotation ist optional, da alle öffentlichen Methoden eines JAX-WS-Webservices automatisch als Webservice veröffentlicht werden. Gleiches gilt für die @WebParam-Annotation, die optional Metadaten für die formale Beschreibung des Webservices zur Verfügung stellt.

Damit JAX-WS das Datenobjekt Item von XML nach Java deserialisieren beziehungsweise von Java nach XML serialisieren kann, muss der Entwickler die Klasse mit JAXB-Annotationen anreichern. Das folgende Listing zeigt die mit JAXB-Annotation erweiterte Klasse Item, die den Umgang mit dem Objekt (@XmlAccessorType) und XML-Transporteigenschaften (@XmlElement) festlegt.

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)

public class Item {
@XmlElement(name="item-id")
private int itemId;
private String description;
...
}

Mit JAXB 2.2 lassen sich über Annotationen alle wesentlichen XML-Schema-Definitionen ausdrücken.

Die scheinbare Leichtigkeit der Definition eines JAX-WS-Webservices täuscht über die Tatsache hinweg, dass zur Definition eines interoperablen SOAP-Webservices viel Fingerspitzengefühl und Know-how für sprachspezifische Eigenheiten notwendig ist. Wer kann zum Beispiel mit Sicherheit sagen, das sich eine gewählte Methodensignatur eines Java-Webservices ohne Probleme von einer anderen Plattform ansprechen lässt? Und wie formuliert man in Java, das ein Integer-Aufrufparameter nur einen bestimmten Wertebereich annehmen kann?

Mit umfassenden Erfahrungen in der Definition solcher Webservices ermöglichen JAX-WS und JAXB die Formulierung interoperabler Webservices. Doch dieser sogenannte Code-first-Ansatz, bei dem die formale Schnittstelle auf der Basis der Java-Schnittstellenbeschreibung generiert wird, führt in der Praxis regelmäßig zu inkompatiblen und inhaltlich unscharfen Webservices.

Deshalb setzen Contract-first-Webservices konsequent auf eine formale Beschreibung der Webservice-Schnittstelle in WSDL und XML Schema. Eine konkrete JAX-WS-Implementierung nutzt diese Beschreibung, um Webservice-Server oder Client-Code zu generieren, der zwischen den Implementierungen portabel ist. Der generierte Code unterscheidet sich dabei nicht wesentlich von dem im obigen Beispiel dargestellten Code-Fragment, mit der Ausnahme, dass die in XML Schema leicht beschreibbaren Einschränkungen zu Datentypen und Wertebereichen in bisweilen komplexe JAX-WS-/JAXB-Deklarationen übersetzt wurden.

Für öffentliche oder unternehmensweite SOAP-Webservices empfiehlt sich daher auf jeden Fall der Contract-first Ansatz, zumal er erfahrungsgemäß neben der Zusicherung der Interoperabilität die Analyse- und Designphase einer Schnittstelle unter Einbindung von etwa Fachabteilungen deutlich vereinfacht.

Als Alternative zu den bisher vorgestellten klassischen SOAP- stellen RESTful-Services nur den Zugriff auf Daten einer Anwendung via HTTP bereit. Die dafür vorgesehenen Operationen erinnern daher auch eher an die Interaktion mit einem persistenten Medium wie einer Datenbank als an die Interaktion mit einem "Service". Würde ein Entwickler nach dem herkömmlichen SOAP-Ansatz für einen Warenkorb Methoden zum Hinzufügen, Ansehen, Aktualisieren und Entfernen eines Artikels realisieren, müsste er die SOAP-Operationen addItem, listItem, updateItem und removeItem umsetzen. RESTful-Services mit ihrem Fokus auf Daten nutzen stattdessen zur Unterscheidung dieser "Create, Read, Update and Delete"-Funktionen (CRUD) von HTTP vorgesehene Features, sodass zur Abbildung der vier RESTful-Operationen eine Ziel-URL reicht, die via HTTP PUT, GET, POST und DELETE angesprochen wird.

Aus Sicht von RESTful-Services ist jedes Datenelement einer Anwendung eine Ressource, die direkt über eine URL erreichbar ist und sich somit lesen beziehungsweise verändern lässt. Hat zum Beispiel der Nutzer Max Mustermann in seinem Warenkorb 4711 die Items 12345 und 12346, ließe sich der Warenkorb über die URL http://www.openknowledge.de/carts/4711 und das Item 12345 über die URL http://www.openknowledge.de/carts/4711/item/12345 ansprechen. Möchte man auf alle Artikel eines Warenkorbs zugreifen, kann man entweder den bestehenden Pfad http://www.openknowledge.de/carts/4711/item nutzen oder aber eine neue Ressource-URL http://www.openknowledge.de/carts/4711/items einführen. Bei der Definition von RESTful-URLs sind der Fantasie keine Grenzen gesetzt, vielmehr bestimmen das Datenmodell einer Anwendung und die Beziehungen zwischen den bereitgestellten Informationen das Aussehen dieser sogenannten Resource Identifiers.

Das Format der zwischen einem RESTful-Client und -Server ausgetauschten Daten richtet sich – gemäß REST-Konvention – nach den Fähigkeiten des Clients. Grundlage der Entscheidung bilden die vom Client mitgeschickten HTTP Accept Header, über die man signalisieren kann, welche Content-Typen der Client im Allgemeinen, aber auch für den konkreten Request als Antwort unterstützt. Denkbare Austauschformate sind neben HTTP-Formulardaten und XML auch Image-Formate sowie die JavaScript Object Notation (JSON). Gerade Letztere ist ein "klassisches" Datenformat für RESTful-Services.

Auch wenn RESTful-Services in vielen Webanwendungen – beispielsweise von Google – schon lange eine große Rolle spielen, mussten Java-Entwickler erst auf Java EE 6 warten, bevor eine direkte REST-Unterstützung für Enterprise-Java angeboten wurde. Wenig überraschend setzt der dafür geschaffene Standard Java API for RESTful Web Services (JAX-RS) erneut auf einfache Java-Klassen, -Interfaces und -Annotationen.

Ressourcen sind gemäß JAX-RS Klassen, die auf Typ- oder Methodenebene mindestens eine @Path-Annotation vorweisen. Die Annotation selbst legt fest, unter welcher URL beziehungsweise über welchen URL-Bestandteil die markierte Klasse oder Methode erreichbar sein soll. Zusätzlich erlaubt sie die Deklaration von an die Methode zu übergebenen Parametern, die man als URL-Bestandteile, aber auch als HTTP-Formulardaten übergeben kann.

Das folgende Listing zeigt einen Warenkorb, der lesend Zugriff auf alle im Warenkorb befindlichen Artikel, aber auch einzelne Artikel ermöglicht. URL-Bestandteile nach item werden automatisch als Parameter der Methode listItem zur Verfügung gestellt, der Zugriff auf alle Artikel erfolgt ohne zusätzliche Parameter.

public class CartResource {

@GET
@Path("items")
@Produces("text/xml")

public List<Item> listItems() {...}

@GET
@Path("item/{item.id}")
@Produces("text/xml")

public Item listItem(@PathParam("item.id") String id) {...}

}

Neben der @GET-Annotation bietet JAX-RS auch die Annotationen @PUT, @POST, @DELETE und – für fließend HTTP-Sprechende nicht unerwartet – @HEAD. Alle Methoden im Beispiel sind zusätzlich noch mit einer @Produces-Annotation versehen. Diese weist zum einen die JAX-RS Runtime an, den Rückgabewert in das Text-/XML-Format zu transformieren. Zusätzlich hilft sie der Runtime, eine passende Methode für den anfragenden REST-Client zu finden. Hierzu werden – wie schon erläutert – die Accept Header der HTTP Requests ausgewertet. Im Umkehrschluss kann somit eine RESTful-Webressource unterschiedliche Methoden je nach anfragendem Client und unterstütztem Content Type bereitstellen.

Die Transformation von Java-Objekten in das jeweilige, für einen RESTful-Client verwertbare Format und umgekehrt übernehmen die sogenannten JAX-RS Entity Provider. Ein MessageBodyReader übernimmt das Mapping von HTTP-Request-Daten zu einem Java-Objekt, das Gegenstück, der MessageBodyWriter, das Erzeugen der HTTP Response anhand eines Java-Objekts. Eine JAX-RS-Implementierung muss dabei neben der direkten Unterstützung einfacher Java-Datentypen wie String oder byte[] auch JAXB-Klassen und HTTP-Formulardaten unterstützen. Um also das im REST-Beispiel genutzte Item nach XML zu serialisieren und umgekehrt, lässt sich das oben für JAX-WS eingeführte Item inklusive der JAXB-Annotationen nutzen.

Darüber hinaus bieten die meisten JAX-RS-Implementierungen direkte Unterstützung des JSON-Formats an. Weiterführende Anforderungen lassen sich mit eigenem MessageBodyReader beziehungsweise MessageBodyWriter leicht umsetzen.