Kapitel 1. Entwerfen, Erstellen und Spezifizieren von APIs

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Beim Entwerfen und Erstellen von APIs stehen dir viele Optionen zur Verfügung. Mit modernen Technologien und Frameworks ist es unglaublich schnell, einen Dienst zu erstellen, aber die Entwicklung eines dauerhaften Ansatzes erfordert sorgfältige Überlegungen. In diesem Kapitel werden wir uns mit REST und RPC beschäftigen, um die Produzenten- und Konsumentenbeziehungen in der Fallstudie zu modellieren.

Du erfährst, wie Standards helfen können, Designentscheidungen abzukürzen und potenzielle Kompatibilitätsprobleme zu vermeiden. Du wirst dir die OpenAPI-Spezifikationen, die praktischen Einsatzmöglichkeiten für Teams und die Bedeutung der Versionierung ansehen.

RPC-basierte Interaktionen werden mithilfe eines Schemas spezifiziert; zum Vergleich mit einem REST-Ansatz werden wir gRPC untersuchen. Mit Blick auf REST und gRPC werden wir uns die verschiedenen Faktoren ansehen, die bei der Modellierung des Austauschs zu berücksichtigen sind. Wir werden uns die Möglichkeit ansehen, sowohl eine REST- als auch eine RPC-API im selben Dienst bereitzustellen, und ob dies die richtige Entscheidung ist.

Fallstudie: Die Gestaltung der Teilnehmer-API

In der Einleitung haben wir beschlossen, unser altes Konferenzsystem zu migrieren und zu einer API-gesteuerten Architektur überzugehen. Als ersten Schritt zu dieser Umstellung werden wir einen neuen Attendee-Dienst erstellen, der eine passende Attendee-API bereitstellen wird. Wir haben auch eine enge Definition einer API gegeben. Um ein effektives Design zu entwickeln, müssen wir den Austausch zwischen Produzent und Konsument umfassender betrachten, und noch wichtiger, wer Produzent und Konsument sind. Der Produzent gehört dem Attendee-Team. Dieses Team unterhält zwei wichtige Beziehungen:

  • Das Teilnehmerteam ist der Produzent und das alte Konferenzteam ist der Konsument. Es besteht eine enge Beziehung zwischen diesen beiden Teams und alle Änderungen in der Struktur lassen sich leicht koordinieren. Ein starker Zusammenhalt zwischen den Erzeuger-/Verbraucherdiensten ist möglich.

  • Das Teilnehmerteam ist der Produzent und das Team des externen CFP-Systems ist der Konsument. Es besteht eine Beziehung zwischen den Teams, aber alle Änderungen müssen koordiniert werden, um die Integration nicht zu unterbrechen. Eine lose Kopplung ist erforderlich und Änderungen, die die Integration unterbrechen, müssen sorgfältig verwaltet werden.

In diesem Kapitel werden wir die Ansätze zur Entwicklung und Erstellung der Teilnehmer-API vergleichen und gegenüberstellen.

Einführung in REST

REpresentation State Transfer (REST) ist eine Reihe von architektonischen Einschränkungen, die meist mit HTTP als zugrunde liegendem Transportprotokoll angewendet werden. Roy Fieldings Dissertation "Architectural Styles and the Design of Network-based Software Architectures" liefert eine vollständige Definition von REST. Aus praktischer Sicht muss deine API, um als RESTful zu gelten, Folgendes gewährleisten:

  • Es wird eine Interaktion zwischen Produzent und Konsument modelliert, bei der der Produzent Ressourcen modelliert, mit denen der Konsument interagieren kann.

  • Anfragen vom Produzenten an den Konsumenten sind zustandslos, d.h. der Produzent speichert keine Details über eine vorherige Anfrage. Um eine Kette von Anfragen an eine bestimmte Ressource zu erstellen, muss der Verbraucher alle erforderlichen Informationen zur Bearbeitung an den Produzenten senden.

  • Anfragen sind cachbar, d.h. der Produzent kann dem Konsumenten Hinweise geben, wenn dies sinnvoll ist. Bei HTTP werden diese Hinweise oft in den Headern angegeben.

  • Dem Verbraucher wird eine einheitliche Schnittstelle vermittelt. Du wirst in Kürze die Verwendung von Verben, Ressourcen und anderen Mustern untersuchen.

  • Es ist ein mehrschichtiges System, das die Komplexität der Systeme hinter der REST-Schnittstelle abstrahiert. Der Verbraucher sollte zum Beispiel nicht wissen oder sich darum kümmern, ob er mit einer Datenbank oder anderen Diensten interagiert.

Einführung in REST und HTTP am Beispiel

Sehen wir uns unter ein Beispiel für REST über HTTP an. Der folgende Austausch ist eine GET-Anfrage, wobei GET für die Methode oder das Verb steht. Ein Verb wie GET beschreibt die Aktion, die mit einer bestimmten Ressource durchgeführt werden soll; in diesem Beispiel betrachten wir die Ressource attendees. Ein Accept-Header wird übergeben, um die Art des Inhalts zu definieren, den der Verbraucher abrufen möchte. REST definiert den Begriff einer Repräsentation im Body und ermöglicht die Definition von Repräsentationsmetadaten in den Headern.

In den Beispielen in diesem Kapitel stellen wir eine Anfrage oberhalb des Trennzeichens --- und eine Antwort darunter dar:

GET http://mastering-api.com/attendees
Accept: application/json
---
200 OK
Content-Type: application/json
{
    "displayName": "Jim",
    "id": 1
}

Die Antwort enthält den Statuscode und die Nachricht des Servers, die es dem Verbraucher ermöglicht, das Ergebnis der Operation an der serverseitigen Ressource abzufragen. Der Statuscode dieser Anfrage war 200 OK, was bedeutet, dass die Anfrage vom Produzenten erfolgreich verarbeitet wurde. Im Antwortkörper wird eine JSON-Darstellung mit den Konferenzteilnehmern zurückgegeben. Viele Inhaltstypen können von einer REST-Anfrage zurückgegeben werden, aber es ist wichtig, zu prüfen, ob der Inhaltstyp vom Verbraucher geparst werden kann. application/pdf ist zum Beispiel ein gültiger Inhaltstyp, aber er würde keinen Austausch darstellen, der leicht von einem anderen System verwendet werden könnte. Wir werden uns später in diesem Kapitel mit Ansätzen zur Modellierung von Inhaltstypen befassen und dabei hauptsächlich JSON betrachten.

Hinweis

REST ist relativ einfach zu implementieren, da die Beziehung zwischen Client und Server zustandslos ist, d.h. der Server speichert keinen Client-Status. Der Client muss den Kontext in nachfolgenden Anfragen an den Server zurückgeben; eine Anfrage anhttp://mastering-api.com/attendees/1 würde beispielsweise weitere Informationen über einen bestimmten Teilnehmer abrufen.

Das Richardson Reifegradmodell

In seinem Vortrag auf der QCon 2008 stellte Leonard Richardson seine Erfahrungen bei der Überprüfung vieler REST-APIs vor. Richardson fand Stufen der Akzeptanz, die Teams bei der Entwicklung von APIs aus der REST-Perspektive anwenden können. Martin Fowler berichtete in seinem Blog auch über Richardsons Reifegradheuristiken. Tabelle 1-1 zeigt die verschiedenen Stufen der Richardson'schen Reifeheuristik und ihre Anwendung auf RESTful-APIs.

Tabelle 1-1. Richardson Reifegradheuristiken

Stufe 0 - HTTP/RPC

Legt fest, dass die API mit HTTP aufgebaut ist und den Begriff einer einzelnen URI hat. Wenn wir unser vorheriges Beispiel von /attendees aufgreifen und kein Verb verwenden, um die Absicht zu spezifizieren, würden wir einen Endpunkt für den Austausch öffnen. Dies stellt im Wesentlichen eine RPC-Implementierung über das REST-Protokoll dar.

Ebene 1 - Ressourcen

Legt die Verwendung von Ressourcen fest und bringt die Idee der Modellierung von Ressourcen im Kontext der URI ein. Wenn wir in unserem Beispiel GET /attendees/1 hinzufügen, das einen bestimmten Teilnehmer zurückgibt, würde es anfangen, wie eine API der Stufe 1 auszusehen. Martin Fowler zieht eine Analogie zur klassischen objektorientierten Welt, indem er die Identität einführt.

