Zum Inhalt springen

6.2 Schnittstellen entwerfen: Fehler, Versionen und Dokumentation

Zu Zen-Modus wechseln

Nach dieser Einheit kannst du:

  • ein einheitliches Fehlerformat samt Statuscode-Zuordnung für eine ganze API festlegen.
  • additive von brechenden Änderungen unterscheiden und das Prinzip der Versionierung erklären.
  • einen API-Entwurf als Dokument mit Beispielen schreiben und fremde Entwürfe nach nachvollziehbaren Kriterien reviewen.

Eine App spricht mit drei Endpunkten derselben API. Der erste meldet Fehler als {"error": "not found"}, der zweite als {"message": "Fehler!"}, der dritte schickt bei einem Fehler eine HTML-Seite. Für jeden Endpunkt braucht die App eigenen Fehlercode, und ein vierter Endpunkt bringt garantiert die vierte Variante.

flowchart LR
    accTitle: Drei Endpunkte, drei Fehlerformate
    a["/api/items:<br/>{error: 'not found'}"] --> app["App braucht<br/>drei Sonderfälle"]
    b["/api/orders:<br/>{message: 'Fehler!'}"] --> app
    c["/api/users:<br/>HTML-Fehlerseite"] --> app

Abb. 6.4: Ohne einheitliches Fehlerformat muss jeder Konsument jeden Endpunkt einzeln behandeln (eigene Darstellung).

In Einheit 6.1 hast du Ressourcen und Datenformate festgelegt, also den Erfolgsfall. Diese Einheit vervollständigt den Vertrag: Fehler, Regeln für spätere Änderungen und das Dokument, in dem alles steht. Im Bild vom Lokal: Die Speisekarte sagt nicht nur, was es gibt, sondern auch, was passiert, wenn etwas aus ist, und sie wird nicht über Nacht umgeschrieben.

  • Fehlerformat: die einheitliche Form, in der eine API alle Fehler meldet.
  • Fehlercode: ein maschinenlesbarer Text wie VALIDATION_FAILED, auf den ein Client gezielt reagieren kann.
  • Additive Änderung: eine Erweiterung, die bestehende Konsumenten nicht stört.
  • Brechende Änderung (breaking change): eine Änderung, nach der bestehende Konsumenten nicht mehr funktionieren.
  • Review: die Prüfung eines Entwurfs durch andere, nach festen Kriterien.

Eine API, die nur den Erfolgsfall dokumentiert, ist halb entworfen. Zum Vertrag gehört ein einheitliches Fehlerformat, das für jede Fehlerantwort der ganzen API gilt:

{
"error": {
"code": "VALIDATION_FAILED",
"message": "preset 'web-1080' is unknown",
"details": [{ "field": "preset", "issue": "unknown value" }]
}
}

Beispielprojekt: 06_schnittstellen_entwerfen_examples/ex02_error_format

Dazu die Zuordnung der Statuscodes:

StatuswannBody
400ungültige Anfragemit details, die sagen, was zu korrigieren ist
404unbekannte RessourceFehlerformat ohne details
409 Conflictfachlicher Konflikt, etwa ein laufender Job, der nicht gelöscht werden kannFehlerformat mit eigenem code
500eigener Fehler des Serversohne interne Details wie Stack Traces

Der maschinenlesbare code erlaubt dem Client gezielte Reaktionen, die message ist für Menschen. Interne Details wie Stack Traces gehören nie in eine Antwort: Sie helfen Angreifern und nützen Konsumenten nichts.

flowchart LR
    accTitle: Jeder Fehler der API hat dieselbe Form
    fail["Fehler im<br/>Endpunkt"] --> status["passender Status:<br/>400, 404, 409, 500"]
    status --> body["immer dasselbe Format:<br/>error.code, error.message,<br/>error.details"]
    body --> client["Client: eine<br/>Fehlerbehandlung für alles"]

Abb. 6.5: Status nach Art des Fehlers, Body immer im selben Format (eigene Darstellung).

Konkret heißt das für einen Client: Er prüft zuerst den Status, und wenn er gezielt reagieren will, den code. Bei VALIDATION_FAILED zeigt er die details neben den betroffenen Eingabefeldern an, bei allen anderen Codes die message.

Wo begegnet dir das? In jedem Formular, das nach dem Absenden direkt neben dem falschen Feld “Bitte gültige E-Mail-Adresse eingeben” anzeigt: Die Seite hat dafür vom Server eine Fehlerantwort mit feldgenauen Details bekommen.

Auflösung

(a) 400 mit VALIDATION_FAILED und details zum Feld preset: Die Anfrage selbst ist ungültig. (b) 409 Conflict: Die Anfrage ist in Ordnung, aber sie kollidiert mit dem aktuellen Zustand des Jobs. (c) 404. (d) 500 (oder 503), ohne interne Details in der Antwort.

Der Wert eines Vertrags ist seine Haltbarkeit. Deshalb die Grundregeln, nach denen sich eine API weiterentwickeln darf:

  • Additive Änderungen sind erlaubt: ein neues optionales Feld in der Antwort, ein neuer Endpunkt. Bestehende Konsumenten ignorieren, was sie nicht kennen, und laufen weiter.
  • Brechende Änderungen sind verboten: ein Feld umbenennen oder entfernen, einen Typ ändern, das Verhalten eines Statuscodes ändern, ein neues Pflichtfeld in der Anfrage verlangen. Wer so etwas braucht, veröffentlicht eine neue Version und lässt die alte weiterlaufen; die übliche Form ist das Pfadpräfix /api/v1/... gegen /api/v2/....
