Beispiel für eine REST API als endlicher Automat
Im letzten Beitrag haben wir diskutiert, wie eine REST API als endlicher Automat modelliert werden kann. Betrachten wir dazu jetzt ein Beispiel.
Wir konstruieren eine REST-Schnittstelle für einen virtuellen Zettelkasten. Wir vereinfachen unser Beispiel insofern, dass wir von einer Benutzerauthentifizierung absehen. Der erste Zustand der Schnittstelle ist die Anzeige aller vorhandenen Karten dem Zettelkasten. Die Basis-URL, d.h. die einzige Adresse die einem Client vor ab bekannt sein muss, bezieht sich also auf die Menge aller Karten. Da die Menge der Karten eventuell sehr groß sein kann, wenden wir hier einen Mechanismus an, mit dem wir nur einzelne Seiten oder Kacheln an den Client zurück geben.
Wir treffen darüber hinaus an dieser Stelle die Entscheidung dass wir in diesem Zustand eine Menge oder Liste von den Adressen der Karten mitgeben. Der Server liefert in diesem Fall also gerade nicht eine vollständige Repräsentation der Karten. Der Grund hierfür ist einfach: zum einen verringern wir damit die Menge der zu übertragenen Daten, zum anderen ermöglichen wir einem Client das lokale Zwischenspeichern von Karten in einem Cache. Dahinter liegt wiederum die Idee, dass sich der Inhalt von Karten relativ selten ändert, und ein lokaler Client durchaus in der Lage sein könnte, eine größere Menge von Karten temporär zu speichern.
Die typische Antwort des Servers auf eine solche Anfrage eines Clients besteht also zum einen aus einer Liste von URLs auf die Karten, zum anderen übergibt der Server an den Client aber auch weitere Hyperlinks über die der Client weitere Aktionen ausführen kann. Da wir unser Ergebnis in Form von einzelnen Seiten übertragen, übergibt der Server an den Client weitere URLs für die Anzeige der folgenden oder der vorherigen Seite, sofern vorhanden. Damit haben wir jetzt im ganzen schon drei Transitionen definiert: zwei Transitionen führen wieder zum gleichen Zustand. Diese entsprechen den beiden Hyperlinks auf die vorherige oder folgende Seite. Die Adressen der konkreten Zettel entsprechend auch einer weiteren Transition. Und diese Transition führt uns nun tatsächlich zu einem weiteren Zustand.
Lassen wir uns diesen letzten Punkt noch einmal wiederholen: alle URLs auf die Zettel stellen Übergänge zu einem einzigen Zustand dar. Warum ist das so? Nun, das Abrufen des Inhalts eines Zettels ist ein neuer Zustand der Applikation, der jedoch unabhängig davon ist, welcher Zettel aufgerufen wird. Wir befinden uns jetzt also in unserem zweiten Zustand der Applikation: das Abrufen des Inhalts von einem einzigen Zettel.
Nun wird es interessant. Innerhalb der Applikation sehen wir jetzt den Inhalt eines Zettels. Aber was sind die nächsten Zustände? Kommen wir nochmal auf den Punkt zurück, dass der Server über den Programmfluss entscheidet der Server definiert hier also den Funktionsumfang des Clients, indem er in der Antwort zusätzlich zum Inhalt der Seite bzw. des Zettels auch noch weitere Informationen mitgibt, was mit diesem Zettel als nächstes gemacht werden kann.
Hat der Nutzer zum Beispiel die Möglichkeit oder das Recht den Zettel inhaltlich zu verändern, dann enthält die Antwort des Servers auch die URL, über die der Client eine aktualisierte Version des Zettels schicken darf. Technisch kann man sich diese Übertragung der Link Information so vorstellen, dass neben der eigentlichen URL auch noch eine Art von semantischen Information zur Bedeutung dieser URL mitgegeben wird. Die Grenzen der semantischen Ausdrucksstärke sind jedoch schnell erreicht. Typischerweise wurde man an dieser Stelle lediglich zum Beispiel ein Wort wie Update mitschicken. Hat der Client darüber hinaus sogar noch das Recht, die Karte auch zu löschen, so würde die Antwort auch noch die URL enthalten, an die der Client die Operation schicken muss.
Auf zwei Punkte möchte ich in diesem Zusammenhang noch einmal eingehen.
Erstens, des Server legt fest, welche Operationen auf den Ressourcen erlaubt sind. Der Server übergibt nur solche Zustandsübergänge, die nach seiner Meinung im aktuellen Kontext erlaubt sind. Zweitens, dadurch dass der Server bei jeder Anfrage nur die erlaubten Zustandsübergänge mitgeschickt, ist es für den Client nicht nur unnötig sondern sogar verboten, URLs die er eventuell in einem folgenden Schritt benötigen würde, selbst zusammen zu bauen. Clients erhalten URLs vom Server und benutzen diese ohne weitere Veränderung. Ausnahmen stellen hierbei lediglich Suchanfragen da. Bei denen würde der Server keine korrekte URL sondern eine Vorlage, ein so genanntes Template einer URL mitgeben, die dann der Client noch durch einen einfachen Ersetzungsprozess in eine korrekte URL umwandeln muss.
Natürlich wäre das Verändern oder gar Löschen eines Zettels jeweils ein neuer Zustand. Eine direkte Folge davon ist wiederum, dass in der Antwort des Servers auf eine Anfrage zum Aktualisieren oder Löschen eines Zettels auch die Informationen über die dann möglichen Folgezustände enthalten sein muss. Nach dem Aktualisieren eines Zettels scheint der Folgezustand natürlicherweise wieder das Abfragen des Zettels zu sein. Der Folgezustand nach dem Löschen eines Zettels ist natürlich ein anderer. Hier böte es sich an, zum Beispiel erneut die Liste der Zettel zu laden, denn es ist ja nicht klar, welcher Zettel als nächster angezeigt werden soll. Man könnte natürlich auch so argumentieren, dass als nächster Zettel der in einer irgendwie definierten natürlichen Ordnung davor oder danach liegende Zettel angezeigt werden soll. Diese Entscheidung es dem Entwickler überlassen.
Um das kleine Beispiel abzuschließen, fehlt uns nun nur noch der Zustand zum Anlegen eines Zettels. In diesem Zustand kommt man nach meiner Einschätzung sowohl aus dem Zustand in dem eine Liste von Zetteln angezeigt wird, als auch aus dem Zustand zum Anzeigen von nur einem Zettel. An dieser Stelle muss man sich wieder klar machen, in welchen Situationen oder Gelegenheiten der Nutzer den Wunsch haben könnte, einen neuen Zettel anzulegen. Nachdem wir nun geklärt haben aus welchen Zuständen man in den Zustand zum Anlegen eines Zettels kommt, liegen wir nun noch fest dass der Folgezustand nach dem Anlegen eines Zettels, dass Anzeigen eines Zettels ist.
Damit haben wir unser kleines Beispiel komplett dargestellt. Zuletzt möchte ich noch kurz auf die Unterschiede eingehen, die nach meiner Meinung entstanden wären, wenn wir beim Design der Schnittstelle nicht einen endlichen Automaten im Hinterkopf gehabt hätten. An die notwendigen URLs, um von einer Übersichtsseite zu einer anderen zu gelangen, daran hätte vermutlich jeder gedacht. Dass man nach dem Anlegen einer neuen Ressource die neue URL in der Antwort mitgibt, ist ebenfalls übliches Vorgehen. Ich bin mir aber sicher, dass man ohne Benutzen eines endlichen Automaten nicht an die notwendigen URLs gedacht hätte, nach dem Aktualisieren oder Löschen dem Client die Entscheidung über die als nächstes anzuzeigenden Daten abzunehmen. Und vor allem hätte man nicht die unterschiedlichen Rollen der Nutzer beachtet und damit auch nicht unterschiedliche Funktionen an die Rollen gebunden.
Die Verwendung eines endlichen Automaten an dieser Stelle ist natürlich kein Selbstzweck. Wir kennen aus der Theorie der endlichen Automaten zahlreiche Prüfverfahren, über die wir die Korrektheit und auch die wohl geformte Zeit eines solchen Automaten zeigen können. Für uns ist es die Grundlagen zur formalen Beschreibung einer solchen API mit Hilfe neuer formaler Sprachen.