Stufe 2 - Verben (Methoden)

Beginnt mit der Einführung der korrekten Modellierung mehrerer Ressourcen-URIs, auf die mit verschiedenen Anfragemethoden (auch als HTTP-Verben bekannt) zugegriffen wird, basierend auf den Auswirkungen der Ressourcen auf den Server. Eine API auf Level 2 kann Garantien dafür geben, dass die Methoden von GET keine Auswirkungen auf den Serverstatus haben und mehrere Operationen für dieselbe Ressourcen-URI darstellen. In unserem Beispiel, in dem wir DELETE /attendees/1 hinzufügen, würde PUT /attendees/1 beginnen, den Begriff einer Level 2-konformen API hinzuzufügen.

Stufe 3 - Hypermedia-Kontrollen

Dies ist der Inbegriff des REST-Designs und beinhaltet navigierbare APIs durch die Verwendung von HATEOAS (Hypertext As The Engine Of Application State). Wenn wir in unserem Beispiel GET /attendees/1 aufrufen, würde die Antwort die Aktionen enthalten, die mit dem vom Server zurückgegebenen Objekt möglich sind. Dies würde die Option beinhalten, den Teilnehmer zu aktualisieren oder zu löschen und was der Client dazu aufrufen muss. In der Praxis wird Level 3 in modernen RESTful-HTTP-Diensten nur selten verwendet, und obwohl die Navigation in Systemen mit flexibler Benutzeroberfläche von Vorteil ist, eignet sie sich nicht für API-Aufrufe zwischen den Diensten. Die Verwendung von HATEOAS wäre eine Plauderei und wird oft dadurch abgekürzt, dass bei der Programmierung gegen den Produzenten im Voraus eine vollständige Spezifikation der möglichen Interaktionen vorliegt.

Bei der Gestaltung des API-Austauschs sind die verschiedenen Stufen der Richardson Maturity zu berücksichtigen. Wenn du dich auf Stufe 2 begibst, kannst du dem Verbraucher ein verständliches Ressourcenmodell projizieren, für das geeignete Aktionen zur Verfügung stehen. Dies wiederum reduziert die Kopplung und verbirgt das gesamte Detail des zugrunde liegenden Dienstes. Später werden wir auch sehen, wie diese Abstraktion auf die Versionierung angewendet wird.

Wenn der Verbraucher das CFP-Team ist, wäre die Modellierung eines Austauschs mit geringer Kopplung und die Projektion eines RESTful-Modells ein guter Ausgangspunkt. Wenn der Verbraucher das Legacy-Konferenzteam ist, können wir uns immer noch für eine RESTful-API entscheiden, aber es gibt auch eine andere Option mit RPC. Um diese Art von traditioneller Ost-West-Modellierung in Betracht zu ziehen, werden wir RPC untersuchen.

Einführung in Remote Procedure Call (RPC) APIs

Remote Procedure Call (RPC) bedeutet, dass eine Methode in einem Prozess aufgerufen wird, der Code aber in einem anderen Prozess ausgeführt wird. Während REST ein Modell der Domäne projizieren kann und dem Verbraucher eine Abstraktion von der zugrunde liegenden Technologie bietet, bedeutet RPC, dass eine Methode von einem Prozess offengelegt wird und direkt von einem anderen Prozess aufgerufen werden kann.

gRPC ist ein modernes Open-Source-Hochleistungs-RPC. gRPC wird von der Linux Foundation verwaltet und ist der De-facto-Standard für RPC auf den meisten Plattformen.Abbildung 1-1 beschreibt einen RPC-Aufruf in gRPC, bei dem der alte Konferenzdienst die Remote-Methode des Attendee-Dienstes aufruft. Der gRPC-Dienst Attendee startet und stellt einen gRPC-Server auf einem bestimmten Port zur Verfügung, über den Methoden aus der Ferne aufgerufen werden können. Auf der Client-Seite (dem alten Konferenzdienst) wird ein Stub verwendet, um die Komplexität des Fernaufrufs in die Bibliothek zu abstrahieren. gRPC erfordert ein Schema, um die Interaktion zwischen Producer und Consumer vollständig abzudecken.

maar 0101
Abbildung 1-1. Beispiel C4-Komponentendiagramm mit gRPC

Ein wesentlicher Unterschied zwischen REST und RPC ist der Zustand. REST ist per Definition zustandslos - bei RPC hängt der Zustand von der Implementierung ab. RPC-basierte Integrationen können in bestimmten Situationen auch einen Zustand als Teil des Austauschs aufbauen. Dieser Zustandsaufbau hat den Vorteil einer hohen Leistung auf Kosten der Zuverlässigkeit und der Routing-Komplexität. Bei RPC neigt das Modell dazu, genau die Funktionalität auf Methodenebene zu übermitteln, die von einem sekundären Dienst benötigt wird. Diese Optionalität des Zustands kann zu einem Austausch führen, der potenziell stärker zwischen Produzent und Konsument gekoppelt ist. Kopplung ist nicht immer etwas Schlechtes, insbesondere bei Ost-West-Diensten, bei denen die Leistung eine wichtige Rolle spielt.

Eine kurze Erwähnung von GraphQL

Bevor wir REST- und RPC-Stile im Detail untersuchen, wäre es nachlässig, GraphQL und seinen Platz in der API-Welt nicht zu erwähnen. RPC bietet Zugang zu einer Reihe einzelner Funktionen, die von einem Produzenten zur Verfügung gestellt werden, erweitert aber in der Regel kein Modell oder eine Abstraktion für den Konsumenten. REST hingegen erweitert ein Ressourcenmodell für eine einzelne API, die vom Produzenten zur Verfügung gestellt wird. Es ist möglich, mit Hilfe von API-Gateways mehrere APIs unter derselben Basis-URL anzubieten. Wir werden dieses Konzept in Kapitel 3 näher untersuchen. Wenn wir auf diese Weise mehrere APIs anbieten, muss der Verbraucher eine Abfrage nach der anderen stellen, um den Status auf der Client-Seite aufzubauen. Außerdem muss der Verbraucher die Struktur aller an der Abfrage beteiligten Dienste verstehen. Dieser Ansatz ist verschwenderisch, wenn der Verbraucher nur an einer Teilmenge der Felder in der Antwort interessiert ist. Mobile Geräte sind durch kleinere Bildschirme und die Netzverfügbarkeit eingeschränkt, daher eignet sich GraphQL hervorragend für dieses Szenario.

GraphQL führt eine Technologieschicht über bestehende Dienste, Datenspeicher und APIs ein, die eine Abfragesprache zur Verfügung stellt, mit der mehrere Quellen abgefragt werden können. Die Abfragesprache ermöglicht es dem Kunden, genau die Felder abzufragen, die er benötigt, einschließlich der Felder, die sich über mehrere APIs erstrecken. GraphQL verwendet die GraphQL-Schemasprache, um die Typen in den einzelnen APIs und die Kombination von APIs zu spezifizieren. Ein großer Vorteil der Einführung eines GraphQL-Schemas in deinem System ist die Möglichkeit, eine einzige Version für alle APIs bereitzustellen, wodurch die Notwendigkeit einer potenziell komplexen Versionsverwaltung auf der Verbraucherseite entfällt.

GraphQL eignet sich hervorragend, wenn ein Kunde einen einheitlichen API-Zugang über eine Vielzahl miteinander verbundener Dienste benötigt. Das Schema stellt die Verbindung her und erweitert das Domänenmodell, so dass der Kunde genau festlegen kann, was auf der Kundenseite benötigt wird. Dies eignet sich hervorragend für die Modellierung einer Benutzeroberfläche und auch für Reportingsysteme oder Systeme im Data-Warehousing-Stil. In Systemen, in denen große Datenmengen in verschiedenen Subsystemen gespeichert werden, kann GraphQL eine ideale Lösung sein, um die interne Systemkomplexität zu abstrahieren.

