© StanislauV/Shutterstock.com
Anleitung für bessere REST-Schnittstellen

RESTful APIs richtig gemacht


Wer schon einmal eine Domäne mit Microservices aufgebaut hat, wird es bereits wissen: APIs für die Service-zu-Service-Kommunikation sind von zentraler Bedeutung. Da jedes Team seinen eigenen Stil hat und Schnittstellen jeweils anders implementiert, kommt es über kurz oder lang zu einem Wildwuchs von verschiedenen Ansätzen. Gleich zu Projektbeginn einen Leitfaden mit Richtlinien und Beispielen zu definieren, hilft, einheitliche und möglichst selbsterklärende APIs zu gewährleisten.

REST mit JSON ist der heute am weitesten verbreitete Ansatz für neue Programmierschnittstellen. Unzählige Bücher, Vorträge, Blogs und andere Quellen im Internet beschäftigen sich mit dem Thema. Trotzdem scheint es große Auffassungsunterschiede in der Entwicklergemeinde zu geben, wie Webschnittstellen auszusehen haben. Die JSON-API-Spezifikation [1] legt genau fest, wie ein RESTful API basierend auf einem einheitlichen Standard implementiert werden sollte.

Zunächst aber noch ein paar Worte zu APIs. Bei der Definition eines API sollte sich der Entwickler genug Zeit nehmen, denn gut entworfene APIs führen automatisch zu besseren Produkten. Es empfiehlt sich, zu Beginn eines Projekts Richtlinien zu entwickeln, die die zentralen Anforderungen an ein API festhalten. Mithilfe von Schlüsselwörtern laut RFC 2119 [2] lässt sich festlegen, wie wichtig (oder zwingend) eine einzelne Anforderung ist. Themen für einen Richtlinienkatalog sind beispielsweise API-Namensgebung, HTTP-Header und Operationen, Anfrageparameter oder Dokumentstruktur.

Richtlinien für den API-Entwurf helfen, ein gemeinsames Verständnis zu entwickeln. Grundsätzlich lässt sich sagen, dass Services, die einheitliche Standards verfolgen, leichter zu verstehen und einfacher zu integrieren sind. Daneben lassen sie sich effizienter umsetzen (gemeinsame Tools), sind stabiler gegenüber Änderungen und einfacher zu warten.

Styleguide für JSON-APIs

In der JSON-API-Spezifikation finden sich konkrete Vorschläge, wie ein gut entworfenes REST-API aussehen kann. Aus unserer Sicht ist die Spezifikation ein guter Einstiegspunkt ins Thema. Für unsere Beispiele begeben wir uns gedanklich in die Welt der Sportvereine. Wir wollen mit unserer gedachten IT-Lösung Teams, Manager und Spieler verwalten.

spiegl_geiler_apis_1.tif_fmt1.jpgAbb. 1: Datenmodell

Das Diagramm (Abb. 1) zeigt die Entität Team und seine Beziehungen. Ein Team wird von einem Manager trainiert. Einem Team werden mehrere Spieler (Player) zugeordnet. Manager und Player sind jeweils vom Typ Person.

Manager oder Coach?

Die Begriffe Manager und Head Coach werden im englischen Fußball synonym für den Cheftrainer einer Mannschaft verwendet. Zum Zweck der Lesbarkeit verwenden wir hier den Begriff Manager.

Das API: Mit wem dürfen wir sprechen?

Den Einstieg ins API bietet für unsere Beispielapplikation die zentrale Domain Entity Team. Über einen API-URL-Pfad werden klassische Operationen wie Lesen, Schreiben, Ändern, Löschen oder die Verwaltung von Beziehungen ermöglicht. Der Pfad dient auch der Dokumentation für den API-Anwender und sollte daher klar und einfach zu deuten sein. Als Einstiegspunkt für die Ressource Team bietet sich der Pfad /teams an. Der Pfadname ist in der Mehrzahl angegeben, schließlich sollen ja mehrere Teams gleichzeitig verwaltet werden können.

Wir legen daher im API fest, dass eine Entität über den Ressourcenpfad /{entity-type} verwaltet werden kann. Es handelt sich dabei typischerweise um eine Collection (1 bis n Teams). Als Namenskonvention gilt: ein Entity Type ist (oder endet auf) ein Nomen in der Mehrzahl, enthält nur Kleinbuchstaben, und einzelne Worte werden durch ein Minus getrennt:

  • richtig: /teams, /team-memberships

  • falsch: /Teams, /team, /teamMemberships, /team-search

