Representational State Transfer (REST)

REST ist in vielerlei Munde. Oft sind sich die Nutzer über die Bedeutung dieses Buzzwords aber nicht einig. Grund genug, sich einmal die orginal Arbeit anzuschauen.

Das erste Interessante an der Arbeit ist einmal der Betreuer: Richard N. Taylor. Wenn man sich mit Software Architekturen auseinandersetzt (vorallem im akademischen Bereich), kommt man kaum um ihn herum. Auch sein Buch ist recht interessant.

Das erste Wort, welches einer Definition bedarf, ist der Architecture Sytle:

Since an architecture embodies both functional and non-functional properties, it can be difficult to directly compare architectures for different types of systems, or for even the same type of system set in different environments. Styles are a mechanism for categorizing architectures and for defining their common characteristics.

Die Arbeit beschreibt den REST Architecture Sytle. Anhand von Constraints wird REST in der Arbeit dann schrittweise definiert:

  • Client-Server: Dieses Constraint basiert auf dem Prinzip des Separation of concerns. Fieldings spricht dabei von separating the user interface concerns from the data storage concerns, um die Portabilität zu verbessern.
  • Stateless: Ein Request muss die vollständige Information enthalten, um ihn abarbeiten zu können. Der Session-State wird vom Client gehalten. Das hat den Nachteil, dass es zu einem erhöhten Netzwerktraffic kommen kann. Ebenfalls ist u.U. mehr Verhaltenslogik im Client erforderlich. Es gibt aber auch einige Verbesserungen:
    • Visibility: Fieldings definiert die Eigenschaft wie folgt: Visibility in this case refers to the ability of a component to monitor or mediate the interaction between two other components. Da das System stateless ist, sieht Fieldings den Vorteil, dass ein Monitoring-System nur einen Request betrachten muss, um den vollständigen Kontext zu verstehen.
    • Reliability: Recovering von partial failures ist einfach (Zur Erinnerung: Session-State am Client und Requests insich geschlossen)
    • Scalability: Zwischen Requests muss kein State gespeichert werden - daher einfach skalierbar.
  • Cache: Ein Cache-Constraint erlaubt es, gewisse Antworten wiederzuverwenden (ohne einen Request absetzen zu müssen). Caching hat viele Vorteile - aber auch einige Hürden. Je nach Use-Case zu entscheiden.
  • Uniform Interface: Zwischen den Komponenten gibt es ein einheitliches Interface. Wichtig ist auch noch die Anmerkung REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.
  • Layered System: siehe Arbeit
  • Code-On-Demand: siehe Arbeit

Diese Contraints machen also REST aus. Als nächstes beschreibt Fieldings die Architectural Elements. Zu beachten ist aus meiner Sicht:

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. “today’s weather in Los Angeles”), a collection of other resources, a non-virtual object (e.g. a person), and so on.

Zwischenstatus - das für mich bis jetzt interessante war:

  • Ressourcen sind der fundamentale Baustein von REST
  • Die Arbeit gibt explizit einen eingeschränkten Verwendungsbereich vor. Design-by-buzzword Architekten wird das nicht erfreuen - aber REST ist nicht für alle Probleme die Lösung.
  • Hypermedia ist ein elementarer Bestandteil.
  • Ressourcen sind facettenreich.
  • HTTP per se wird nie explizit mit REST in Verbindung gebracht - es ist eine Möglichkeit der Representations (siehe Kapitel 5.2.1.2 bzw. 5.2.2).

Anwendungspraktische Details

HTTP ist einer der häufigsten Protokolle, welche im Zusammenhang mit REST verwendet wird. Folgende Tabelle fand ich sehr interessant:

Verb Safe Idempotent
GET Yes Yes
HEAD Yes Yes
OPTIONS Yes Yes
PUT No Yes
DELETE No Yes
POST No No

Safe bedeutet, dass keine Seiteneffekte zu erwarten sind (das eventuell Logfiles geschrieben werden, wird nicht berücksichtigt). Idempotent bedeutet, dass der Effekte der Gleiche ist, egal ob der Client der Request einmal macht oder mehrfach macht. POST fällt hier aus der Reihe. Hier wurden ein paar Lösungen vorgestellt:

  1. Client erzeugt eine eindeutige URL (leere Bestellung, die nichts auslöst): http://example.com/orders/AFDAD7BC-A9E0-4972-99A3-041F4988BEE7 Auf diese Bestellung wird PUT ausgeführt, um Items hinzuzufügen.
  2. Eindeutige URI vom Server anfordern, mit der man PUT durchführen kann
  3. POST Once Exactly (POE)

DELETE stellt auch einen interessante Fall dar, da es idempotent ist. Oft wird aber der Weg gewählt, dass nach einem erneuten Aufruf “404 (Not Found)” returniert wird - und nicht wie erwartet immer “200 (OK)”.