Es ist möglich, GraphQL über bestehende Altsysteme zu legen und dies als Fassade zu nutzen, um die Komplexität zu verbergen, obwohl die Bereitstellung von GraphQL über einer Schicht gut gestalteter APIs oft bedeutet, dass die Fassade einfacher zu implementieren und zu warten ist. GraphQL kann als ergänzende Technologie betrachtet werden und sollte bei der Gestaltung und Erstellung von APIs in Betracht gezogen werden. GraphQL kann auch als kompletter Ansatz zum Aufbau eines gesamten API-Ökosystems betrachtet werden.

GraphQL kann in bestimmten Szenarien glänzen und wir empfehlen dir, einen Blick auf Learning GraphQL (O'Reilly) und GraphQL in Action (O'Reilly) zu werfen, um tiefer in dieses Thema einzutauchen.

REST API Standards und Struktur

REST hat einige sehr grundlegende Regeln, aber die Umsetzung und Gestaltung bleibt größtenteils den Entwicklern überlassen. Wie werden zum Beispiel Fehler am besten übermittelt? Wie sollte die Paginierung implementiert werden? Wie vermeidet man, dass eine API entsteht, die häufig nicht kompatibel ist? An diesem Punkt ist es sinnvoll, eine praktischere Definition für APIs zu haben, um für Einheitlichkeit und Erwartungen bei verschiedenen Implementierungen zu sorgen. Hier können Standards oder Richtlinien helfen, aber es gibt eine Vielzahl von Quellen, aus denen man wählen kann.

Für die Diskussion über das Design werden wir die Microsoft REST API Guidelines verwenden, die eine Reihe interner Richtlinien darstellen, die als Open Source veröffentlicht wurden. Die Richtlinien verwenden RFC-2119, der die Terminologie für Standards wie MUST, SHOULD, SHOULD NOT, MUST NOT usw. definiert und es dem Entwickler ermöglicht, zu bestimmen, ob die Anforderungen optional oder obligatorisch sind.

Tipp

Da sich die REST-API-Standards weiterentwickeln, ist auf der Github-Seite des Buches eine offene Liste von API-Standards verfügbar. Bitte trage per Pull-Request alle offenen Standards bei, von denen du denkst, dass sie für andere Leser nützlich sind.

Betrachten wir das Design von der Attendee API unter Verwendung der Microsoft REST API-Richtlinien und führen einen Endpunkt ein, um ein neues attendee zu erstellen. Wenn du mit REST vertraut bist, wirst du sofort daran denken, POST zu verwenden:

POST http://mastering-api.com/attendees
{
    "displayName": "Jim",
    "givenName": "James",
    "surname": "Gough",
    "email": "jim@mastering-api.com"
}
---
201 CREATED
Location: http://mastering-api.com/attendees/1

Der Location-Header gibt den Standort der neu erstellten Ressource auf dem Server an, und in dieser API modellieren wir eine eindeutige ID für den Nutzer. Es ist möglich, das E-Mail-Feld als eindeutige ID zu verwenden, aber die Microsoft REST API Guidelines empfehlen in Abschnitt 7.9, dass personenbezogene Daten (PII) nicht Teil der URL sein sollten.

Warnung

Der Grund für das Entfernen sensibler Daten aus der URL ist, dass Pfade oder Abfrageparameter versehentlich im Netzwerk zwischengespeichert werden könnten - zum Beispiel in Serverprotokollen oder anderswo.

Ein weiterer Aspekt von APIs, der schwierig sein kann, ist die Namensgebung. Wie wir im Abschnitt "API-Versionierung" erörtern werden , kann schon eine einfache Namensänderung die Kompatibilität beeinträchtigen. In den Microsoft REST-API-Richtlinien gibt es eine kurze Liste von Standardnamen, die verwendet werden sollten, aber die Teams sollten diese Liste erweitern, um ein gemeinsames Datenwörterbuch zu erstellen, das die Standards ergänzt. In vielen Unternehmen ist es unglaublich hilfreich, die Anforderungen an das Datendesign und in manchen Fällen auch an die Governance proaktiv zu untersuchen. Organisationen, die für Konsistenz über alle APIs eines Unternehmens hinweg sorgen, bieten eine Einheitlichkeit, die es den Verbrauchern ermöglicht, Antworten zu verstehen und zu verknüpfen. In einigen Bereichen gibt es vielleicht bereits eine weithin bekannte Terminologie - verwende sie !

Sammlungen und Paginierung

Es scheint sinnvoll, die GET /attendees Anfrage als Antwort zu modellieren, die ein rohes Array enthält. Der folgende Quelltextausschnitt zeigt ein Beispiel dafür, wie ein solcher Antwortkörper aussehen könnte:

GET http://mastering-api.com/attendees
---
200 OK
[
    {
        "displayName": "Jim",
        "givenName": "James",
        "surname": "Gough",
        "email": "jim@mastering-api.com",
        "id": 1,
    },
    ...
]

Betrachten wir ein alternatives Modell für die GET /attendees Anfrage, bei dem das Array mit den Teilnehmern in einem Objekt verschachtelt wird. Es mag seltsam erscheinen, dass eine Array-Antwort in einem Objekt zurückgegeben wird, aber der Grund dafür ist, dass wir damit größere Sammlungen und Paginierung modellieren können. Bei der Paginierung wird ein Teilergebnis zurückgegeben, während gleichzeitig Anweisungen gegeben werden, wie der Verbraucher den nächsten Satz von Ergebnissen anfordern kann. Dies ist ein Vorteil im Nachhinein; eine spätere Paginierung und die Umwandlung von einem Array in ein Objekt, um eine @nextLink hinzuzufügen (wie von den Standards empfohlen), würde die Kompatibilität brechen:

GET http://mastering-api.com/attendees
---
200 OK
{
    "value": [
        {
            "displayName": "Jim",
            "givenName": "James",
            "surname": "Gough",
            "email": "jim@mastering-api.com",
            "id": 1,
        }
    ],
    "@nextLink": "{opaqueUrl}"
}

Sammlungen filtern

Unsere Konferenz sieht mit nur einem Teilnehmer etwas einsam aus, aber wenn die Sammlungen größer werden, müssen wir vielleicht zusätzlich zur Paginierung eine Filterung hinzufügen. Der Filterungsstandard bietet eine Ausdruckssprache innerhalb von REST, um zu standardisieren, wie sich Filterabfragen verhalten sollen, die auf dem OData-Standard basieren. Zum Beispiel könnten wir alle Teilnehmer mit dem displayName Jim mit:

GET http://mastering-api.com/attendees?$filter=displayName eq 'Jim'

Es ist nicht notwendig, alle Filter- und Suchfunktionen von Anfang an zu vervollständigen. Die Entwicklung einer API im Einklang mit den Standards ermöglicht es dem Entwickler jedoch, eine sich entwickelnde API-Architektur zu unterstützen, ohne die Kompatibilität für die Verbraucher zu unterbrechen. Filtern und Abfragen ist eine Funktion, die GraphQL wirklich gut kann, vor allem, wenn das Abfragen und Filtern über viele deiner Dienste relevant wird.

Fehlerbehandlung

Eine wichtige Überlegung bei der Ausweitung von APIs auf Verbraucher ist die Festlegung, was in verschiedenen Fehlerszenarien passieren soll. Es ist sinnvoll,Fehlerstandards im Voraus zu definieren und sie mit den Produzenten zu teilen, um Konsistenz zu gewährleisten. Es ist wichtig, dass Fehler dem Verbraucher genau beschreiben, was bei der Anfrage schief gelaufen ist, da dies den Supportaufwand für die API nicht erhöht.

In den Richtlinien heißt es : "Für Nicht-Erfolgsbedingungen SOLLTEN Entwickler in der Lage sein, einen Code zu schreiben, der Fehler konsistent behandelt."Der Verbraucher muss einen genauen Statuscode erhalten, denn oft bauen Verbraucher eine Logik um den in der Antwort angegebenen Statuscode herum auf. Wir haben viele APIs gesehen, die Fehler im Body zusammen mit einer 2xx-Antwort zurückgeben, die als Erfolgsmeldung verwendet wird. 3xx-Statuscodes für Weiterleitungen werden von einigen verbrauchenden Bibliotheksimplementierungen aktiv befolgt und ermöglichen es Anbietern, den Standort zu wechseln und auf externe Quellen zuzugreifen. 4xx weist in der Regel auf einen clientseitigen Fehler hin; an dieser Stelle ist der Inhalt des message Feldes für den Entwickler oder den Endnutzer äußerst nützlich. 5xx weist in der Regel auf einen Fehler auf der Serverseite hin und einige Client-Bibliotheken versuchen es bei dieser Art von Fehlern erneut. Es ist wichtig zu überlegen und zu dokumentieren, was bei einem unerwarteten Fehler im Dienst passiert - bedeutet z. B. eine 500 in einem Zahlungssystem, dass die Zahlung erfolgt ist oder nicht?

Warnung

Achte darauf, dass die Fehlermeldungen, die an einen externen Verbraucher zurückgeschickt werden, keine Stack Traces und andere sensible Informationen enthalten. Diese Informationen können einem Hacker helfen, das System zu kompromittieren. Die Fehlerstruktur in den Microsoft-Richtlinien hat das Konzept eines InnerError, in dem detailliertere Stack Traces/Beschreibungen von Problemen untergebracht werden können. Das wäre für die Fehlersuche unglaublich hilfreich, muss aber vor einem externen Verbraucher entfernt werden.

Wir haben gerade erst an der Oberfläche des Aufbaus von REST-APIs gekratzt, aber es ist klar, dass es viele wichtige Entscheidungen zu treffen gilt, wenn wir mit dem Aufbau einer API beginnen. Wenn wir den Wunsch nach intuitiven und konsistenten APIs mit dem Wunsch nach einer sich weiterentwickelnden und kompatiblen API verbinden, lohnt es sich, frühzeitig einen API-Standard einzuführen.

ADR-Leitfaden: Auswahl eines API-Standards

Um deine Entscheidung über API-Standards zu treffen, listet der Leitfaden in Tabelle 1-2 wichtige Themen auf, die es zu beachten gilt. Es gibt eine Reihe von Richtlinien, aus denen du wählen kannst, einschließlich der in diesem Abschnitt besprochenen Microsoft-Richtlinien, und es ist eine wichtige Entscheidung, eine zu finden, die am besten zu den Arten von APIs passt, die produziert werden.

Tabelle 1-2. API-Normen Leitfaden

Entscheidung

Welchen API-Standard sollten wir übernehmen?

Diskussionspunkte

Hat die Organisation bereits andere Standards innerhalb des Unternehmens? Können wir diese Standards auf externe Kunden ausweiten?

Verwenden wir APIs von Drittanbietern, die wir einem Verbraucher zur Verfügung stellen müssen (z. B. Identitätsdienste) und die bereits einen Standard haben?

Wie wirkt sich das Fehlen einer Norm auf unsere Verbraucher aus?

Empfehlungen

Wähle einen API-Standard, der am besten zur Kultur der Organisation und zu den Formaten der APIs passt, die du vielleicht schon im Bestand hast.

Sei darauf vorbereitet, einen Standard weiterzuentwickeln und um bereichs- oder branchenspezifische Änderungen zu ergänzen.

Fang früh mit etwas an, um zu vermeiden, dass du später die Kompatibilität aufgeben musst, um beständig zu sein.

Sei kritisch gegenüber bestehenden APIs. Sind sie in einem Format, das die Verbraucher verstehen würden, oder ist mehr Aufwand nötig, um den Inhalt anzubieten?

REST-APIs mit OpenAPI spezifizieren

Wie wir sehen, ist das Design einer API von grundlegender Bedeutung für den Erfolg einer API-Plattform. Der nächste Punkt, den wir besprechen werden, ist die gemeinsame Nutzung der API mit den Entwicklern, die unsere APIs nutzen.

API-Marktplätze bieten eine öffentliche oder private Auflistung von APIs, die einem Verbraucher zur Verfügung stehen. Ein Entwickler kann die Dokumentation durchsuchen und eine API schnell im Browser ausprobieren, um das Verhalten und die Funktionalität der API zu erkunden. Öffentliche und private API-Marktplätze haben REST-APIs in den Verbraucherbereich gebracht. Der Erfolg von REST-APIs wurde sowohl durch die technische Landschaft als auch durch die niedrige Einstiegshürde sowohl für den Client als auch für den Server gefördert.

Als die Zahl der APIs wuchs, wurde es schnell notwendig, einen Mechanismus zu haben, um die Form und Struktur der APIs mit den Verbrauchern zu teilen. Deshalb wurde die OpenAPI-Initiative von führenden Vertretern der API-Branche gegründet, um die OpenAPI-Spezifikation (OAS) zu erstellen. Swagger war die ursprüngliche Referenzimplementierung der OpenAPI-Spezifikationen, aber die meisten Tools haben sich inzwischen auf die Verwendung von OpenAPI geeinigt.

Die OpenAPI-Spezifikationen sind JSON- oder YAML-basierte Darstellungen der API, die die Struktur, die ausgetauschten Domänenobjekte und alle Sicherheitsanforderungen der API beschreiben. Neben der Struktur enthalten sie auch Metadaten über die API, einschließlich rechtlicher oder lizenzrechtlicher Anforderungen, sowie Dokumentation und Beispiele, die für Entwickler, die die API nutzen, nützlich sind. OpenAPI-Spezifikationen sind ein wichtiges Konzept im Zusammenhang mit modernen REST-APIs, und viele Tools und Produkte wurden rund um ihre Verwendung entwickelt.

Praktische Anwendung der OpenAPI-Spezifikationen

Sobald eine OAS gemeinsam genutzt wird, wird die Leistungsfähigkeit der Spezifikation deutlich.OpenAPI.Tools dokumentiert eine ganze Reihe verfügbarer Open- und Closed-Source-Tools. In diesem Abschnitt werden wir einige der praktischen Anwendungen von Tools auf der Grundlage ihrer Interaktion mit der OpenAPI-Spezifikation untersuchen.

In der Situation, in der das GFP-Team der Verbraucher ist, ermöglicht die gemeinsame Nutzung der OAS dem Team, die Struktur der API zu verstehen. Einige der folgenden praktischen Anwendungen können dazu beitragen, sowohl die Erfahrung der Entwickler zu verbessern als auch die Gesundheit des Austauschs zu gewährleisten.

Code-Erzeugung

Vielleicht ist eine der nützlichsten Funktionen einer OAS die Generierung von clientseitigem Code, um die API zu nutzen. Wie bereits erwähnt, können wir die vollständigen Details des Servers, der Sicherheit und natürlich der API-Struktur selbst einbeziehen. Mit all diesen Informationen können wir eine Reihe von Modell- und Service-Objekten generieren, die die API darstellen und aufrufen. Das OpenAPI Generator-Projekt unterstützt eine Vielzahl von Sprachen und Toolchains. In Java kannst du zum Beispiel Spring oder JAX-RS verwenden und in TypeScript kannst du eine Kombination aus TypeScript und deinem Lieblingsframework wählen. Es ist auch möglich, die API-Implementierungs-Stubs aus der OAS zu erzeugen.

Dies wirft die wichtige Frage auf, was zuerst kommen sollte - die Spezifikation oder der serverseitige Code? In Kapitel 2 besprechen wir das "Contract Tracing", einen verhaltensorientierten Ansatz zum Testen und Erstellen von APIs. Die Herausforderung bei OpenAPI-Spezifikationen besteht darin, dass sie nur die Form der API vermitteln. OpenAPI-Spezifikationen modellieren nicht vollständig die Semantik (oder das erwartete Verhalten) der API unter verschiedenen Bedingungen. Wenn du eine API externen Nutzern präsentieren willst, ist es wichtig, dass die Bandbreite des Verhaltens modelliert und getestet wird, um zu vermeiden, dass du die API später drastisch ändern musst.

APIs sollten aus der Perspektive des Verbrauchers entwickelt werden und die Anforderung berücksichtigen, die zugrundeliegende Repräsentation zu abstrahieren, um die Kopplung zu reduzieren. Es ist wichtig, dass die Komponenten hinter den Kulissen frei refaktorisiert werden können, ohne die API-Kompatibilität zu verletzen, sonst verliert die API-Abstraktion an Wert.

