Zum Inhalt springen

6. Schnittstellen entwerfen

Zu Zen-Modus wechseln

Bisher waren wir Konsumenten fremder Schnittstellen; ab jetzt wechseln wir die Seite. Der Lehrplan verlangt, Schnittstellen zur Kommunikation zwischen Anwendungen zu entwerfen und zu implementieren, und die Reihenfolge ist ernst gemeint: Erst der Entwurf, dann die Implementierung. Eine Schnittstelle ist ein Vertrag, und Verträge schreibt man, bevor man baut; nachträgliche Vertragsänderungen beschädigen jeden, der schon konsumiert. In diesem Kapitel entsteht eine vollständige API auf Papier, die in den Next.js-Kapiteln dann wirklich implementiert wird.

Als Entwurfsstil verwenden wir REST (Representational State Transfer), den De-facto-Standard für Web-APIs. REST ist kein Gesetz, sondern eine Sammlung von Konventionen; ihr Wert liegt in der Vorhersehbarkeit: Wer eine REST-API kennt, findet sich in der nächsten sofort zurecht.

REST denkt in Ressourcen: den Dingen, die die API verwaltet. Als durchgehendes Beispiel dieses und der folgenden Kapitel dient eine kleine Render-Farm für Videoaufträge; ihre Ressourcen sind etwa Jobs, Presets und Ergebnisdateien. (In den Next.js-Kapiteln wird daraus das Projekt render-farm, in den Übungen wenden Sie dieselben Prinzipien auf eine andere Domäne an, das Schulbuffet.) Aus den Ressourcen ergeben sich die URLs, und zwar nach festen Regeln:

  • URLs benennen Ressourcen als Substantive im Plural: /api/jobs, /api/presets.
  • Eine Sammlung ist /api/jobs, ein Element daraus /api/jobs/42.
  • Die Aktion steckt in der HTTP-Methode, niemals in der URL: DELETE /api/jobs/42 statt /api/deleteJob?id=42.
  • Zugehörigkeit darf sich verschachteln: /api/jobs/42/log ist das Log von Job 42. Mehr als eine Ebene Verschachtelung wird schnell unhandlich; dann lieber flach bleiben und filtern.

Damit ergibt sich für eine Ressource das Standard-Quintett:

Methode + PfadBedeutungErfolgsstatus
GET /api/jobsalle Jobs auflisten200
POST /api/jobsneuen Job anlegen201 Created
GET /api/jobs/42einen Job lesen200
PATCH /api/jobs/42einen Job ändern200
DELETE /api/jobs/42einen Job löschen204 No Content

Feinheiten, die eine API professionell machen: POST beantwortet das Anlegen mit 201 und liefert die neue Ressource (samt vergebener ID) im Body zurück; DELETE liefert 204 ohne Body; ein GET auf eine unbekannte ID liefert 404. Wer diese Konventionen einhält, erspart jedem Konsumenten das Rätselraten.

Filtern, Sortieren und Blättern laufen über Query-Parameter der Sammlung, nicht über neue Pfade:

GET /api/jobs?state=failed
GET /api/jobs?sort=createdAt&order=desc
GET /api/jobs?limit=20&offset=40

Die zweite Hälfte des Vertrags sind die Datenformate. Für jede Operation wird festgelegt, wie Anfrage- und Antwort-Body aussehen, und zwar verbindlich und mit Beispielen. Für den Render-Job:

{
"id": 42,
"filename": "intro.mp4",
"preset": "web-1080p",
"state": "running",
"progress": 0.62,
"createdAt": "2027-03-14T09:31:02Z"
}

Beim Anlegen (POST /api/jobs) schickt der Client nur, was er wissen kann:

{ "filename": "intro.mp4", "preset": "web-1080p" }

id, state, progress und createdAt vergibt der Server; das Antwortformat ist wieder der vollständige Job. Diese Trennung (Eingabeformat gegen Ausgabeformat) ist eine bewusste Vertragsentscheidung und gehört dokumentiert.

Formatregeln, die sich bewährt haben und in diesem Jahr verbindlich sind:

  • Feldnamen in camelCase, konsistent über die ganze API.
  • Zeitstempel im ISO-8601-Format als String ("2027-03-14T09:31:02Z", das Z markiert UTC); niemals lokale Formate wie 14.03.2027.
  • Zustandsfelder als dokumentierte Wertemenge (queued | running | done | failed), im Code ein Union Type.
  • IDs sind für den Client undurchschaubare Kennungen; ob Zahl oder String, entscheidet der Anbieter einmal und bleibt dabei.
  • Kein Über-Verschachteln: Antworten so flach wie fachlich möglich.

In TypeScript wird der Vertrag zu Interfaces, die später Client und Server importieren; eine Quelle der Wahrheit statt zweier Abschriften:

export interface Job {
id: number;
filename: string;
preset: string;
state: "queued" | "running" | "done" | "failed";
progress: number;
createdAt: string;
}
export type NewJob = Pick<Job, "filename" | "preset">;

(Pick baut aus einem Interface ein neues mit ausgewählten Feldern; einer der wenigen TypeScript-Hilfstypen, die sich früh lohnen.)

Eine API, die nur den Erfolgsfall dokumentiert, ist halb entworfen. Zum Vertrag gehört ein konsistentes 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" }]
}
}

Dazu die Zuordnung der Statuscodes: 400 für ungültige Anfragen (mit details, die dem Client sagen, was zu korrigieren ist), 404 für unbekannte Ressourcen, 409 Conflict für fachliche Konflikte (etwa: Job läuft bereits und kann nicht gelöscht werden), 500 für eigene Fehler des Servers, dann ohne interne Details wie Stack Traces, die Angreifern helfen und Konsumenten nichts nützen. Der maschinenlesbare code erlaubt dem Client gezielte Reaktionen, die message ist für Menschen.

Der Wert eines Vertrags ist seine Haltbarkeit. Deshalb die Grundregeln der API-Evolution:

  • 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: Feld umbenennen oder entfernen, Typ ändern, Statuscode-Verhalten ändern. 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/....

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.

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 Doku, den Konsumenten wirklich lesen, und sie zwingen den Autor, den eigenen Entwurf einmal konkret durchzuspielen. Professionelle Ökosysteme formalisieren solche Beschreibungen (das Format OpenAPI ist der verbreitete Standard, aus dem sich Doku-Seiten und Client-Code generieren 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: Sind die URLs Substantive und konsistent? Passen Methoden und Statuscodes zu den Konventionen? Ist jedes Feld typisiert und jedes Beispiel gültig? Deckt das Fehlerformat alle dokumentierten Fehlerfälle? Kann ein Konsument allein aus diesem Dokument einen Client schreiben? Die letzte Frage ist der eigentliche Test: Wenn ja, ist der Vertrag vollständig.

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“

Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:

  • Erklären: das Ressourcen-Prinzip von REST (Substantiv-URLs, Methoden als Aktionen, Statuscodes als Ergebnis) erklären.
  • Entwerfen: für eine gegebene Domäne die Ressourcen, Endpunkte und Query-Parameter einer API festlegen.
  • Entwerfen: Request- und Response-Formate als JSON-Verträge mit Beispielen definieren und als TypeScript-Interfaces festhalten.
  • Entwerfen: ein konsistentes Fehlerformat samt Statuscode-Zuordnung für die ganze API festlegen.
  • Erklären: additive von brechenden Änderungen unterscheiden und das Prinzip der Versionierung erläutern.
  • Beurteilen: fremde API-Entwürfe anhand nachvollziehbarer Kriterien reviewen und Lücken im Vertrag aufdecken.
  • Aufgabe 10 - API auf Papier