Kann man mit JSF eigentlich Webapplikationen entwickeln?

RESTful JavaServer Faces

Holger Kraus

Im Java Magazin 01.2011 analysierten Michael Plöd und Stefan Tilkov, inwieweit man mit dem Webframework Wicket Applikationen entwickeln kann, die den Prinzipien einer REST-Architektur entsprechen. Dieser Artikel betrachtet JavaServer Faces aus der REST-Perspektive.

JavaServer Faces aus der REST-Perspektive zu betrachten, ist deshalb interessant, weil dabei Aspekte in den Fokus rücken, die sonst bei der Bewertung von Webframeworks oft unbeachtet bleiben. Der Architekturstil REST ist ja bekanntermaßen das Ergebnis einer Dissertation [1] von Roy Fielding, in der er aus der Architektur des World Wide Webs einen allgemeinen Architekturstil für verteilte Anwendungen ableitet. Eine Betrachtung von JavaServer Faces aus der REST-Sicht bedeutet somit, dass man ein Urteil darüber fällt, inwieweit eine JSF-Applikation den grundlegenden Prinzipien des World Wide Webs entspricht.

Die grundlegenden Eigenschaften von JSF

Um von einem gemeinsamen Verständnis der JSF-Architektur ausgehen zu können, sollen an dieser Stelle kurz die wichtigsten Eigenschaften von JavaServer Faces [2] rekapituliert werden. Neben der vereinfachten Abwicklung von Routineaufgaben war eine wichtige Zielsetzung von JavaServer Faces, Entwickler, die sich nicht auf die Webentwicklung spezialisiert haben, in die Lage zu versetzen, anspruchsvolle webbasierte Benutzeroberflächen implementieren zu können. In JSF werden Benutzeroberflächen bzw. Views deshalb aus Komponenten zusammengesetzt. Ein Teil der Komponenten wird bereits vom jeweiligen JSF-Framework zur Verfügung gestellt. Daneben können aber auch eigene Komponenten entwickelt oder solche von Drittherstellern eingebunden werden. Möglich wird dies durch ein standardisiertes Komponentenmodell. Um zu gewährleisten, dass auf Komponenten auch programmatisch zugegriffen werden kann, verfügt jede clientseitige Komponente über eine korrespondierende Repräsentation auf dem Server, die insgesamt den so genannten Komponentenbaum bilden. Entwickler werden durch eine Vielzahl unterschiedlicher Mechanismen unterstützt. Converter wandeln übergebene HTTP-Parameter in Java-Objekte um, damit sie innerhalb der Applikation verwendet werden können. Validatoren stellen sicher, dass die übergebenen Werte den Gültigkeitskriterien der Applikation entsprechen. Der Zustand von Managed Beans wird automatisch mit den durch den HTTP-Request übergebenen Benutzereingaben aktualisiert. Über aufgetretene Fehler wird der Benutzer durch ein eigenes Nachrichtensystem informiert.

JavaServer Faces verwendet ein eigenes Ereignismodell. Dieses reagiert nicht direkt auf Benutzeraktionen, sondern auf Ereignisse im Rahmen der serverseitigen Verarbeitung. Dabei kann es sich um Veränderungen in Komponenten, das Auslösen von Aktionen oder das Erreichen einer bestimmten Verarbeitungsphase handeln. Da Workflows in Webapplikationen oft durch eine Abfolge unterschiedlicher Views realisiert werden, unterstützt JSF die Definition solcher Arbeitsabläufe durch ein eigenes Navigationsmodell. Die unterschiedlichen Verarbeitungsmechanismen sind durch ein standardisiertes Modell, den so genannten JSF-Lifecycle, miteinander verbunden, der von jedem HTTP-Request durchlaufen wird. Am Anfang des Lifecycles steht die Wiederherstellung des serverseitigen Komponentenbaums, am Ende das Rendern einer Antwort [3].

Dieser kurze Überblick soll deutlich machen, dass JavaServer Faces über ein eigenes Programmiermodell verfügt. Ein Entwickler, der eine JSF-Applikation implementiert, bewegt sich primär innerhalb dieses Programmiermodells und muss sich meistens mit den Details, die dadurch verborgen werden, nicht auseinandersetzen.

JSF aus der Perspektive der fünf Grundprinzipien von REST