OpenAPI-Validierung

OpenAPI-Spezifikationen sind nützlich, um den Inhalt eines Austauschs zu validieren und sicherzustellen, dass die Anfrage und die Antwort mit den Erwartungen der Spezifikation übereinstimmen. Auf den ersten Blick mag es nicht einleuchtend erscheinen, wo dies nützlich sein könnte - wenn Code generiert wird, wird der Austausch sicherlich immer richtig sein? Eine praktische Anwendung der OpenAPI-Validierung ist die Sicherung von APIs und der API-Infrastruktur. In vielen Unternehmen ist eine zonale Architektur üblich, bei der eine demilitarisierte Zone (DMZ) verwendet wird, um ein Netzwerk vor eingehendem Datenverkehr zu schützen. Eine nützliche Funktion ist die Abfrage von Nachrichten in der DMZ und die Beendigung des Datenverkehrs, wenn die Spezifikation nicht übereinstimmt. Wir werden die Sicherheit in Kapitel 6 ausführlicher behandeln.

Atlassian zum Beispiel hat ein Tool namens swagger-request-validator veröffentlicht, das JSON REST-Inhalte validieren kann. Das Projekt verfügt auch über Adapter, die in verschiedene Mocking- und Test-Frameworks integriert werden können, um sicherzustellen, dass die API-Spezifikationen im Rahmen von Tests eingehalten werden. Das Tool verfügt über eine OpenApiInteractionValidator, mit der eine ValidationReport auf einem Exchange erstellt wird. Der folgende Code zeigt, wie ein Validator aus der Spezifikation erstellt wird, einschließlich aller basePathOverrides- die notwendig sein können, wenn eine API hinter einer Infrastruktur eingesetzt wird, die den Pfad ändert. Der Validierungsbericht wird aus der Analyse der Anfrage und der Antwort an dem Punkt erstellt, an dem die Validierung ausgeführt wird:

//Using the location of the specification create an interaction validator
//The base path override is useful if the validator will be used
//behind a gateway/proxy
final OpenApiInteractionValidator validator = OpenApiInteractionValidator
        .createForSpecificationUrl(specUrl)
        .withBasePathOverride(basePathOverride)
        .build;

//Requests and Response objects can be converted or created using a builder
final ValidationReport report = validator.validate(request, response);

if (report.hasErrors()) {
    // Capture or process error information
}

Beispiele und Spott

Die OAS kann Beispielantworten für die Pfade in der Spezifikation bereitstellen. Beispiele sind, wie bereits erwähnt, nützlich für die Dokumentation, um Entwicklern das erwartete API-Verhalten zu verdeutlichen. Einige Produkte haben damit begonnen, Beispiele zu verwenden, um dem Nutzer die Möglichkeit zu geben, die API abzufragen und Beispielantworten von einem Mock-Service zurückzugeben. Das kann z. B. in einem Entwicklerportal sehr nützlich sein, in dem Entwickler die Dokumentation durchsuchen und APIs aufrufen können. Eine weitere nützliche Funktion von Mocks und Beispielen ist die Möglichkeit, Ideen zwischen Produzent und Konsument auszutauschen, bevor der Dienst gebaut wird. Die Möglichkeit, die API "auszuprobieren", ist oft wertvoller als der Versuch, zu prüfen, ob eine Spezifikation deinen Anforderungen entspricht.

Beispiele können ein interessantes Problem aufwerfen, nämlich dass dieser Teil der Spezifikation im Wesentlichen ein String ist (um XML/JSON usw. zu modellieren).openapi-examples-validator validiert, dass ein Beispiel mit der OAS für die entsprechende Anfrage/Antwort übereinstimmt component der API.

Veränderungen aufspüren

OpenAPI Spezifikationen können auch hilfreich sein, um Änderungen an einer API zu erkennen. Dies kann als Teil einer DevOps-Pipeline unglaublich nützlich sein. Änderungen für die Abwärtskompatibilität zu erkennen, ist sehr wichtig, aber zuerst müssen wir die Versionierung von APIs genauer verstehen.

API-Versionierung

Wir haben die Vorteile der gemeinsamen Nutzung eines OAS mit einem Verbraucher untersucht, einschließlich der Geschwindigkeit der Integration. Was passiert, wenn mehrere Verbraucher mit der API arbeiten? Was passiert, wenn es eine Änderung an der API gibt oder einer der Verbraucher neue Funktionen für die API anfordert?

Gehen wir einen Schritt zurück und stellen wir uns vor, es handelte sich um eine Code-Bibliothek, die zur Kompilierungszeit in unsere Anwendung eingebaut wird. Jede Änderung an der Bibliothek würde als neue Version verpackt werden und bis der Code neu kompiliert und mit der neuen Version getestet wurde, gäbe es keine Auswirkungen auf die Produktionsanwendungen. Da es sich bei APIs um laufende Dienste handelt, haben wir einige Upgrade-Optionen, die uns sofort zur Verfügung stehen, wenn Änderungen erforderlich sind:

Gib eine neue Version heraus und verteile sie an einem neuen Ort.

Ältere Anwendungen arbeiten weiterhin mit der älteren Version der APIs. Aus Sicht der Verbraucher ist das in Ordnung, da sie nur dann auf den neuen Standort und die neue API aktualisieren, wenn sie die neuen Funktionen benötigen. Der Eigentümer der API muss jedoch mehrere Versionen der API pflegen und verwalten, einschließlich aller notwendigen Patches und Fehlerbehebungen.

Gib eine neue Version der API heraus, die mit der vorherigen Version der API abwärtskompatibel ist.

Dies ermöglicht additive Änderungen, ohne die bestehenden Nutzer der API zu beeinträchtigen. Für den Verbraucher sind keine Änderungen erforderlich, aber wir müssen möglicherweise die Ausfallzeiten oder die Verfügbarkeit der alten und der neuen Version während des Upgrades berücksichtigen. Wenn es eine kleine Fehlerbehebung gibt, die etwas so Kleines wie einen falschen Feldnamen ändert, würde dies die Kompatibilität brechen.

Die Kompatibilität mit der vorherigen API wird aufgehoben und alle Verbraucher müssen ihren Code aktualisieren, um die neue API zu nutzen.

Auf den ersten Blick scheint das eine schreckliche Idee zu sein, denn das würde dazu führen, dass Dinge in der Produktion unerwartet kaputt gehen.1 Es kann jedoch eine Situation eintreten, in der wir es nicht vermeiden können, die Kompatibilität mit älteren Versionen zu verletzen. Diese Art von Änderung kann eine Lockstep-Änderung des gesamten Systems auslösen, die eine Koordination der Ausfallzeiten erfordert.

Die Herausforderung besteht darin, dass alle diese verschiedenen Upgrade-Optionen Vorteile, aber auch Nachteile für den Verbraucher oder den Produzenten bieten. In Wirklichkeit wollen wir eine Kombination aus allen drei Optionen unterstützen. Um dies zu erreichen, müssen wir Regeln für die Versionierung und die Art und Weise, wie die Versionen dem Verbraucher zugänglich gemacht werden, einführen.

Semantische Versionierung

Die semantische Versionierung bietet einen Ansatz, den wir auf REST-APIs anwenden können, um eine Kombination der vorangegangenen Upgrade-Optionen zu erhalten. Die semantische Versionierung definiert eine numerische Darstellung, die einem API-Release zugeordnet wird. Diese Nummer basiert auf der Änderung des Verhaltens im Vergleich zur vorherigen Version, wobei die folgenden Regeln angewendet werden:

  • Eine Hauptversion führt nicht kompatible Änderungen mit früheren Versionen der API ein. Bei einer API-Plattform ist das Upgrade auf eine neue Hauptversion eine aktive Entscheidung des Verbrauchers. Es gibt wahrscheinlich einen Migrationsleitfaden und eine Nachverfolgung, wenn die Verbraucher auf die neue API umsteigen.

  • Eine Nebenversion führt eine abwärtskompatible Änderung mit der vorherigen Version der API ein. In einer API-Serviceplattform ist es akzeptabel, dass Verbraucher Nebenversionen erhalten, ohne eine aktive Änderung auf der Client-Seite vorzunehmen.

  • Eine Patch-Version ändert oder führt keine neuen Funktionen ein, sondern dient der Fehlerbehebung bei einer bestehenden Major.Minor Version von Funktionen.