flowchart LR
    accTitle: Additive Änderungen bleiben in v1, brechende brauchen v2
    change{"Änderung"} -- "neues optionales Feld,<br/>neuer Endpunkt" --> v1["bleibt /api/v1:<br/>alte Clients laufen weiter"]
    change -- "umbenannt, entfernt,<br/>neues Pflichtfeld" --> v2["neu /api/v2:<br/>v1 läuft parallel weiter"]

Abb. 6.6: Was alte Clients nicht stört, bleibt in der Version; alles andere bekommt eine neue (eigene Darstellung).

Für die Schulprojekte genügt Version 1 und die Disziplin, sie nicht zu brechen. Das Prinzip zu kennen gehört aber zum Entwurfshandwerk, und es erklärt, warum öffentliche APIs Versionsnummern in ihren Pfaden tragen.

Wo begegnet dir das? Eine App, die du monatelang nicht aktualisiert hast, funktioniert meist trotzdem weiter, weil ihr Server die alte API-Version noch bedient. Erst wenn der Anbieter die alte Version abschaltet, verlangt die App ein Update.

Auflösung

(a) und (c) sind additiv. (b) ist brechend: Alte Clients schicken kein priority und bekommen plötzlich 400. (d) ist besonders tückisch brechend: Name und Typ bleiben gleich, aber die Bedeutung ändert sich, und alte Clients zeigen statt 62 % plötzlich 6200 % an. Für (b) und (d) braucht es /api/v2, oder priority wird optional mit einem Standardwert.

Der Entwurf wird als Dokument festgehalten, pro Endpunkt nach festem Schema. Das Format ist bewusst schlicht, Markdown genügt; entscheidend ist die Vollständigkeit:

### POST /api/jobs
Legt einen neuen Render-Job an.
Request-Body: { "filename": string, "preset": string }
- filename: Name der Quelldatei, nicht leer
- preset: eines der Presets aus GET /api/presets
Antworten:
- 201 Created: der vollständige Job (siehe Job-Format), Header "Location: /api/jobs/{id}"
- 400 VALIDATION_FAILED: filename leer oder preset unbekannt
- Beispiel-Request und Beispiel-Antwort: siehe unten

Beispiele sind Pflicht, für Erfolgs- und Fehlerfall: Sie sind der Teil der Dokumentation, den Konsumenten wirklich lesen, und sie zwingen dich, den eigenen Entwurf einmal konkret durchzuspielen. Professionelle Teams formalisieren solche Beschreibungen; das Format OpenAPI ist der verbreitete Standard, aus dem sich Doku-Seiten und Client-Code erzeugen lassen. Für dieses Jahr ist das strukturierte Markdown-Dokument das Lernziel, der Blick auf OpenAPI der Ausblick.

Der Entwurf wird im Team reviewt, bevor implementiert wird, mit denselben Regeln wie jedes Review: Kritik nennt das verletzte Kriterium. Prüffragen:

  1. Sind die URLs Substantive und einheitlich?
  2. Passen Methoden und Statuscodes zu den Konventionen?
  3. Ist jedes Feld typisiert und jedes Beispiel gültig?
  4. Deckt das Fehlerformat alle dokumentierten Fehlerfälle?
  5. Kann ein Konsument allein aus diesem Dokument einen Client schreiben?

Die letzte Frage ist der eigentliche Test: Wenn ja, ist der Vertrag vollständig.

flowchart LR
    accTitle: Vom Entwurf zur Implementierung
    draft["Entwurf v1<br/>mit Beispielen"] --> review["Review durch<br/>ein anderes Team"]
    review --> v2["Entwurf v2:<br/>Befunde eingearbeitet"]
    v2 --> impl["Implementierung<br/>ab Kapitel 9"]

Abb. 6.7: Erst reviewen, dann bauen; ein Loch im Vertrag ist auf Papier billig, im Code teuer (eigene Darstellung).

Wo begegnet dir das? Große Anbieter veröffentlichen ihre API-Dokumentation genau in diesem Muster: pro Endpunkt Parameter, Antworten und Beispiele, oft mit einem Knopf “Ausprobieren”. Die Open-Meteo-Dokumentation aus Aufgabe 05-1a ist ein gutes Beispiel.

  1. Jeder Endpunkt mit eigenem Fehlerformat: Konsumenten brauchen für jeden einen Sonderfall.
  2. Stack Traces in Fehlerantworten: verraten Interna, helfen niemandem außer Angreifern.
  3. Neues Pflichtfeld in der Anfrage für eine kleine Änderung gehalten: Es bricht jeden bestehenden Client.
  4. Bedeutung geändert, Name gleich gelassen: besonders tückischer Bruch, weil der Typ weiterhin passt.
  5. Dokumentation ohne Beispiele: Abstrakte Feldlisten verstecken Widersprüche, die erst ein konkretes JSON zeigt.

Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Beispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.

Du kannst jetzt:

  • ein einheitliches Fehlerformat samt Statuscode-Zuordnung für eine ganze API festlegen.
  • additive von brechenden Änderungen unterscheiden und das Prinzip der Versionierung erklären.
  • einen API-Entwurf als Dokument mit Beispielen schreiben und fremde Entwürfe nach nachvollziehbaren Kriterien reviewen.
  • Aufgabe 06-1a - API auf Papier, Stufen PlusPlus und MandalorianPlus (Review, Einarbeitung, Härtetest)
  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules