Konzept REST
Diese Seite ist ein Teil der IsyFact-Standards. Alle Inhalte der Seite, insbesondere Texte und Grafiken, sind urheberrechtlich geschützt. Alle urheberrechtlichen Nutzungs- und Verwertungsrechte liegen beim Bundesverwaltungsamt.
Die Nutzung ist unter den Lizenzbedingungen der Creative Commons Namensnennung 4.0 International gestattet.
1. Einleitung
Dieses Dokument enthält das Konzept zum Erstellen von REST-Schnittstellen. Es sorgt für eine einheitliche Umsetzung der Prinzipien in allen IT-Systemen. Es richtet sich vorwiegend an Architekten und Entscheidungsträger.
Dafür beschreibt es in Eigenschaften REST-Schnittstellen zuerst die allgemeinen Eigenschaften, die von der REST-Schnittstelle beachtet werden müssen. In Festlegung Umsetzung geht es um festgelegte Technologien und automatische Code-Generierung. Im Anschluss wird in Querschnittliche Konzepte ein Überblick über den Umgang mit Ereignissen in der REST-API gegeben.
2. Grundlagen
REST steht für Representational State Transfer und ist ein Paradigma für die Softwarearchitektur von verteilten Systemen, insbesondere für Webservices (Wikipedia). Roy Fielding hat in seiner Dissertation die Grundlagen von REST-Services definiert. Diese lauten wie folgt:
- Client-Server
-
REST-Services besitzen die Eigenschaften einer Client-Server-Architektur und folgen diesem Architekturstil.
- Zustandslosigkeit
-
Ein REST-Service speichert zwischen zwei Anfragen keinerlei Zustandsinformationen. Jede Ressource muss alle Informationen enthalten, die für den Server bzw. Client notwendig sind, um sie verarbeiten zu können.
- Caching
-
REST-Nachrichten sollen, entweder explizit oder implizit, angeben, ob sie sich für einen Cache eignen oder nicht.
- Einheitliche Schnittstelle
-
REST-Schnittstellen besitzen vier Eigenschaften, um einheitlich beschaffen und leicht nutzbar zu sein.
Jede Ressource ist über einen URI eindeutig adressierbar. Damit geschieht der Zugriff für jeden Client eindeutig und für alle nachvollziehbar.
REST-Services können an ihre Clients unterschiedliche Repräsentationen der Ressourcen ausliefern, z. B. in verschiedenen Sprachen oder Formaten. Die Veränderung einer Ressource erfolgt nur über eine dieser Repräsentationen.
Jede Anfrage muss alle Informationen zu ihrer Verarbeitung mitliefern, also selbstbeschreibend sein. Beispiele dazu sind das explizite Kennzeichnen des übertragenen Formats oder die Verwendung von HTTP-Methoden.
Hypermedia as the Engine of Application State (HATEOAS): REST-Services dürfen bei ihren Clients nur minimale Kenntnisse der Schnittstelle voraussetzen. Insbesondere das Navigieren zwischen Ressourcen sollte über Links geschehen, die Teil vorausgehender Ressourcen sind. Die Umsetzung von HATEOAS erzeugt so eine Navigation zwischen Ressourcen ähnlich zur Navigation zwischen Webseiten über Hyperlinks.
- Mehrschichtige Systeme
-
Für Aufrufer von REST-Services muss es transparent sein, welche Systeme zwischen dem Client und dem Server oder hinter der Schnittstelle liegen. Aufrufe ändern sich nicht durch Änderungen an der Infrastruktur (z. B. durch Maßnahmen zur Skalierung) oder durch technische Änderungen, welche nicht den REST-Service selbst betreffen.
Einen guten Überblick und Verweise auf detailliertere Beschreibungen bietet die Wikipedia-Seite zu REST. Für einen tiefen Einstieg in die Grundlagen bietet sich die Dissertation von Roy Fielding an. |
Das Reifegradmodell von Leonard Richardson (Richardson Maturity Model, s. Abbildung 1) ordnet diese Eigenschaften vier Reifegraden zu.
Die IsyFact zielt auf eine möglichst vollständige Umsetzung der Eigenschaften und peilt daher einen hohen Reifegrad für ihre REST-Services an.
Konkret stellt Stufe 2 des Richardson Maturity Models folgende Anforderungen:
-
Ressourcen werden über einen eindeutigen URI angesprochen.
-
Ressourcen werden über HTTP-Methoden angesprochen und je nach HTTP-Methode abgefragt oder verändert.
-
Ressourcen melden ihren Status durch eine Abfrage oder Veränderung über HTTP-Statuscodes zurück.
Stufe 3 des Richardson Maturity Models stellt darüber hinaus weitere Anforderungen:
-
Ressourcen enthalten Verweise auf URIs, mit denen weitere Anfragen im Kontext der Ressource möglich sind.
Diese grundlegende Festlegung hat große Auswirkungen auf die einzelnen Aspekte der Ausgestaltung der REST-Webservices. Diese werden in den folgenden Kapiteln beschrieben und konkretisiert.
2.1. Ausnahmen (Erlaubte Abweichungen zum Reifegradmodell)
In Ausnahmefällen kann auf die Einhaltung der Vorgabe zum Reifegrad (mindestens Level 2) verzichtet werden.
Prozessorientierte Aufrufe - Geschäftsvorfälle
Ein Ausnahmefall stellt die Abbildung von prozessorientierten Aufrufen dar, die häufig durch die Migration von bestehenden prozessorientierten Schnittstellen (z.B. RPC) entstanden sind. Die Abbildung auf eine ressourcenorientierte REST-Schnittstelle ist i.d.R. mit einer Neukonzeption der Anwendung unter einer ressourcenorientierten Denkweise verbunden. Der Aufwand für die Neukonzeption steht oft nicht im Verhältnis zum tatsächlichen (fachlichen) Nutzen. Aus diesem Grund dürfen prozessorientierte Aufrufe über einfache http-Aufrufe realisiert werden, die bis auf die Ressourcenorientierung und Verwendung der http-Verben, den übrigen Vorgaben des REST-Konzepts folgen. Diese http-Aufrufe entsprechen zwar nicht dem Reifegradmodell (Level 2), sind aber in diesem Ausnahmefall IsyFact-konform.
Wird über die Schnittstelle ein Geschäftsvorfall angestoßen, ist der Geschäftsvorfall in der URI zu adressieren:
Prozessorientierte URIs | |
---|---|
Schema |
|
Beispiele |
|
3. Eigenschaften von REST-Schnittstellen
3.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:
Ressourcen-URIs | |
---|---|
Schema |
|
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 |
Beispiele für eine Menge von Objekten. Die Ressource liefert alle Objekte zurück.
Beispiel für ein einzelnes Objekt. Die Ressource liefert ein eindeutig identifizierbares Objekt zurück.
Beispiel für zusammengesetzte Begriffe.
Beispiel für die Ersetzung von Sonderzeichen. |
3.1.1. Beziehungen zwischen Ressourcen
Beziehungen zwischen Ressourcen können entweder über HATEOAS (vgl. Stufe 3 in Abbildung 1) oder über die URI selber abgebildet werden.
Beziehungen zwischen Ressourcen | |
---|---|
Schema |
|
Hinweis |
Enthält eine Ressource wiederum mehrere Ressourcen, wird wieder der Plural verwendet. |
Beispiele |
adressiert den Absender einer bestimmten Nachricht.
adressiert den bestimmten CC-Empfänger (mit der Id |
3.1.2. Adressierung von mehreren Ressourcen
Der Zugriff auf mehrere Ressourcen aus einer Menge erfolgt über eine kommaseparierte Liste von IDs.
Adressierung von mehreren Ressourcen | |
---|---|
Schema |
|
Beispiele |
|
3.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
Für alle anderen Suchen sind POST-Requests zu verwenden, siehe Suche und Filterung mittels POST.
3.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.
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
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.
POST /kunden HTTP/1.1
HOST: xx.yy.zz
Content-Type: application/json; charset=utf-8
{
"name":"Mustermensch",
"adresse":"Musterstraße 1",
"ort":"Musterstadt"
}
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"id":1234,
"name":"Mustermensch",
"adresse":"Musterstraße 1",
"ort":"Musterstadt"
}
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.
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"
}
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.
PATCH /kunden/1234 HTTP/1.1
HOST: xx.yy.zz
Content-Type: application/json; charset=utf-8
{
"adresse":"Neue Straße 2"
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id":1234,
"name":"Mustermensch",
"adresse":"Neue Straße 2",
"ort":"Beispielort"
}
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
Eine Übersicht über die HTTP-Methoden und ihre Eigenschaften findet sich in Anhang: http-Methoden |
3.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.3. Verwendung von HTTP-Statuscodes
Im Folgenden werden die HTTP-Statuscodes dargestellt, die zu verwenden sind.
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. |
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. |
Weitere Statuscodes sind in Abhängigkeit vom Fachverfahren möglich.
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. |
3.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.
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. |
3.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
.
Für die häufigsten Repräsentationen legt Anhang: Content types die erwarteten und zurückgegebenen Inhaltstypen fest.
3.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.
3.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 Anhang: http-Methoden |
3.7. HATEOAS
Nach dem HATEOAS-Paradigma soll eine zurückgelieferte Ressourcen weiterführende URLs (vergleichbar mit HTML-Links) zu verwandten Ressourcen enthalten, anstelle die verwandten Ressourcen mit der angefragten Ressource zusammengeführt zu übermitteln.
{
"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.
4. Festlegungen zur Umsetzung
4.1. Technologieauswahl
Die IsyFact legt folgende Technologien für die Umsetzung von REST-Schnittstellen (vgl. Tabelle 1) fest.
Framework | Technologie-Stack | Beschreibung |
---|---|---|
Spring Webflux |
Java/Spring |
Umsetzung von REST-Clients (unter Nutzung von Apache HTTPComponents) |
Spring MVC |
Java/Spring |
Umsetzung von REST-Services (zur Nutzung mit Tomcat) |
Angular Framework Modul: @angular/common/http |
TypeScript/Angular |
Umsetzung von REST-Clients |
OpenAPI |
technologie-unabhängig |
Dokumentation der Service Schnittstelle und Erzeugung von Service und Client |
4.2. Spring MVC im Vergleich zu Spring Webflux
Das Spring Framework stellt seit Version 5 Spring Webflux als reaktive Alternative zu Spring MVC bereit.
Mit Webflux ist es möglich, reaktive REST APIs zu implementieren. Beim reaktiven Programmierparadigma geht es um die Reaktion auf Veränderungen (Observer Pattern), im Fall von REST APIs um die Reaktion auf gesendete Daten. Das ist hilfreich, wenn man große Datenmengen (z. B. Mediendateien oder größere Ergebnismengen aus einer Datenbankabfrage) über das REST API übertragen möchte. Die Daten werden dann in mehreren Paketen gesendet, der Empfänger reagiert jeweils auf den Erhalt eines Pakets.
Der Hauptvorteil von Webflux liegt aber im geringeren Ressourcenverbrauch auf der Server Seite. Webflux basiert auf einem Event-Loop Mechanismus, während Spring MVC auf Thread Pools basiert. Der Performance Vorteil von Webflux macht sich insbesondere bei vielen gleichzeiten Service-Aufrufen und/oder der Übertragung größerer Datenmengen bei einem Service-Aufruf bemerkbar.
Trotzdem empfiehlt die IsyFact grundsätzlich, Spring MVC für die Umsetzung von REST-Services einzusetzen. Die Vorteile des reaktiven Programmierparadigmas erfüllen sich nur, wenn alle Teile der Umsetzung entsprechend gestaltet sind. Dies ist aktuell nicht der Fall.
Für REST-Clients wiederum empfiehlt die IsyFact die Verwendung des WebClient
anstatt des RestTemplate
, da letzteres nicht mehr aktiv weiterentwickelt wird.
Für die Anbindung von Angular-Clients enthält der Baustein Angular eine entsprechende Komponente.
4.3. Schnittstellendokumentation
Alle auf IsyFact basierten Anwendungen müssen ihre REST-Schnittstellen mit der OpenAPI 3.0 Spezifikation beschreiben. Sowohl YAML als auch JSON sind als Format der Schnittstellendokumentation zulässig. Für die Erstellung der Schnittstellendokumentation ist grundsätzlich kein besonderes Tool erforderlich, ein Texteditor ist ausreichend. Das OpenAPI Format wird jedoch von diversen Entwicklungsumgebungen (z. B. IntelliJ) unterstützt, was die Bearbeitung erleichtert.
4.3.1. Code-Generierung
Bei der Erstellung von REST-APIs gibt es grundsätzlich 2 Ansätze: Contract/API First oder Code First. Bei Contract/API First wird zunächst die Schnittstellenbeschreibung erstellt und daraus der Code (Server und Client) generiert. Bei Code First wird zuerst die API implementiert und mit Annotationen für die Schnittstellenbeschreibung versehen. Aus den Annotationen wird dann die Schnittstellenbeschreibung generiert.
Die IsyFact empfiehlt den Contract/API First Ansatz.
Für die Generierung des Codes von Server und Client sind die Standards von OpenApi 3.x zu beachten.
4.3.2. Bereitstellen von generierten Clients
Wie beschrieben können aus der OpenAPI Schnittstellenbeschreibung Services und Clients für diverse Plattformen generiert werden. Aus Convenience-Gründen wird empfohlen, dass Anbieter von Schnittstellen fertig generierte Clients für die gängigen Plattformen zur Verfügung stellen. Gängige Clients im Kontext der IsyFact sind Java (Spring) und Javascript (Angular).
4.4. Verwendung von Transportobjekten
Die DTOs werden ebenfalls innerhalb der Schnittstellenbeschreibung schematisch beschrieben.
Der Code für die DTOs wird daraus generiert.
Innerhalb der Schnittstellenbeschreibung des Service werden die Schemata der DTOs üblicherweise als Referenz mittels #ref
eingebunden.
Die Referenz kann sich dabei auf eine Beschreibung innerhalb derselben Datei beziehen oder auch auf eine externe Datei.
Die Datei kann dabei sowohl über einen Dateipfad als auch über eine URL adressiert werden.
Beispiel aus Swagger Petstore:
requestBody: content: application/json: schema: $ref: '#/components/schemas/Pet' ... components: schemas: Pet: name: type: string example: doggie
5. Querschnittliche Konzepte
5.1. Validierung von Anfragen
Wie bereits gesagt muss die formale Validierung der Daten vor der inhaltlichen Validierung im Anwendungskern stattfinden. Genau genommen müssen die Daten formal korrekt sein, bevor sie überhaupt in irgendeiner Weise weiter verarbeitet werden können. Daher ist die formale Prüfung der erste Schritt in der Verarbeitung von Daten und komplett unabhängig von der weiteren Verarbeitung.
Eine geeignete Methode zur Prüfung der Daten auf formale Korrektheit ist die Validierung gegen das vorhandene Schema der Schnittstellenbeschreibung. Atlassian bietet den Swagger Request Validator als freie Software an (Apache 2.0 Lizenz). Damit lassen sich HTTP Requests und Responses gegen eine OpenAPI bzw. Swagger Spezifikation prüfen, OpenAPI v3 wird dabei bereits unterstützt. Der Swagger Request Validator hat keine weiteren Abhängigkeiten und kann somit unabhängig von HTTP APIs oder Frameworks verwendet werden.
5.2. Fehlerbehandlung
In diesem Abschnitt werden alle Aspekte zur Fehlerbehandlung in REST-Services beschrieben.
Bei der Fehlerbehandlung müssen zwei Varianten unterschieden werden: technische Fehler und fachliche Fehler.
Definition von technischen Exceptions: Service-Methoden deklarieren keine oder eine technische Exception. Die technische Exception muss für alle Service-Methoden einer Service-Schnittstelle gleich sein.
Definition von fachlichen Exceptions: Service-Methoden können beliebig viele fachliche Exceptions deklarieren. Diese können spezifisch für jede Service-Methode sein.
Übermittlung von Daten: Die Felder Ausnahme-ID, UUID und Fehlernachricht, wie in den Nutzungsvorgaben Fehlerbehandlung beschrieben, müssen stets übertragen werden. Weiterhin darf kein Stack-Trace übertragen werden.
Grundsätzlich sind die Vorgaben aus den Nutzungsvorgaben Fehlerbehandlung zu beachten. Im Anschluss folgen REST-Spezifische Regeln.
Technische Fehler werden in der HTTP-Response immer mit dem HTTP-Code 500 gesendet.
Fachlichen Fehlern muss ein entsprechend passender Fehlercode im Bereich 4xx zugeordnet werden.
Einheitliche Fehlermeldungen beschreibt den Inhalt einer Fehlermeldung.
5.2.1. Entkopplung von Fehlerbehandlung und Service-Logik
Die Fehlerbehandlung erfolgt wie im Detailkonzept Service beschrieben, außerhalb der Service-Fassade, an zentraler Stelle, in einer Exception-Fassade.
Die Exception-Fassade wird durch RestExceptionHandler
Klassen implementiert, (vgl. Abbildung 2).
In der Exception-Fassade werden alle Exceptions aus Methoden des Anwendungskerns oder Methoden der Service-Endpoints gefangen, gelogged,
in Exception-Transport-Objekte umgewandelt und als Response zurückgegeben.
Spring Webflux unterstützt dazu ebenso wie Spring MVC die Annotation @RestControllerAdvice
.
Die Implementierung der RestExceptionHandler
-Funktionalität kann daher über die Annotation einer Klasse mit @RestControllerAdvice
erfolgen.
Details zur Implementierung können der offiziellen Spring Dokumentation entnommen werden. |
5.2.2. Einheitliche Fehlermeldungen
Eine Fehlernachricht aus einer REST-Schnittstelle (egal, ob Client oder Server Error) muss mindestens folgende Informationen beinhalten.
Feld | Datentyp | Beschreibung |
---|---|---|
|
String |
Exakter Zeitpunkt an dem der Fehler aufgetreten ist. |
|
Integer |
HTTP-Fehlercode |
|
String |
Kurzbeschreibung des Fehlers, z. B. Entity not found. |
|
String |
Detailbeschreibung des Fehlers. Bei Client Errors sollte beschrieben werden wie der Client den Fehler beheben kann. Es sollen keine technischen/internen Details (z. B. Stacktrace) in der Beschreibung enthalten sein. Die message setzt sich, wie in den Nutzungsvorgaben Fehlerbehandlung beschrieben, folgendermaßen zusammen:
|
|
String |
Pfad der Ressource, die in der Anfrage adressiert wurde. |
Fehler werden in einem Array zurückgegeben um auch das Senden mehrerer Fehler, beispielsweise bei der Validierung von Eingaben, zu ermöglichen.
[
{
"timestamp": "2020-08-23T14:53:33.452+02:00",
"status": 404,
"error": "Not Found",
"message": "#0001 Die Nachricht mit der ID=1 konnte nicht gefunden werden. #4321",
"path": "/ressource/1234"
}
]
Sollten bei der Verarbeitung mehrere Fehler auftreten, sind alle Fehler zurückzugeben.
Exceptions werden im RestExceptionHandler
auf den jeweiligen HTTP-Statuscode und die entsprechende Nachricht gemapped.
Welche Informationen enthalten sein dürfen wird im Nutzungsvorgaben Fehlerbehandlung genauer beschrieben.
5.3. Sicherheit
Die Authentifizierung wird nach OAuth2 sichergestellt.
Hierfür muss der Client mit jeder Nachricht einen Bearer Token mitsenden.
Dieser wird im Request-Header mit dem Tag "Authorization" versehen.
Im Sicherheitsbaustein wird das Token an den SecurityContextHolder
weitergegeben.
Dort werden die Daten ausgelesen und der Token wird auf Echtheit überprüft.
Bei einer reinen Weitergabe greift die Bearer Propagation.
Nähere Informationen zu OAuth2 gibt es auf der OAuth2 Homepage. Informationen zur Bearer Propagation gibt es in der offiziellen Spring Dokumentation. |
Um eine Klasse oder einzelne Methoden zu sichern, wird empfohlen, die @Secured
Annotation von Spring Security in der Service-Schicht zu verwenden.
Die Verwendung auf einzelnen Webcontroller Klassen/Methoden wird nicht empfohlen. Für Webcontroller ohne Service Schicht kann Request Level Autorisierung genutzt werden.
Generell ist das Konzept der Sicherheitsarchitektur zu beachten.
5.4. Logging
Eingehende und ausgehende Nachrichten sind an der Systemgrenze zu loggen. Für die Erstellung der Log-Einträge ist der Baustein Isy-Logging zu verwenden. Abweichungen zum Konzept von Isy-Logging sind im Folgenden aufgeführt.
5.4.1. Server / Service-Provider
Der Baustein bietet mit @Systemgrenze
eine Annotation, die einen Interceptor aufruft, der automatisch Aufrufe aller Methoden in der annotierten Klasse (z. B. Controller) fachlich loggt.
(vgl. Nutzungsvorgaben Logging Kapitel 4.2.2.1)
Für ein technisches Logging soll zusätzlich ein weiterer Interceptor verwendet werden.
Dieser Interceptor soll folgende Nachrichten am Controller loggen:
-
eingehende Anfragen (eingehende Nachrichten),
-
ausgehende Antworten (ausgehende Nachrichten).
5.4.2. Client / Service-Consumer
In der Klasse, welche die Aufrufe an den Service-Provider stellt, sollen
-
ausgehende Anfragen (ausgehende Nachrichten),
-
eingehende Antworten (eingehende Nachrichten),
geloggt werden.
Wie auch serverseitig, sollen diese Log-Nachrichten möglichst automatisiert via AOP erstellt werden.
5.4.3. Inhalt des Log-Eintrags für eine Nachricht
Um die Nachrichten im Zuge der Log-Auswertung zu filtern, sind nachrichtenspezifische Ereignisschlüssel zu verwenden.
Zusätzlich zu den Anforderungen an die Inhalte einer Log-Nachricht Konzept Logging Kapitel 4.1.1), sollen folgende Informationen als Marker in der Log-Nachricht enthalten sein:
Die zu protokollierende Information ist abhängig, ob es ein Request oder Response ist | |
---|---|
Ein-/Ausgehende Nachricht |
immer protokollieren |
Request-URL (Address + URI) |
immer protokollieren |
Query String |
immer protokollieren |
HTTP-Methode |
immer protokollieren |
Protocol |
immer protokollieren |
Connection- z. B. keep-alive, Transfer-Encoding |
immer protokollieren |
Encoding |
immer protokollieren |
Accept |
immer protokollieren |
Content-Type |
immer protokollieren |
Content-Length |
immer protokollieren |
Aufgerufene Methode der Klasse |
immer protokollieren (REST-Controller-Methode) |
Zeitpunkt |
bei ausgehend: Sende-Zeitpunkt |
Dauer der Verarbeitung |
bei eingehend: Dauer vom Empfang des Requests bis zum Versand der Antwort |
Aufgerufenes Nachbarsystem |
bei ausgehendenden Requests, sonst leer |
Konfigurationsparameter
Diese Konfigurationsparameter aus Kapitel 4.2.2.1 Nutzungsvorgaben Logging sollen für den neuen Interceptor ebenfalls existieren:
-
loggeDauer
-
loggeDaten
-
loggeDatenBeiException
-
loggeMaximaleParameterGroesse
Nachrichten Logging
Zum Test und Debuggen einer Anwendung soll es möglich sein, dass komplette Nachrichten in ihrem Rohformat inkl. dem kompletten Header geloggt werden. Das Aktivieren dieses Loggings ist über einen Konfigurationsparameter steuerbar.
5.5. Überwachung
Die IsyFact stellt zur Überwachung von Anwendungen den Baustein Überwachung bereit.
Die Grundlagen der Überwachung von IsyFact basierten Anwendungen werden im Konzept Überwachung beschrieben.
Detailliertere Informationen zur Konfiguration und zur Verwendung des Bausteins Überwachung findet man in den Nutzungsvorgaben Überwachung. Dieses Dokument beinhaltet auch das Kapitel "Informationen von Services". Dieses Kapitel beinhaltet eine Liste der zu überwachenden bzw. anzubietenden Informationen für die Services von Anwendungen. Die dort aufgelisteten Informationen müssen für jeden Service einzeln angeboten werden. Die dort beschriebenen Informationen zur Überwachung gelten in gleichem Maße auch für REST Services.
5.6. Versionierung
Versionierung kann auf verschiedene Weisen stattfinden.
Prinzipiell gilt es inkompatible API-Änderungen in einer REST-Schnittstelle zu vermeiden. Falls irgendwann eine inkompatible Änderung notwendig sein sollte, ist eine Versionierung in der URL zu verwenden.
Die Version steht immer vor dem Pfad der Ressource und beinhaltet ausschließlich die Major Version.
https://service.de/api/v1/messages/{id} https://service.de/api/v2/messages/{id}
Anhang A: Content Types
Die folgende Tabelle enthält alle Inhaltstypen, die vom Baustein REST der IsyFact vorgesehen und unterstützt sind.
Repräsentation | Content-Type | Accept |
---|---|---|
Textbasierte Repräsentationen |
||
JSON (ohne HATEOAS) |
|
|
JSON (mit HATEOAS) |
|
|
XML |
|
|
Binäre Repräsentationen |
||
|
|
|
JPG |
|
|
Anhang B: HTTP-Methoden
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 |