Die Formatierung für die semantische Versionierung kann als Major.Minor.Patch dargestellt werden. 1.5.1 würde zum Beispiel für die Hauptversion 1, die Nebenversion 5 und das Patch-Upgrade von 1 stehen. In Kapitel 5 wirst du untersuchen, wie die semantische Versionierung mit dem Konzept des API-Lebenszyklus und der Releases zusammenhängt.

OpenAPI Spezifikation und Versionierung

Nachdem wir nun die Versionskontrolle kennengelernt haben, können wir uns Beispiele für Änderungen an der Attendee-API-Spezifikation ansehen. Es gibt verschiedene Tools, um Spezifikationen zu vergleichen, und in diesem Beispiel werden wir openapi-diff von OpenAPITools verwenden.

Wir beginnen mit einer grundlegenden Änderung: Wir ändern den Namen des Feldes givenName infirstName. Dies ist eine wichtige Änderung, weil die Verbraucher erwarten, dass givenName geparst wird, nicht firstName. Wir können das Diff-Tool von einem Docker-Container aus mit folgendem Befehl ausführen:

$docker run --rm -t \
   -v $(pwd):/specs:ro \
   openapitools/openapi-diff:latest /specs/original.json /specs/first-name.json
==========================================================================
...
- GET    /attendees
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed */*
          Schema: Broken compatibility
          Missing property: [n].givenName (string)
--------------------------------------------------------------------------
--                                Result                                --
--------------------------------------------------------------------------
                 API changes broke backward compatibility
--------------------------------------------------------------------------

Wir können versuchen, ein neues Attribut zum Rückgabetyp /attendees hinzuzufügen, um ein zusätzliches Feld namens age hinzuzufügen. Das Hinzufügen neuer Felder bricht nicht das bestehende Verhalten und somit auch nicht die Kompatibilität:

$ docker run --rm -t \
 -v $(pwd):/specs:ro \
openapitools/openapi-diff:latest --info /specs/original.json /specs/age.json
==========================================================================
...
- GET    /attendees
  Return Type:
    - Changed 200 OK
      Media types:
        - Changed */*
          Schema: Backward compatible
--------------------------------------------------------------------------
--                                Result                                --
--------------------------------------------------------------------------
                   API changes are backward compatible
--------------------------------------------------------------------------

Es lohnt sich, dies auszuprobieren, um zu sehen, welche Änderungen kompatibel sind und welche nicht. Die Einführung dieser Art von Werkzeugen als Teil der API-Pipeline wird dazu beitragen, unerwartete, nicht kompatible Änderungen für die Verbraucher zu vermeiden. OpenAPI-Spezifikationen sind ein wichtiger Teil eines API-Programms, und in Kombination mit Werkzeugen, Versionierung und Lebenszyklus sind sie von unschätzbarem Wert.

Warnung

Tools sind oft OpenAPI-versionsspezifisch, daher ist es wichtig zu prüfen, ob das Tool die Spezifikation, mit der du arbeitest, unterstützt. Im vorangegangenen Beispiel haben wir das diff-Tool mit einer früheren Version einer Spezifikation ausprobiert und es wurden keine bahnbrechenden Änderungen festgestellt.

RPC mit gRPC implementieren

Ost-West-Dienste wie z.B. Attendee neigen zu höherem Datenverkehr und können als Microservices implementiert werden, die in der gesamten Architektur verwendet werden. gRPC kann aufgrund der geringeren Datenübertragung und Geschwindigkeit innerhalb des Ökosystems ein geeigneteres Werkzeug als REST für Ost-West-Dienste sein. Alle Leistungsentscheidungen sollten immer gemessen werden, um informiert zu sein.

Die folgende .proto-Datei modelliert dasselbe attendee Objekt, das wir in unserem OpenAPI-Spezifikationsbeispiel untersucht haben. Wie bei den OpenAPI-Spezifikationen ist die Codegenerierung aus einem Schema schnell und wird in mehreren Sprachen unterstützt.

Die Datei attendees .proto definiert eine leere Anfrage und gibt eine wiederholte Antwort Attendee zurück. Bei Protokollen, die für binäre Darstellungen verwendet werden, ist es wichtig zu beachten, dass die Position und Reihenfolge der Felder entscheidend ist, da sie das Layout der Nachricht bestimmen. Das Hinzufügen eines neuen Dienstes oder einer neuen Methode ist ebenso abwärtskompatibel wie das Hinzufügen eines Feldes zu einer Nachricht, aber es ist Vorsicht geboten. Alle neuen Felder, die hinzugefügt werden, dürfen keine Pflichtfelder sein, da sonst die Abwärtskompatibilität nicht mehr gegeben ist.

Das Entfernen eines Feldes oder das Umbenennen eines Feldes führt zu Kompatibilitätsproblemen, ebenso wie das Ändern des Datentyps eines Feldes. Das Ändern der Feldnummer ist ebenfalls ein Problem, da Feldnummern zur Identifizierung von Feldern auf der Leitung verwendet werden. Die Einschränkungen der Kodierung bei gRPC bedeuten, dass die Definition sehr spezifisch sein muss. REST und OpenAPI sind recht nachsichtig, da die Spezifikation nur ein Leitfaden ist.2 Zusätzliche Felder und die Reihenfolge spielen bei OpenAPI keine Rolle, daher sind Versionierung und Kompatibilität bei gRPC noch wichtiger:

syntax = "proto3";
option java_multiple_files = true;
package com.masteringapi.attendees.grpc.server;

message AttendeesRequest {
}

message Attendee {
  int32 id = 1;
  string givenName = 2;
  string surname = 3;
  string email = 4;

}

message AttendeeResponse {
  repeated Attendee attendees = 1;
}

service AttendeesService {
  rpc getAttendees(AttendeesRequest) returns (AttendeeResponse);
}

Der folgende Java-Code veranschaulicht eine einfache Struktur für die Implementierung des Verhaltens der generierten gRPC-Serverklassen:

@GrpcService
public class AttendeesServiceImpl extends
    AttendeesServiceGrpc.AttendeesServiceImplBase {

    @Override
    public void getAttendees(AttendeesRequest request,
        StreamObserver<AttendeeResponse> responseObserver) {
          AttendeeResponse.Builder responseBuilder
              = AttendeeResponse.newBuilder();

          //populate response
          responseObserver.onNext(responseBuilder.build());
          responseObserver.onCompleted();
    }
}

Den Java-Dienst, der dieses Beispiel modelliert, findest du auf der GitHub-Seite dieses Buches. gRPC kann ohne zusätzliche Bibliotheken nicht direkt von einem Browser aus abgefragt werden. Du kannst jedoch gRPC UI installieren, um den Browser zum Testen zu verwenden.grpcurl bietet auch ein Kommandozeilen-Tool:

$ grpcurl -plaintext localhost:9090 \
    com.masteringapi.attendees.grpc.server.AttendeesService/getAttendees
{
  "attendees": [
    {
      "id": 1,
      "givenName": "Jim",
      "surname": "Gough",
      "email": "gough@mail.com"
    }
  ]
}

gRPC gibt uns eine weitere Möglichkeit, unseren Dienst abzufragen und definiert eine Spezifikation für den Verbraucher, um Code zu generieren. gRPC hat eine strengere Spezifikation als OpenAPI und erfordert, dass die Methoden/Interna vom Verbraucher verstanden werden.

Modellierung des Austauschs und Wahl eines API-Formats

In der Einleitung haben wir das Konzept der Verkehrsmuster und den Unterschied zwischen Anfragen von außerhalb des Ökosystems und Anfragen innerhalb des Ökosystems erörtert. Verkehrsmuster sind ein wichtiger Faktor bei der Bestimmung des geeigneten API-Formats für das jeweilige Problem. Wenn wir die volle Kontrolle über die Dienste und den Austausch innerhalb unserer Microservices-basierten Architektur haben, können wir anfangen, Kompromisse zu machen, die wir mit externen Verbrauchern nicht eingehen könnten.