Caching

Caching funktioniert mit HTTP sehr gut. Dies liegt einerseits an der beschriebenen Semantik bzw. an folgenden Features:

  • If-Modified-Since:

The If-Modified-Since request-header field is used with a method to make it conditional: if the requested variant has not been modified since the time specified in this field, an entity will not be returned from the server; instead, a 304 (not modified) response will be returned without any message-body. (Quelle)

  • If-None-Match und E-Tag: Ein Beispiel auf Wikipedia

Ein sehr bekannter Cache ist Varnish.

Asynchronität

Sicher auch ein interessantes Thema im Zusammenhang mit HTTP / REST. Hier stellt die Literatur auch eine Lösung parat die auf Polling basiert.

# Request
GET /images/task/1 HTTP/1.1
Host: www.example.org

# Response
HTTP/1.1 200 OK
Content-Type: application/xml;charset=UTF-8

<status xmlns:atom="http://www.w3.org/2005/Atom">
 <state>pending</state>
 <atom:link href="http://www.example.org/images/task/1" rel="self"/>
 <message xml:lang="en">Your request is currently being processed.</message>
 <ping-after>2009-09-13T02:09:27Z</ping-after>
</status>

# Response
HTTP/1.1 303 See Other
Location: http://www.example.org/images/1
Content-Location: http://www.example.org/images/task/1

<status xmlns:atom="http://www.w3.org/2005/Atom">
 <state>done</state>
 <atom:link href="http://www.example.org/images/task/1" rel="self"/>
 <message xml:lang="en">Your request has been processed.</message>
</status>

Alternativ kann der Client auch ein Callback-Interface anbieten oder eben per Websockets notifziert werden. Aber das ist mein aktuelles TODO auf der Liste - hier scheint es auch im Netz (und bei mir) viel Verwirrung zu geben.

Hypermedia

Ähnlich dem Konzept von Webseiten, findet auch hier Hypermedia Einzug. Durch Links werden dem Benutzer weitere Schritte angezeigt (Beispiel aus dem REST Buch von Oreilly).

<order xmlns=″http://schemas.restbucks.com″ xmlns:dap=″http://schemas.restbucks.com/dap″>
 <dap:link mediaType=″application/vnd.restbucks+xml″ uri=″http://restbucks.com/order/1234″ rel=″http://relations.restbucks.com/cancel″/>
 <dap:link mediaType=″application/vnd.restbucks+xml″ uri=″http://restbucks.com/payment/1234″ rel=″http://relations.restbucks.com/payment″/>
 <dap:link mediaType=″application/vnd.restbucks+xml″ uri=″http://restbucks.com/order/1234″ rel=″http://relations.restbucks.com/update″/>
 <dap:link mediaType=″application/vnd.restbucks+xml″ uri=″http://restbucks.com/order/1234″ rel=″self″/>
 <item>
 <milk>semi</milk>
 <size>large</size>
 <drink>cappuccino</drink>
 </item>
 <location>takeAway</location>
 <cost>2.0</cost>
 <status>unpaid</status>
</order>

API Design

Auch das Tooling rund um REST ist super. Hier sei erwähnt: Swagger und RAML. Diese beiden Tools erleichtern das API Design. Ein ebenfalls interessanter Ansatz ist HAL.

Umsetzung

.NET bietet mit der ASP.NET Web API 2 eine sehr einfache Nutzung an. Die Dokumentation ist sehr gut - daher sei an dieser Stelle nur auf diese verwiesen. ASP.NET nutzt Convensions - Methoden können allerdings auch mit Attributen versehen werden.

public class HelloWorldController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new[] { "hello", "world" };
    }
    
    // [Route("api/{controller}/{id}")]
    // [HttpGet]
    public string Get(int id)
    {
        return "hello world";
    }

    public void Post([FromBody]string value)
    { }

    public void Put(int id, [FromBody]string value)
    { }

    public void Delete(int id)
    { }
}

Fazit

Ich finde REST sehr, sehr interessant. Es gibt viele Anwendungsbereiche, wo diese Denkweise Sinn macht. Alternativ kann man das Ganze auch wie in der folgenden Skizze gestalten. Die Skizze zeigt eine logische Darstellung einer Architektur (welche ich mir einmal für ein Einsatzleitsystem ausgedacht habe). Hohe Abfragelasten und eine hohe Anzahl an Events wurden umgesetzt durch Separierung und Entlastung des Backends. Hier wurde REST nur für lesende Aktionen verwendet, da ein Client-Request auch umgetzt werden soll, wenn der Client sich abmeldet und das Backend gerade nicht verügbar ist. Es gibt sicher auch andere Lösungen - wäre mal interessant, diese zu testen (e.g. PUT oder POST Requests zu persistieren).

Architekturbeispiel