Vorgaben und Konventionen für REST-Services

1. Aufbau der Ressourcen-URIs

Jede Ressource eines REST-Services ist über einen URI eindeutig adressierbar. Dieser Abschnitt legt Konventionen für den Aufbau der URIs fest.

Dieses Konzept empfiehlt die Nutzung von HATEOAS.

Ressourcen repräsentieren Objekte und werden deshalb als Substantiv beschrieben. Die Bezeichnung muss möglichst einfach gewählt werden (z. B. der Name der Entität oder die Aufgabe der Ressource). Repräsentiert die Ressource eine Menge von Objekten, wird der Plural verwendet.

Der hierarchische Aufbau von Ressourcen ist explizit erwünscht:

Tabelle 1. Namenskonvention Ressourcen-URIs
Ressourcen-URIs

Schema

/{ressource}/{id}/{ressource}/{id}/{...}

Regeln

Die Bezeichnung der Ressource beinhaltet ausschließlich Kleinbuchstaben.

Zwischen Teilen zusammengesetzter Begriffe steht jeweils ein Bindestrich.

Beinhaltet die Bezeichnung Sonderzeichen, müssen sie gemäß üblicher Transkriptionsregeln ersetzt werden (z. B. "ae" statt "ä").

Beispiele

/kunden/12345/bestellungen/123/artikel

/nachrichten

Beispiele für eine Menge von Objekten.

Die Ressource liefert alle Objekte zurück.

/nachrichten/{id}

Beispiel für ein einzelnes Objekt. Die Ressource liefert ein eindeutig identifizierbares Objekt zurück.

/eingehende-nachrichten/

Beispiel für zusammengesetzte Begriffe.

/vertraege/

Beispiel für die Ersetzung von Sonderzeichen.

1.1. Beziehungen zwischen Ressourcen

Beziehungen zwischen Ressourcen können entweder über HATEOAS (vgl. Stufe 3 im Richardson Maturity Model) oder über die URI selber abgebildet werden.

Tabelle 2. Namenskonvention Beziehungen zwischen Ressourcen
Beziehungen zwischen Ressourcen

Schema

/{ressourcen A}/{id von einer Ressource A}/{ressource(n) B}/{...}

Hinweis

Enthält eine Ressource wiederum mehrere Ressourcen, wird wieder der Plural verwendet.

Beispiele

/nachrichten/{id}/absender

adressiert den Absender einer bestimmten Nachricht.

/nachrichten/{id_n}/cc/{id_e}

adressiert den bestimmten CC-Empfänger (mit der Id id_e) einer bestimmten Nachricht (mit der Id id_n).

1.2. Adressierung von mehreren Ressourcen

Der Zugriff auf mehrere Ressourcen aus einer Menge erfolgt über eine kommaseparierte Liste von IDs.

Tabelle 3. Namenskonvention Adressierung von mehreren Ressourcen
Adressierung von mehreren Ressourcen

Schema

/{ressourcen}/{id1},{id2}

Beispiele

/nachrichten/{id1},{id2}

1.3. Query Parameter

Query Parameter werden ausschließlich für das Sortieren, Paginierung und Filtern von Ressourcen verwendet.

Beispiel Sortierung:

/nachrichten?sort=timestamp,ASC

Beispiel Filter:

/nachrichten?timestamp=2020-08-23&land=deutschland

Beispiel Paginierung:

/nachrichten?page=5&pageSize=15
Datenschutzrichtlinien bei Query Parametern beachten

Es dürfen nur nicht datenschutzrelevante Informationen in Query Parametern verwendet werden, um das Loggen von datenschutzrelevanten Daten zu verhindern.

Für alle anderen Suchen sind POST-Requests zu verwenden, siehe Suche und Filterung mittels POST.

2. Verwendung von HTTP-Methoden

Die folgenden Kapitel beschreiben, welche HTTP-Methoden zu unterstützen sind und welche Funktion sie besitzen. Die übrigen HTTP-Methoden werden nicht verwendet.

GET wird verwendet, um eine Ressource zu lesen. Die Ressource wird dabei nicht verändert, wodurch diese Methode idempotent ist.

Idempotenz beschreibt die Möglichkeit, den gleichen HTTP-Request mehrfach an die Schnittstelle senden zu können, ohne dass ein anderes Ergebnis erzielt wird als bei einem einzelnen Request.