Es ist wichtig zu erkennen, dass die Leistungsmerkmale eines Ost-West-Dienstes wahrscheinlich eher zutreffen als die eines Nord-Süd-Dienstes. Bei einem Nord-Süd-Austausch wird der Datenverkehr, der von außerhalb der Umgebung des Produzenten kommt, in der Regel über das Internet ausgetauscht. Das Internet bringt ein hohes Maß an Latenz mit sich, und eine API-Architektur sollte immer die sich gegenseitig verstärkenden Effekte jedes Dienstes berücksichtigen. In einer Microservices-basierten Architektur ist es wahrscheinlich, dass eine Nord-Süd-Anfrage mehrere Ost-West-Austausche nach sich zieht. Ein hoher Ost-West-Verkehrsaustausch muss effizient sein, um eine kaskadenartige Verlangsamung zu vermeiden, die sich bis zumKonsumenten zurück ausbreitet.

Dienstleistungen mit hohem Verkehrsaufkommen

In unserem Beispiel ist Attendees ein zentraler Dienst. In einer Microservices-basierten Architektur verfolgen die Komponenten einen attendeeId. APIs, die den Verbrauchern angeboten werden, rufen potenziell Daten ab, die im Attendee-Dienst gespeichert sind, und in der Größenordnung wird dies eine Komponente mit hohem Datenverkehr sein. Wenn die Häufigkeit des Austauschs zwischen den Diensten hoch ist, werden die Kosten für die Netzwerkübertragung aufgrund der Größe der Nutzlast und der Beschränkungen eines Protokolls im Vergleich zu einem anderen mit zunehmender Nutzung immer höher. Die Kosten können sich entweder in den monetären Kosten für jede Übertragung oder in der Gesamtzeit, die die Nachricht bis zum Ziel benötigt, niederschlagen.

Große Austausch-Payloads

Große Nutzdaten können auch zu einer Herausforderung beim API-Austausch werden und die Übertragungsleistung über die Leitung verringern. JSON über REST ist für Menschen lesbar und oft ausführlicher als eine feste oder binäre Darstellung, was zu einem Anstieg der Nutzdaten führt.

Tipp

Ein häufiges Missverständnis ist, dass die "menschliche Lesbarkeit" als Hauptgrund für die Verwendung von JSON bei Datenübertragungen angeführt wird. Die Anzahl der Male, die ein Entwickler eine Nachricht lesen muss, ist im Vergleich zum Leistungsaspekt mit modernen Tracing-Tools kein starkes Argument. Außerdem werden große JSON-Dateien selten von Anfang bis Ende gelesen. Bessere Protokollierung und Fehlerbehandlung können das Argument der menschlichen Lesbarkeit entkräften.

Ein weiterer Faktor beim Austausch großer Nutzdaten ist die Zeit, die die Komponenten benötigen, um den Inhalt der Nachricht in Domänenobjekte auf Sprachebene zu parsen. Die Zeit für das Parsen von Datenformaten hängt stark von der Sprache ab, in der ein Dienst implementiert ist. Viele traditionelle serverseitige Sprachen haben zum Beispiel Probleme mit JSON im Vergleich zu einer binären Darstellung. Es lohnt sich, die Auswirkungen des Parsens zu untersuchen und diese Überlegung bei der Auswahl eines Formats einzubeziehen.

HTTP/2 Leistungsvorteile

HTTP/2-basierte Dienste können die Leistung des Datenaustauschs verbessern, indem sie binäre Komprimierung und Framing unterstützen. Die binäre Framing-Schicht ist für den Entwickler transparent, teilt und komprimiert die Nachricht aber im Hintergrund in kleinere Teile. Der Vorteil des binären Framings ist, dass es ein vollständiges Multiplexing von Anfrage und Antwort über eine einzige Verbindung ermöglicht. Stell dir vor, du bearbeitest eine Liste in einem anderen Dienst und musst 20 verschiedene Teilnehmer abrufen. Würden wir diese als einzelne HTTP/1-Anfragen abrufen, müssten wir 20 neue TCP-Verbindungen aufbauen. Durch Multiplexing können wir 20 einzelne Anfragen über eine einzige HTTP/2-Verbindung durchführen.

gRPC verwendet standardmäßig HTTP/2 und reduziert die Größe des Datenaustauschs durch die Verwendung eines binären Protokolls. Wenn die Bandbreite ein Problem oder ein Kostenfaktor ist, bietet gRPC einen Vorteil, insbesondere wenn die Nutzdaten erheblich größer werden. gRPC kann im Vergleich zu REST von Vorteil sein, wenn die Nutzdatenbandbreite ein kumulatives Problem darstellt oder der Dienst große Datenmengen austauscht. Wenn große Datenmengen häufig ausgetauscht werden, lohnt es sich auch, einige der asynchronen Fähigkeiten von gRPC in Betracht zu ziehen.

Tipp

HTTP/3 ist auf dem Weg und wird alles verändern. HTTP/3 verwendet QUIC, ein Transportprotokoll, das auf UDP aufbaut. Mehr dazu findest du in HTTP/3 erklärt.

Vintage-Formate

Nicht alle Dienste in einer Architektur werden auf einem modernen Design basieren. In Kapitel 8 werden wir uns damit befassen, wie alte Komponenten isoliert und weiterentwickelt werden können, da ältere Komponenten bei der Weiterentwicklung von Architekturen aktiv berücksichtigt werden müssen. Es ist wichtig, dass die an einer API-Architektur Beteiligten die Auswirkungen der Einführung alter Komponenten auf die Gesamtleistung verstehen.

Leitfaden: Modellierung von Tauschgeschäften

Wenn es sich bei dem Verbraucher um das Team des alten Konferenzsystems handelt, ist der Austausch in der Regel eine Ost-West-Beziehung. Wenn es sich bei dem Verbraucher um das CFP-Team handelt, ist der Austausch in der Regel eine Nord-Süd-Beziehung. Aufgrund der unterschiedlichen Kopplungs- und Leistungsanforderungen müssen die Teams überlegen, wie der Austausch modelliert wird. Einige Aspekte, die zu berücksichtigen sind, findest du in der Richtlinie in Tabelle 1-3.

Tabelle 1-3. Leitfaden für den Austausch von Modellen

Entscheidung

Welches Format sollten wir für die Modellierung der API für unseren Dienst verwenden?

Diskussionspunkte

Ist der Austausch ein Nord-Süd- oder Ost-West-Austausch? Haben wir die Kontrolle über den Verbrauchercode?

Gibt es eine starke Geschäftsdomäne, die mehrere Dienste umfasst, oder wollen wir den Verbrauchern die Möglichkeit geben, ihre eigenen Abfragen zu erstellen?

Welche Überlegungen zur Versionierung müssen wir anstellen?

Wie häufig wird das zugrunde liegende Datenmodell eingesetzt/geändert?

Handelt es sich um einen stark frequentierten Dienst, bei dem Probleme mit der Bandbreite oder der Leistung aufgetreten sind?

Empfehlungen

Wenn die API von externen Nutzern in Anspruch genommen wird, bietet REST eine niedrige Einstiegshürde und ein starkes Domänenmodell. Externe Nutzer bedeuten in der Regel auch, dass ein Dienst mit loser Kopplung und geringen Abhängigkeiten wünschenswert ist.

Wenn die API zwischen zwei Diensten interagiert, die unter enger Kontrolle des Produzenten stehen, oder wenn der Dienst nachweislich stark frequentiert ist, solltest du gRPC in Betracht ziehen.

Mehrere Spezifikationen

In diesem Kapitel haben wir eine Reihe von API-Formaten untersucht, die in einer API-Architektur in Betracht gezogen werden sollten. Die letzte Frage lautet vielleicht : "Können wir alle Formate anbieten?"Die Antwort lautet: Ja, wir können eine API unterstützen, die eine RESTful-Präsentation, einen gRPC-Dienst und Verbindungen zu einem GraphQL-Schema hat. Das wird jedoch nicht einfach sein und ist vielleicht auch nicht das Richtige. In diesem letzten Abschnitt werden wir einige der verfügbaren Optionen für eine Multiformat-API und die damit verbundenen Herausforderungen untersuchen.

