REST-API-Design
APIs sind Produkte: Das mentale Modell des Konsumenten, nicht Ihr Datenbankschema, bestimmt die Form. Der teure Fehler, den diese Fähigkeit verhindert, ist die Auslieferung einer inkonsistenten Oberfläche – gemischte Namensgebung, falsche Statuscodes, Offset-Paginierung auf einer wachsenden Tabelle – die jeder Konsument dann fest einprogrammiert und die nach der Integration praktisch nicht mehr korrigierbar ist.
Vorgehensweise
Arbeiten Sie in dieser Reihenfolge. Ressourcen kommen vor Methoden, da die Methodensemantik von der Ressource abhängt; Fehler- und Paginierungsverträge kommen vor der Spezifikation, da nachträgliche Änderungen Konsumenten brechen.
Schritt 1: Eingaben sammeln
Sammeln Sie vor dem Entwurf. Kennzeichnen Sie jede Annahme als Schätzung.
- Die Konsumenten: interne Dienste, Erstanbieter-Clients oder externe Entwickler. Externe Konsumenten erhöhen die Messlatte für Konsistenz – gehen Sie standardmäßig von extern aus, wenn unbekannt.
- Die Kernnomen der Domäne und welche wem gehören (bestimmt die Verschachtelung).
- Erwartete Sammlungsgrößen und Wachstum. Eine Liste, die ~10.000 Zeilen überschreiten kann, schließt Offset-Paginierung aus.
- Lese-/Schreibverhältnis und Latenzerwartungen pro Endpunkt.
- Authentifizierungsmodell (beeinflusst die Korrektheit von 401 vs. 403).
Schritt 2: Ressourcen benennen
- Plural-Nomen: /users, /orders, /products.
- Eine Ebene verschachteln für Besitzverhältnisse: /users/{id}/orders. Nicht tiefer als zwei Ebenen verschachteln – mit Filtern abflachen (/orders?user_id=... ist besser als /users/{id}/orders/{oid}/items/{iid}).
- Keine Verben in Pfaden; die HTTP-Methode ist das Verb. Für echte Aktionen, die zu keinem CRUD-Verb passen (z. B. Rechnung versenden), als Subressourcen-POST modellieren: POST /invoices/{id}/send-attempts.
- Einheitliche Schreibweise: kebab-case für mehrteilige Pfadsegmente, snake_case für JSON-Felder – einmal wählen, überall anwenden.
Schritt 3: Methoden und Statuscodes zuweisen
- GET: lesen, idempotent, kein Request-Body. 200 mit Body.
- POST: erstellen. 201 plus Location-Header, der auf die neue Ressource zeigt. Die erstellte Repräsentation im Body zurückgeben, damit der Client einen nachfolgenden GET-Aufruf spart.
- PUT: vollständiger Ersatz, idempotent. Fehlende Felder bedeuten „löschen“, nicht „behalten“.
- PATCH: partielle Aktualisierung (JSON Merge Patch oder JSON Patch – in der Spezifikation angeben).
- DELETE: entfernen, idempotent, gibt 204 zurück. Das Löschen einer bereits gelöschten Ressource gibt 204 oder 404 zurück – eines auswählen und dokumentieren.
- Fehler: 400 bei fehlerhafter Eingabe, 401 bei fehlenden/ungültigen Anmeldedaten, 403 bei gültigen Anmeldedaten ohne Berechtigung, 404 bei nicht vorhandenen Ressourcen (auch 404 statt 403 verwenden, wenn die Offenlegung der Existenz ein Leck darstellt), 409 bei Zustandskonflikten, 422 bei wohlgeformter, aber semantisch ungültiger Eingabe, 429 bei Ratenbegrenzungen (mit Retry-After), 500 bei Serverfehlern – niemals Stacktraces oder interne Identifikatoren preisgeben.
Schritt 4: Fehlervertrag festlegen
Eine Form für jeden Fehler, maschinenlesbarer Code zuerst:
{
"error": "validation_error",
"message": "Email is required",
"field": "email",
"request_id": "req_abc123"
}
Die request_id muss serverseitig protokolliert werden, damit der Support korrelieren kann. Die menschliche Nachricht ist für Entwickler beim Lesen von Logs, nicht für die Endbenutzeranzeige.
Schritt 5: Paginierung und Filterung festlegen
- Cursor-basierte Paginierung für jede Sammlung, die wächst: einen undurchsichtigen Cursor und ein has_more-Flag zurückgeben. Offset-Paginierung ist nur für kleine, begrenzte, admin-seitige Listen akzeptabel – sie degradiert linear und überspringt/dupliziert Zeilen bei gleichzeitigen Schreibvorgängen.
- Standard-Seitengröße 25, maximal 100. Seitengrößen über dem Maximum mit 400 ablehnen, nicht stillschweigend begrenzen.
- Filterung und Sortierung über Query-Parameter: ?status=active&sort=created_at:desc. Dokumentieren, welche Felder filterbar sind; unbekannte Filterparameter mit 400 ablehnen, anstatt sie zu ignorieren (stilles Ignorieren versteckt Client-Fehler).
Schritt 6: Leistungs- und Payload-Budgets anwenden
- p95-Latenzbudget: 300 ms für interaktive Endpunkte, 1 s für Berichte/Exporte. Wenn ein Vorgang 1 s nicht erreichen kann, asynchron machen: 202 Accepted plus eine Statusressource zum Abfragen.
- Antwort-Payload-Budget: Typische Antworten unter 100 KB halten; alles, was routinemäßig über 1 MB liegt, benötigt Paginierung, Feldauswahl (?fields=) oder einen dedizierten Export-Flow.
- Breaking Changes in den URL-Pfad versionieren (/v1/). Additive Änderungen (neue optionale Felder) erfordern keine Versionserhöhung. Die Deprecation-Timeline selbst an api-versioning-strategist übergeben.
Ausgearbeiteter Kontrast
Schlecht:
POST /getUserOrders?userId=42&page=3
→ 200 { "data": [...], "error": null }
Verb im Pfad, GET-Semantik auf einem POST, Offset-Paginierung, 200 mit einem Fehlerfeld, sodass Clients den Body parsen müssen, um zu wissen, ob der Aufruf fehlgeschlagen ist.
Gut:
GET /users/42/orders?status=shipped&limit=25&cursor=eyJpZCI6OTh9
→ 200 { "orders": [...], "next_cursor": "eyJpZCI6MTIzfQ", "has_more": true }
→ 404 { "error": "not_found", "message": "User 42 does not exist", "request_id": "req_9f2" }
Nomen-Pfad, Besitzverschachtelung, Cursor-Paginierung, Statuscodes tragen das Ergebnis.
Liefergegenstand
Erstellen Sie eine Endpunktspezifikation, die Folgendes enthält: die Ressourcentabelle (Pfad, Methoden, Statuscodes pro Methode), die gemeinsame Fehlerstruktur, den Paginierungsvertrag (Cursorformat, Standardwerte, Maximum), die Filter-Whitelist und das Latenz-/Payload-Budget pro Endpunkt.
NICHT
- Spiegeln Sie nicht das Datenbankschema in der API wider – Konsumenten werden an interne Details gekoppelt, die Sie später ändern möchten.
- Geben Sie bei Fehlern nicht 200 mit einem Fehlerbody zurück; Clients und Überwachung stützen sich auf Statuscodes.
- Verwenden Sie keine Offset-Paginierung für unbegrenzte Sammlungen – sie dupliziert und überspringt Zeilen bei Schreibvorgängen und degradiert linear.
- Verschachteln Sie Pfade nicht tiefer als zwei Ebenen; tiefe Verschachtelung kodiert einen einzigen Zugriffspfad und verbietet alle anderen.
- Ignorieren Sie unbekannte Query-Parameter nicht stillschweigend – ein vertippter Filter, der ungefilterte Daten zurückgibt, ist ein Datenleck-Fehler.
Qualitätsmaßstab
- Ein Konsument könnte einen Client aus der Spezifikation implementieren, ohne eine einzige Frage zu stellen.
- Jeder Endpunkt gibt seine Statuscodes an, einschließlich Fehlerfälle.
- Alle Sammlungsendpunkte teilen einen Paginierungsvertrag; alle Fehler teilen eine Form.
- Identische Konzepte haben identische Namen über alle Pfade und Payloads hinweg.
- Jeder Endpunkt hat ein explizites Latenzbudget, und diejenigen >1 s sind asynchron.