Listing 1. Beispiele für GET-Anfrage
GET /kunden/1234 HTTP/1.1
accept: text/html, application/json, application/xhtml+xml, application/xml;q=0.9, */*;
charset=utf-8
HOST: xx.yy.zz
ACCEPT-ENCODING: gzip, deflate, br
Listing 2. Beispiele für GET-Antwort
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
    "id":1234,
    "name":"Mustermensch",
    "adresse":"Musterstraße 1",
    "ort":"Musterstadt"
}

POST wird verwendet, um eine neue Subressource innerhalb einer Ressource anzulegen. Es ist beim Erstellen von Datensätzen in den meisten Fällen das Mittel der Wahl, da das Backend hierbei die ID vergibt. Dadurch, dass die Datensätze mit neuen IDs angelegt werden, wird bei jedem weiteren senden des Requests ein neuer Datensatz angelegt und die Methode ist nicht idempotent. Des Weiteren muss POST für komplexe Suchen und Suchen mit datenschutzrelevanten Informationen verwendet werden, siehe Suche und Filterung mittels POST.

Listing 3. Beispiele für POST-Anfrage
POST /kunden HTTP/1.1
HOST: xx.yy.zz
Content-Type: application/json; charset=utf-8
{
    "name":"Mustermensch",
    "adresse":"Musterstraße 1",
    "ort":"Musterstadt"
}
Listing 4. Beispiele für POST-Antwort
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
    "id":1234,
    "name":"Mustermensch",
    "adresse":"Musterstraße 1",
    "ort":"Musterstadt"
}
Verwendung von POST

POST wird auch für fachliche Operationen genutzt, die keiner der anderen HTTP-Methoden zugeordnet werden können (z. B. Verifikation eines Antrags).

PUT wird verwendet, um eine Ressource zu ändern oder zu erstellen. Beim Ändern wird die gesamte Ressource mitgesendet und nicht nur der zu ändernde Teil wie bei PATCH. Ist die Ressource mit der gesendeten ID noch nicht vorhanden, wird diese durch PUT erstellt. Dies ist aber zu vermeiden, da der Client keine IDs vergeben soll. PUT ist, da immer die gesamte Ressource geändert wird, bzw. die ID beim Erstellen schon vorgegeben ist, idempotent. Deshalb ist PUT PATCH beim Aktualisieren vorzuziehen. In Abhängigkeit davon, ob eine Ressource geändert oder neu erstellt wurde, wird der entsprechende Response-Code (200 OK bzw. 201 Created) zurückgegeben.

Listing 5. Beispiele für PUT-Anfrage
PUT /kunden/1234 HTTP/1.1
HOST: xx.yy.zz
Content-Type: application/json; charset=utf-8
{
    "name":"Mustermensch",
    "adresse":"Musterstraße 1",
    "ort":"Beispielort"
}
Listing 6. Beispiele für PUT-Antwort
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
    "id":1234,
    "name":"Mustermensch",
    "adresse":"Musterstraße 1",
    "ort":"Beispielort"
}

PATCH wird verwendet, um eine Ressource mit einer bestimmten ID zu ändern. Hierbei werden nur die Felder gesendet, die geändert werden sollen und nicht die ganze Ressource. Das kann bei Ressourcen mit beispielsweise einem Auto-Increment Feld dazu führen, dass bei mehreren Patches unterschiedliche Ergebnisse erzielt werden. Deswegen kann Patch idempotent sein, muss aber nicht.

Listing 7. Beispiele für PATCH-Anfrage
PATCH /kunden/1234 HTTP/1.1
HOST: xx.yy.zz
Content-Type: application/json; charset=utf-8
{
    "adresse":"Neue Straße 2"
}
Listing 8. Beispiele für PATCH-Antwort
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
    "id":1234,
    "name":"Mustermensch",
    "adresse":"Neue Straße 2",
    "ort":"Beispielort"
}
Verwendung von PATCH

PATCH ist nur zu verwenden, wenn PUT aus triftigen Gründen nicht funktioniert.

DELETE wird verwendet zum Löschen einer Ressource. Da jede Ressource nur einmal gelöscht werden kann, ist diese Methode idempotent.

DELETE /kunden/1234

HEAD wird verwendet, um Meta-Informationen einer Ressource zu erhalten. Es gibt die gleichen Daten zurück wie GET nur ohne Response-Body und ist damit idempotent.

HEAD /kunden/1234

OPTIONS wird verwendet, um die von einer Ressource unterstützten Methoden anzuzeigen. Es ist idempotent, da nichts verändert wird.

OPTIONS /kunden
Tabelle 4. Übersicht über HTTP-Methoden und ihre Eigenschaften
Methode Idempotent Cacheable

GET

Ja

Ja

POST

Nein, kann und sollte aber idempotent umgesetzt werden

Möglich

PUT

Ja

Nein

PATCH

Nein, kann und sollte aber idempotent umgesetzt werden

Nein

DELETE

Ja

Nein

HEAD

Ja

Ja

OPTIONS

Ja

Nein

2.1. Suche und Filterung mittels POST

Eine Filterung und Suche sollte im Regelfall über Query Parameter einer Anfrage mit dem HTTP-Methoden "GET" geschehen. Einige Abfragen sind jedoch zu komplex, um sie über HTTP GET abzubilden. Aspekte, die für eine Verwendung von POST statt GET sprechen, sind folgende (ohne Anspruch auf Vollständigkeit):

  • Query Parameter sind zu lang und es besteht die Gefahr, dass so die gesamte URL zu lang ist und vom Provider nicht verarbeitet werden kann.

  • Query Parameter enthalten datenschutzrelevante Informationen, die aufgrund der Aufzeichnung von URLs in Logs o. ä. nicht in der URL übertragen werden dürfen.

  • Die Suche ist eine unscharfe Suche, bzw. sucht nicht nach Gleichheit, wie beispielsweise Timestamp größer als.

  • Die Abfrageparameter sind nicht über ein logisches UND verknüpft, sondern z. B. über ein logisches ODER.

In diesen Fällen muss die HTTP-Methode "POST" verwendet werden. Die Filter- und Suchkriterien werden in den Body der Anfrage aufgenommen.

3. Verwendung von HTTP-Statuscodes

Statuscodes bieten eine effektive Möglichkeit zur gezielten Steuerung des Client-Verhaltens. Die konkrete Nutzung der beschriebenen und weiterer Statuscodes sind in Abhängigkeit von den Anforderungen anwendungsspezifisch festzulegen.

HTTP-Statuscode Nachricht Erläuterung

200

OK

Wird als Ergebnis eines erfolgreichen HTTP-Requests zurückgeliefert.

201

Created

Wird als Ergebnis eines erfolgreichen HTTP-Requests zurückgeliefert, wenn eine neue Ressource angelegt wurde.

202

Accepted

Die Anfrage wurde empfangen und verstanden, aber wird noch asynchron bearbeitet. Die Anfrage ist zum Zeitpunkt der Antwort möglicherweise noch nicht abgeschlossen, aber der Server hat sie zur weiteren Bearbeitung angenommen.

204

No Content

Wird als Ergebnis eines erfolgreichen HTTP-Requests zurückgeliefert, wenn der Request keinen Response-Body liefert.

304

Not Modified

Wird beim Caching eingesetzt und sagt dem Client, dass seine Ressource noch aktuell ist.

400

Bad Request

Der HTTP-Request enthält fehlerhafte Daten, z. B. XML nicht Schema-konform, es wurde ein Virus gefunden, die Anfrage enthält fachliche Fehler, etc.

401

Unauthorized

Es ist ein Fehler bei der Authentifizierung aufgetreten, z. B. falscher Benutzername/Passwort.

403

Forbidden

Der Benutzer hat nicht die erforderlichen Rechte.

404

Not found

Die angeforderte Ressource existiert nicht.

405

Method not allowed

Die aktuelle Operation ist auf der Ressource nicht erlaubt (beispielsweise PUT auf einer read-only Ressource).

406

Not Acceptable

Die Content Negotiation ist fehlgeschlagen.

409

Conflict

Die Ressource wurde zwischenzeitlich geändert.

500

Internal Server Error

Es ist ein interner Fehler bei der Bearbeitung der Anfrage aufgetreten.

Für die Auswahl weiterer Statuscodes bietet die entsprechende Wikipedia-Seite zu HTTP-Statuscodes einen guten Startpunkt. Empfehlenswert ist außerdem diese gut strukturierte Übersicht von openapi-generator.tech, die Informationen von Wikipedia und des IETF übersichtlich aufbereitet.

Ziele der Nutzung von Statuscodes in Verbindung mit einem Response-Mechanismus sind eine effiziente Gestaltung von API-Interaktionen, das Sicherstellen einer reibungslosen Nutzererfahrung und das Vermeiden von unnötiger Netzwerklast. Durch den gezielten Einsatz passender Statuscodes in Verbindung mit Response-Headern können Clients beispielsweise erkennen, ob ein erneuter Request sinnvoll ist, sie warten sollten oder ein Fehler in der Anfrage vorliegt. Damit Clients automatisiert und sinnvoll auf API-Antworten reagieren können, muss die Implementierung der Statuscodes dem tatsächlichen Verarbeitungszustand entsprechen.

Response-Mechanismus Sinnvolle Statuscodes Anmerkung

Retry-After

202 Accepted, 429 Too Many Request, 503 Service Unavailable

Einzusetzen, wenn eine spätere Wiederholung der Anfrage sinnvoll ist.

Status-URLs

202 Accepted

Zur initialen Bereitstellung von Informationen, wo der Status einer Anfrage bei asynchroner Verarbeitung abgefragt werden kann.

Detaillierte Fehlermeldungen

400 Bad Request, 406 Not Acceptable, 409 Conflict

Ermöglicht es dem Client, Fehlerursachen gezielt zu erkennen und zu beheben.

4. Repräsentationen

REST-Services können verschiedene Repräsentationen derselben Ressource anbieten. Diese Repräsentationen sind entweder textbasiert oder binär.

Liegt eine Ressource textbasiert vor, müssen ihre Repräsentationen, z. B. durch ein Schema, validierbar sein. Ebenso muss jede Repräsentation den gleichen, fachlichen Inhalt umfassen.

Liegt eine Ressource binär vor, muss sie bei direkten Abfragen (über GET) binär ohne weitere Veränderungen (z. B. Einbettung in eine textbasierte Form oder zusätzliche Encodings) zurückgegeben werden. Nicht erlaubt ist eine Einbettung einer binären Ressource in eine textbasierte Repräsentation (z. B. ein BASE64-encodiertes Bild in einem dafür geschaffenen JSON- oder XML-Dokument). Ist eine binäre Ressource Teil einer textbasierten Ressource, so wird eine Einbettung ebenfalls nicht empfohlen. Stattdessen sollte der URI der binären Ressource in der textbasierten Ressource enthalten sein, um sie bei Bedarf direkt abzufragen.

Repräsentationsform der kommunizierten Daten

Alle REST-Services innerhalb einer Systemlandschaft nutzen eine einheitliche, textbasierte Repräsentationsform. Binäre Daten werden über direkte Anfragen binär ohne Transformation zurückgeliefert.

Diese Regel reduziert die Komplexität der internen Services und erhöht die Homogenität der Systemlandschaft. Ebenso fällt der Aufwand zur Pflege mehrerer, inhaltlich identischer Repräsentationsformen weg.

Die Kommunikation mit IT-Systemen außerhalb der Systemlandschaft sollte ebenfalls über eine festgelegte textbasierte Repräsentation geschehen. Hier sind jedoch gesetzliche Vorgaben, Rahmenbedingungen und die Anforderungen der externen Kommunikationspartner zu berücksichtigen.

Ein typisches Beispiel ist eine Systemlandschaft, die intern JSON als Repräsentation nutzt, nach außen hin aber XÖV-konforme Nachrichten schicken muss und daher teilweise XML als Repräsentation nutzt.

4.1. Content Negotiation

Liegen Ressourcen in mehreren Repräsentationen vor (z. B. als JSON- und XML-Dokumente oder Medien in Form von komprimierten Daten oder Rohdaten), geschieht die Auslieferung einer konkreten Repräsentation über HTTP Content Negotiation.

Weitere Details zu Content Negotiation bietet die entsprechende Spring Dokumentation.

REST-Services setzen in ihren Antworten einen fest definierten Inhaltstyp pro Repräsentation. Wenn für eine Repräsentation mehr als ein Inhaltstyp üblich ist, reagiert ein REST-Service auf eine Anfrage tolerant. D. h. er liefert auch dann die gewünschte Ziel-Repräsentation der angeforderten Ressource aus, falls in der Anfrage ein anderer MIME-Type mit dem gleichen inhaltlichen Ziel-Format angefordert wurde.

Anfragen geben ihre inhaltlichen Präferenzen im HTTP-Header Accept mit. Der REST-Service setzt den Inhaltstyp der Antworten im HTTP-Header Content-Type. Die folgende Tabelle enthält alle Inhaltstypen, die von der IsyFact vorgesehen und unterstützt sind.

Tabelle 5. Zuordnung von Repräsentationen zu Content-Type- und Accept-Headern
Repräsentation Content-Type Accept

Textbasierte Repräsentationen

JSON (ohne HATEOAS)

application/json

application/json

JSON (mit HATEOAS)

application/hal+json

application/hal+json
application/json

XML

application/xml

application/xml
text/xml

Binäre Repräsentationen

PDF

application/pdf

application/pdf

JPG

image/jpg

image/jpg

5. Metadaten

Der Bereich Metadaten kann optional implementiert werden.

Zum Erhalten von Metadaten wird die HEAD-Methode verwendet. Die HEAD-Methode ist identisch zu GET, außer, dass sie keinen Response-Body zurückgibt.

6. Caching

Caching ist eine optionale Technik.

Wenn Caching eingesetzt werden soll, ist der Einsatz von ETags das Mittel der Wahl. ETags beschreiben die Version der Ressource, die angefragt wird. Ist der ETag im Header der Anfrage der gleiche, wie der ETag der gespeicherten Ressource, wird als Statuscode 304 zurückgegeben.

Zur Implementation wird in Spring der ShallowEtagHeaderFilter verwendet. Dieser reduziert allerdings nur die genutzte Netzwerk-Kommunikation, nicht aber die Rechenzeit im Server, da zum Vergleichen der ETags die Ressource geladen werden muss.

Weitere Caching-Methoden, die auch die Serverlast reduzieren, sind möglich.

Eine Übersicht, welche HTTP-Methoden Caching-Fähig sind, findet sich unter: Übersicht über HTTP-Methoden und ihre Eigenschaften.

7. HATEOAS

Nach dem HATEOAS-Paradigma soll eine zurückgelieferte Ressource weiterführende URLs (vergleichbar mit HTML-Links) zu verwandten Ressourcen enthalten, anstelle die verwandten Ressourcen mit der angefragten Ressource zusammengeführt zu übermitteln.

Listing 9. Beispiel für eine HATEOAS-konforme Resource mit URIs zu weiterer Resource
{
    "kunde": "Alex Mustermensch",
    "kontostand": 30,
    "vertrag": "https://base.url/kunde/1234/vertrag"
}

Folgende Vorteile ergeben sich daraus:

  • Der Client benötigt nur minimales Wissen über die Struktur der Services und der Datenstrukturen: Änderungen können somit leichter realisiert werden.

  • Der Server hat die Möglichkeit in Abhängigkeit vom Client Optionen auszublenden, indem z. B. einfach der Link nicht enthalten ist.

  • Die Bearbeitung der Anfrage auf der Serverseite ist kostengünstiger und performanter zu realisieren, da Daten nicht "zusammengesucht und -gesetzt" werden müssen.

  • Es muss in der Regel eine kleinere Nachricht übertragen werden.

Die letzten beiden Punkte führen direkt zu Fragestellungen des Schnittstellen- bzw. API-Designs:

Die letzten zwei Vorteile ergeben sich vor allem aus der Voraussetzung, dass der Client in der Regel die "zusätzlichen Daten" nicht benötigt, die nur über die URI als Referenz übermittelt werden. Werden diese zusätzlichen Daten nach jedem Aufruf der übergeordneten Ressource angefordert, werden zusätzliche technische Verarbeitungsschritte nötig (Client: Request für die neuen Daten aufbauen, versenden. Server: Anfrage & Berechtigung prüfen, Daten suchen, ggf. konvertieren, versenden, …).

Bei häufigen, parallelen Anfragen würde dieses Szenario dann einen hohen Overhead generieren. Hier ist im Einzelfall die Schnittstelle entsprechend dem gewünschten Trade-Off zu modellieren.