Gibt es die Goldene Spezifikation?

Die .proto-Datei für Teilnehmer und die OpenAPI-Spezifikation sehen nicht allzu unterschiedlich aus; sie enthalten dieselben Felder und haben beide Datentypen. Ist es möglich, eine .proto-Datei aus einer OAS zu erzeugen, indem du das openapi2proto-Tool verwendest? Wenn du openapi2proto --spec spec-v2.json ausführst, wird die .proto-Datei standardmäßig mit alphabetisch geordneten Feldern ausgegeben. Das ist in Ordnung, bis wir ein neues Feld zur OAS hinzufügen, das abwärtskompatibel ist, und sich plötzlich die ID aller Felder ändert, wodurch die Abwärtskompatibilität gebrochen wird.

Die folgende Beispiel- .proto-Datei zeigt, dass das Hinzufügen von a_new_field alphabetisch an den Anfang gesetzt wird, wodurch das Binärformat geändert und bestehende Dienste unterbrochen werden:

message Attendee {
    string a_new_field = 1;
    string email = 2;
    string givenName = 3;
    int32 id = 4;
    string surname = 5;
}
Hinweis

Es gibt auch andere Tools, die das Problem der Spezifikationskonvertierung lösen können. Dabei ist jedoch zu beachten, dass einige Tools nur die Version 2 der OpenAPI-Spezifikation unterstützen. Die Zeit, die für den Wechsel zwischen den Versionen 2 und 3 in einigen der um OpenAPI herum entwickelten Tools benötigt wird, hat dazu geführt, dass viele Produkte beide Versionen der OAS unterstützen müssen.

Eine alternative Option ist grpc-gateway, die einen Reverse-Proxy generiert, der eine REST-Fassade vor dem gRPC-Dienst bereitstellt. Der Reverse-Proxy wird zur Erstellungszeit anhand der .proto-Datei generiert und erzeugt ein Best-Effort-Mapping auf REST, ähnlich wie openapi2proto. Du kannst auch Erweiterungen in der .proto-Datei angeben, um die RPC-Methoden auf eine schöne Darstellung im OAS abzubilden:

import "google/api/annotations.proto";
//...
service AttendeesService {
  rpc getAttendees(AttendeesRequest) returns (AttendeeResponse) {
	option(google.api.http) = {
		get: "/attendees"
	};
}

Mit grpc-gateway haben wir eine weitere Möglichkeit, sowohl einen REST- als auch einen gRPC-Dienst zu präsentieren. grpc-gateway beinhaltet jedoch mehrere Befehle und ein Setup, das nur Entwicklern vertraut ist, die mit der Sprache Go oder der Build-Umgebung arbeiten.

Herausforderungen der kombinierten Spezifikationen

Bei der Konvertierung von OpenAPI versuchen wir, unsere RESTful-Darstellung in eine Reihe von gRPC-Aufrufen umzuwandeln. Wir versuchen, ein erweitertes Hypermedia-Domänenmodell in einen Funktionsaufruf auf niedrigerer Ebene umzuwandeln. Dies ist eine potenzielle Verwechslung des Unterschieds zwischen RPC und APIs und wird wahrscheinlich zu Kompatibilitätsproblemen führen.

Bei der Umwandlung von gRPC in OpenAPI haben wir ein ähnliches Problem: Das Ziel ist es, gRPC wie eine REST-API aussehen zu lassen. Das wird bei der Weiterentwicklung des Dienstes wahrscheinlich zu einer Reihe von Problemen führen.

Sobald Spezifikationen kombiniert oder voneinander abgeleitet werden, wird die Versionskontrolle zu einer Herausforderung. Es ist wichtig, darauf zu achten, dass sowohl die gRPC- als auch die OpenAPI-Spezifikationen ihre individuellen Kompatibilitätsanforderungen einhalten. Es sollte aktiv entschieden werden, ob die Kopplung der REST-Domäne mit einer RPC-Domäne sinnvoll ist und einen Mehrwert bietet.

Anstatt die RPC für Ost-West aus Nord-Süd zu generieren, ist es sinnvoller, die Kommunikation der Microservices-basierten Architektur (RPC) unabhängig von der REST-Darstellung sorgfältig zu entwerfen, so dass sich beide APIs frei entwickeln können. Dies ist die Wahl, die wir für die Fallstudie der Konferenz getroffen haben und die als ADR in das Projekt aufgenommen wird.

Zusammenfassung

In diesem Kapitel haben wir uns mit dem Entwurf, der Entwicklung und der Spezifikation von APIs befasst und mit den verschiedenen Umständen, unter denen du dich für REST oder gRPC entscheiden kannst. Es ist wichtig, sich daran zu erinnern, dass es nicht um REST oder gRPC geht, sondern darum, was in der jeweiligen Situation die beste Wahl für die Modellierung des Austauschs ist. Die wichtigsten Erkenntnisse sind:

  • Die Hürde für die Entwicklung von REST- und RPC-basierten APIs ist bei den meisten Technologien niedrig. Die sorgfältige Berücksichtigung von Design und Struktur ist eine wichtige architektonische Entscheidung.

  • Bei der Wahl zwischen REST- und RPC-Modellen solltest du das Richardson Maturity Model und den Grad der Kopplung zwischen Producer und Consumer berücksichtigen.

  • REST ist ein relativ lockerer Standard. Bei der Entwicklung von APIs stellt die Einhaltung eines vereinbarten API-Standards sicher, dass deine APIs konsistent sind und das erwartete Verhalten für deine Kunden aufweisen. API-Standards können auch dazu beitragen, mögliche Designentscheidungen, die zu einer inkompatiblen API führen könnten, zu vermeiden.

  • OpenAPI-Spezifikationen sind eine nützliche Methode, um API-Strukturen gemeinsam zu nutzen und viele kodierungsbezogene Aktivitäten zu automatisieren. Du solltest aktiv OpenAPI-Funktionen auswählen und entscheiden, welche Tooling- oder Generierungsfunktionen in Projekten angewendet werden sollen.

  • Die Versionskontrolle ist ein wichtiges Thema, das die Komplexität für den Hersteller erhöht, aber notwendig ist, um die Nutzung der APIs für die Verbraucher zu erleichtern. Es ist gefährlich, die Versionskontrolle in den APIs, die den Verbrauchern zur Verfügung stehen, nicht einzuplanen. Die Versionskontrolle sollte eine aktive Entscheidung im Rahmen der Produktmerkmale sein, und ein Mechanismus zur Übermittlung der Versionskontrolle an die Verbraucher sollte Teil der Diskussion sein.

  • gRPC funktioniert unglaublich gut bei Austauschvorgängen mit hoher Bandbreite und ist eine ideale Option für Ost-West-Austauschvorgänge. gRPC ist ein leistungsfähiges Tool und bietet eine weitere Option für die Modellierung von Austauschvorgängen.

  • Die Modellierung mehrerer Spezifikationen wird immer schwieriger, vor allem, wenn man von einer Art von Spezifikation zu einer anderen wechselt. Die Versionskontrolle verkompliziert die Angelegenheit noch weiter, ist aber ein wichtiger Faktor, um Änderungen zu vermeiden. Teams sollten sich genau überlegen, ob sie RPC-Darstellungen mit RESTful-API-Darstellungen kombinieren wollen, denn es gibt grundlegende Unterschiede in Bezug auf die Nutzung und die Kontrolle über den Consumer-Code.

Die Herausforderung für eine API-Architektur besteht darin, die Anforderungen aus der Sicht der Kunden zu erfüllen, ein großartiges Entwicklererlebnis rund um die APIs zu schaffen und unerwartete Kompatibilitätsprobleme zu vermeiden. In Kapitel 2 beschäftigst du dich mit dem Testen, das entscheidend dafür ist, dass die Dienste diese Ziele erfüllen.

1 Wir waren schon oft in dieser Situation, meistens als Erstes an einem Montag!

2 Die Validierung von OpenAPI-Spezifikationen zur Laufzeit hilft, eine größere Strenge durchzusetzen.

Get Beherrschung der API-Architektur now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.