Die erwähnten Eigenschaften von JavaServer Faces ermöglichen es, innerhalb kurzer Zeit Webapplikationen mit einer klar strukturierten Architektur zur Verfügung zu stellen. Inwieweit sich diese Architektur mit der Architektur des Webs verträgt, soll im Folgenden untersucht werden, indem JavaServer Faces in Beziehung zu den fünf Grundprinzipien [4] von REST gesetzt wird.

  1. Ressourcen mit eindeutiger Identifikation: Ressourcen sind in einer REST-Architektur Objekte, die in einem globalen Namensraum eindeutig identifizierbar sind. Die Identifikation dieser Objekte geschieht im Rahmen von RESTful HTTP durch die Verwendung eines Uniform Resource Identifier (URI) . In einer Anwendung, die den REST-Prinzipien genügt, verfügen sämtliche extern relevanten Domänenobjekte über einen eigenen URI. In einer typischen JSF-Applikation werden Domänenobjekte dagegen nicht als Ressourcen angesehen. Ein URI identifiziert hier eine View. Die Daten der Anwendung liegen gekapselt hinter der Benutzeroberfläche der Anwendung.
  2. Erzeugung von Repräsentationen: Ressourcen verfügen in einer REST-Architektur über eine oder mehrere Repräsentationen, da sich in der Ressource selbst nur ein abstraktes Konzept manifestiert. HTTP spezifiziert, dass ein Client dem Server in einer Anfrage über die HTTP-Header mitteilt, welche Repräsentationen von ihm verarbeitet werden können. Der Server teilt dann in der Antwort über die HTTP-Header mit, welche Repräsentation einer Ressource tatsächlich übermittelt wurde. JavaServer Faces verfügt zwar auch über die Fähigkeit, Inhalte in unterschiedliche Repräsentationen auszuliefern, das geschieht allerdings nicht über die Content Negotiation von HTTP, sondern durch die Verwendung von Renderern. Die Renderer sind für die Darstellung einer einzelnen UI-Komponente verantwortlich, während in den Komponentenklassen selbst das darstellungsunabhängige Verhalten implementiert ist. REST fordert unterschiedliche Repräsentationen für Inhalte, JavaServer Faces bietet dagegen unterschiedliche Repräsentationen für Benutzeroberflächen.
  3. Standardmethoden: Global identifizierbare Ressourcen entfalten erst dann ihr gesamtes Potenzial, wenn allen potenziellen Nutzern bekannt ist, wie sie verwendet werden können. Eine Applikation mit einer REST-Architektur verfügt deshalb über ein einheitliches Methoden-Interface, d. h. alle Ressourcen können global über dieselben Methoden angesprochen werden. HTTP spezifiziert insgesamt acht Standardmethoden. Im Rahmen von RESTful HTTP werden davon meist die folgenden vier verwendet:
    • GET kommt zum Einsatz, wenn der Client über den aktuellen Zustand einer Ressource informiert werden möchte. Der Client sendet zu diesem Zweck einen GET-Request mit dem URI der angeforderten Ressource zum Server. Der Server liefert daraufhin eine Repräsentation der Ressource an den Client.
    • PUT dient zum Erzeugen einer neuen oder zum Modifizieren einer bereits existierenden Ressource. Neben dem URI wird dazu eine Repräsentation der Ressource an den Server gesendet. Wenn eine Ressource mit dem referenzierten URI bereits existiert, wird sie anhand der gesendeten Repräsentation aktualisiert. Existiert unter dem angegebenen URI bisher keine Ressource, wird sie erzeugt.
    • POST kann ebenfalls zum Erzeugen einer neuen Ressource verwendet werden, allerdings unterscheidet sich das Anlegen einer Ressource mit POST semantisch vom Anlegen einer Ressource mit PUT. Bei einem POST identifiziert der URI die Listenressource des gesendeten Ressourcentyps, eine neue Ressource wird somit an eine bestehende Liste angehängt. Der Server ist damit dafür verantwortlich, der neu erzeugten Ressource einen URI zuzuordnen. Wenn der POST-Request wiederholt wird, wird jedes Mal eine neue Ressource erzeugt, da der Server davon ausgeht, dass wieder ein neues Objekt an die Liste angehängt werden soll. POST verfügt noch über eine zweite Bedeutung. Alle Aktionen, die sich durch die Verwendung der Standardmethoden von HTTP nicht abbilden lassen, können auch durch ein POST getunnelt werden. POST kann also verwendet werden, um Abläufe zu implementieren, die sich mit dem Standardverhalten von HTTP nicht realisieren lassen.
    • DELETE veranlasst das Löschen einer Ressource. Der Client sendet mit einem DELETE-Request einen URI zum Server. Der Server löscht die Ressource, die durch diese URI referenziert wird.

    Bis einschließlich JSF 1.2 wurde sämtliche Kommunikation zwischen Client und Server durch die Verwendung von POST-Requests abgewickelt. Die REST-Prinzipien erlauben es zwar, POST zum Tunneln eigener Kommunikationsprotokolle zu verwenden, das sollte aber eigentlich nur geschehen, wenn für ein Anwendungsszenario keine geeignete HTTP-Methode zur Verfügung steht. Aber selbst das Verfolgen von Hyperlinks, das normalerweise einen GET-Request auslöst, wird in JSF 1.2 durch einen POST-Request abgebildet. Der Grund dafür ist, dass bei JavaServer Faces jeder Request immer den standardisierten JSF-Lifecycle durchläuft. Die Aktion, die schließlich auf dem Server ausgeführt wird, wird allein durch den erzeugten ActionEvent bestimmt und nicht durch die verwendete HTTP-Methode. Das POST ist somit lediglich die Methode, die den JSF-Lifecyle anstößt. Die ausschließliche Verwendung von POST-Requests hatte zur Folge, dass nicht einmal Browser-Bookmarks auf die Seiten einer JSF-Applikation gesetzt werden konnten oder der Back-Button funktionierte. In Version 2.0 hat man JavaServer Faces deshalb um einen Mechanismus erweitert, der es erlaubt, dass auch GET-Requests verarbeitet werden können. Darauf wird später im Artikel detaillierter eingegangen.

  4. Statuslose Kommunikation: HTTP ist ein zustandsloses Protokoll. Die Kommunikation zwischen Client und Server findet in kompletter Isolation statt, d. h. der Server ist für die korrekte Verarbeitung eines Requests nicht auf Informationen aus einem vorherigen Request angewiesen. Das Speichern von Sitzungsinformationen auf dem Server steht im Widerspruch zu den Prinzipien von RESTful HTTP. Bei JavaServer Faces wird jede View – wie bereits erwähnt – durch einen Komponentenbaum auf dem Server repräsentiert. Dieser Komponentenbaum existiert genauso lange, wie die aktuelle View bearbeitet wird, d. h. über mehrere Request-Response-Zyklen hinweg. Zwischen zwei Requests muss deshalb der Zustand des Komponentenbaums gespeichert werden. Dafür ist der so genannte StateManager verantwortlich. Er sorgt dafür, dass vor Absenden der Server-Response der Status des Komponentenbaums serialisiert und gespeichert wird. Beim Empfang eines Requests stellt er umgekehrt sicher, dass anhand des serialisierten und gespeicherten Zustands des Komponentenbaums auf dem Server wieder ein Java-Objekt-Baum aufgebaut wird. Die serialisierte Information kann sowohl auf dem Client als auch auf dem Server gespeichert werden. Eine clientseitige Zustandsspeicherung ist im Sinne von REST eine statuslose Kommunikation, wohingegen eine serverseitige Zustandsspeicherung eine statusbehaftete Kommunikation darstellt. Neben dem Zustand des Komponentenbaums gibt es noch weitere Informationen, die zwischen mehreren Requests nicht verloren gehen dürfen. Zur Speicherung werden oft Managed Beans verwendet, die jeweils einem der JSF-Scopes zugeordnet werden. Meist werden diese Informationen über mehrere Request-Response-Zyklen hinweg benötigt, sodass sie im Session- oder View-Scope serverseitig gespeichert werden. In diesem Fall sind die Kommunikationsabläufe in JavaServer Faces statusbehaftet. Managed Beans können alternativ auch im Request Scope abgelegt werden. Das erfordert einigen Aufwand, und die relevanten Managed Beans müssen dann bei jedem Request neu instanziert werden. Wenn man beim Architekturentwurf explizit darauf achtet, ist statuslose Kommunikation in JSF möglich. Es ist konzeptionell aber ein Ausnahmefall und hat viel Overhead zur Folge.
  5. Nutzung von Hypermedia: In einer Anwendung nach REST-Prinzipien werden einem Benutzer sämtliche aktuell möglichen Statusveränderungen über Verknüpfungen in der jeweiligen Ressourcenrepräsentation angeboten. Der Server bestimmt bei der Erzeugung der Repräsentation, welche Statusveränderungen sinnvoll sind, der Client entscheidet aber selbst, welcher Verknüpfung er schließlich folgt. In JavaServer Faces sind die aktuell möglichen Aktionen zwar auch in der aktuellen Repräsentation der View enthalten, dabei handelt es sich allerdings nicht um Verknüpfungen, sondern um UI-Komponenten, die in der Lage sind, ActionEvents auszulösen. JSF ähnelt in der internen Kommunikationsabwicklung eher dem Modell des Remote Procedure Calls, da die eigentlichen Aktionen durch das Aufrufen von serverseitigen Methoden , die jeweils zu einer Managed Bean gehören, ausgelöst werden. Der neue Applikationszustand wird in JSF durch den Aufruf eines Prozesses erzeugt und nicht durch das Verfolgen eines Hyperlinks. Auf welche View die Anwendung schließlich verzweigt, wird anhand serverseitiger Navigationsregeln bestimmt. Diese sind für den Client nicht sichtbar.
Geschrieben von
Holger Kraus
Kommentare
  1. Klaus2015-03-25 10:46:46

    Eine sehr, sehr gute Zusammenfassung und Erklärung derTechnologien!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.