Datenobjekte abfragen: Welche Mannschaften gibt es eigentlich?

Beginnen wir mit einem einfachen HTTP GET auf den API-Pfad /teams:

GET /teams HTTP/1.1 Accept: application/vnd.api+json

Im HTTP-Header Accept mit dem Wert application/vnd.api+json wird festgelegt, dass in der Antwort ein JSON-Dokument erwartet wird. Jeder HTTP Request sollte diesen Header setzen. Das zurückgelieferte Dokument enthält ein Array aller Team-Objekte. Eine Response könnte also etwa so aussehen wie in Listing 1.

Listing 1: Array aller „Team“-Objekte

{ "data": [ { "id": "1", "type": "teams", "attributes": { "name": "FC Norden Jugend", "category": "juniors" }, "links": { "self": "http://example.com/teams/1" } }, { "id": "2", "type": "teams", "attributes": { "name": "FC Essen", "category:" "masters" }, "links": { "self": "http://example.com/teams/2" } } ] }

Die Dokumentstruktur: Ordnung ist das halbe Leben

Zu einem gut strukturierten API gehört auch eine vorgegebene Dokumentstruktur. Sie hilft, wiederverwendbare Werkzeuge zu bauen, auch um das Zusammenspiel von Komponenten und den Daten im User Interface zu erleichtern. Ein API mit einheitlicher Struktur wirkt wie aus einem Guss – auch dann, wenn unterschiedliche Entwicklerteams an seiner Definition arbeiten. Die Struktur eines JSON-Dokuments wird von der JSON API Specification [3] genau festgelegt. Jedes Dokument besitzt immer eines von zwei Elementen: data oder errors. Optional können auch die Elemente meta, jsonapi, links oder included auf oberster Ebene enthalten sein. Das Element data enthält die eigentlichen Daten und kann entweder aus einem einzelnen Ressourcenobjekt oder aus einem Array von Ressourcenobjekten bestehen. Zusätzlich können Objektreferenzen oder auch null enthalten sein. Im Dokument aus dem letzten Beispiel wird im data-Element ein Ressourcenarray, eine Liste von Datenobjekten vom Typ teams, geliefert. Das Datenobjekt wiederum hat die Elemente id, type, attributes und links. Die Attribute id und type stellen eine Referenz {"id": "1", "type": "teams"} auf eine Entität vom Typ teams dar. Jede Ressource muss diese beiden Attribute besitzen. Datenobjekte bleiben so auch losgelöst vom API noch eindeutig identifizierbar. Der Typ deckt sich mit dem Pfadnamen im API und ist so immer ein Nomen im Plural. Die eigentlichen Daten (also z. B. "name": "FC Essen") der Entität werden im Element attributes geführt. Es scheint untypisch, dass Daten von der Objektreferenz getrennt gehalten werden. Im klassischen RDBMS oder in JPA werden Daten und Identifier meist gleichberechtigt in der Entity geführt. Die Trennung ist dennoch sinnvoll, da type und id rein technische Auszeichnungen des Objekts sind und nicht mit den fachlichen Attributen vermischt werden sollten. Durch Weglassen aller anderen Attribute erhalten wir außerdem jederzeit die Objektreferenz {"id": "1", "type": "teams"}.

Nach Datenobjekten suchen: Wo ist mein Team?

Für die Suche nach unseren Teams hängen wir wie gewohnt URL-Abfrageparameter an den URL-Pfad. Um nach Attributwerten zu filtern, sieht die Spezifikation Parameter mit dem Namensmuster filter[{attribute_name}] vor. Die Unterscheidung in den Attributen erfolgt über den assoziativen Parameter {attribute_name}. Die Suche nach dem Team „FC-Norden“ sieht dann beispielsweise folgendermaßen aus:

GET /teams?filter[name]=FC+Norden

Filterparameter lassen sich mit einem logischen UND verknüpfen:

GET /teams?filter[name]=FC+Norden+Jugend&filter[category]=juniors

oder können ein Set von Werten enthalten, was einem logischen ODER entspricht:

GET /teams?filter[category]=juniors,masters

Der reservierte Name filter hat den großen Vorteil, dass für den Anwender des API sofor...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang