FAQ zur Teamzusammenarbeit: Verträge, Konfiguration und Fixtures
Fazit: Benennungsstil, Zeitformate, null-Semantik, Enum-Formen, Konfigurationsformate, Fixture-Organisation und Merge-Konflikte – zwölf Abwägungen, über die Teams tatsächlich streiten.

Kurz gesagt: Klären Sie diese Punkte einmal in einer Teamkonvention – eine {"data":…,"meta":…}-Hülle mit HTTP-Statuscodes, ein projektweiter Benennungsstil, RFC-3339-Zeitstempel mit Zeitzone und String-Enums – und entwickeln Sie dann jede neue API gegen denselben Vertrag, indem Sie nur Felder hinzufügen, sie nie ändern oder löschen.
Es gibt keine einzige richtige Antwort auf diese Fragen, aber ein Team muss eine einzige Antwort haben – sonst beginnt jede neue API dieselbe Diskussion neu.
1. API-Verträge
Welche Struktur sollte eine API zurückgeben?
Drei verbreitete Formen, jede mit Kosten:
| Form | Beispiel | Vorteile | Nachteile |
|---|---|---|---|
| Rohdaten | [{...}, {...}] |
Einfach | Kann keine Metadaten tragen |
| Feste Hülle | {"code":0,"message":"ok","data":{...}} |
Einheitliche Fehlerbehandlung | Geschäftscode dupliziert den HTTP-Status |
| Ressourcenhülle | {"data":{...},"meta":{...},"links":{...}} |
Erweiterbar, klare Semantik | Eine zusätzliche Verschachtelungsebene |
Für interne Geschäfts-APIs bevorzugen Sie „Ressourcenhülle + HTTP-Statuscodes, die das Ergebnis ausdrücken“:
{
"data": [{ "id": "1", "name": "示例" }],
"meta": { "total": 128, "page": 2, "pageSize": 20 }
}
Verwenden Sie für Fehler einen HTTP-Statuscode plus einen strukturierten Fehler-Body, statt einen weiteren code zu verschachteln:
{
"error": {
"type": "validation_failed",
"message": "请求参数校验失败",
"details": [
{ "path": "/email", "reason": "格式不正确" }
]
}
}
Kernpunkt: HTTP-Statuscodes drücken bereits Erfolg oder Fehlschlag aus, ein Geschäftscode ist also redundant. Koexistieren die beiden Systeme, sind Inkonsistenzen wie „201, aber code ist 500“ fast unvermeidlich.
Vermeiden Sie nackte Arrays. Mit einem Array auf oberster Ebene können Sie Paginierung, Summen oder Cursor nicht anhängen, und sie später hinzuzufügen heißt, jeden Aufrufer zu ändern.
Sollten Feldnamen snake_case oder camelCase verwenden?
Wählen Sie eines, wenden Sie es projektweit an und mischen Sie sie nie innerhalb eines JSON-Dokuments.
| Stil | Häufiges Ökosystem | Beispiel |
|---|---|---|
camelCase |
JavaScript / TypeScript / Java | userName, createdAt |
snake_case |
Python / Ruby / Go (teils) / Datenbankspalten | user_name, created_at |
kebab-case |
Selten; mit Bindestrichen versehene Schlüssel sind umständlich zuzugreifen | user-name |
Kriterien:
- Wer konsumiert dieses JSON: camelCase wählen, wenn das Frontend dominiert, snake_case, wenn Datenpipelines und Python-Dienste dominieren;
- Bildet es direkt auf Datenbankspalten ab: snake_case spart eine Konvertierungsschicht, wenn Tabellenstrukturen direkt durchgereicht werden;
- Kontinuität bestehender Systeme: Behalten Sie, was die bestehende API verwendet; Konsistenz schlägt Korrektheit.
Müssen Sie wirklich konvertieren, tun Sie es an der Grenze (Gateway oder Frontend-Request-Schicht), statt toCamelCase-Aufrufe durch den Geschäftscode zu verstreuen.
Welches Format sollten Zeitfelder verwenden?
Verwenden Sie RFC-3339-/ISO-8601-Strings mit einem Zeitzonen-Offset:
{
"createdAt": "2026-10-30T11:37:57+08:00",
"updatedAt": "2026-10-30T03:37:57Z"
}
Kernpunkte:
- Die Zeitzone einschließen:
2026-10-30 11:37:57ohne Zone bricht in zeitzonenübergreifenden Szenarien unweigerlich; Zoder einen Offset konsistent verwenden: Beides ist korrekt, aber mischen Sie sie nicht innerhalb einer API;- Numerische Zeitstempel vermeiden: Ob Sekunden oder Millisekunden, und ob Schaltsekunden enthalten sind, muss jedes Team neu interpretieren. Ist Kompaktheit nötig, verwenden Sie eine Millisekunden-Ganzzahl, aber nageln Sie die Einheit in der Dokumentation fest.
Bevorzugen Sie Feldnamen mit At-Suffix wie createdAt / updatedAt / deletedAt,
damit auf einen Blick klar ist, dass der Wert ein Zeitpunkt statt einer Dauer ist. Für Dauern verwenden Sie timeoutMs, ttlSeconds – legen Sie die Einheit in den Namen.
Sollten Nullen null sein oder ein weggelassenes Feld?
Das hängt davon ab, ob „dieses Feld bedeutsam ist“:
| Semantik | Form | Hinweise |
|---|---|---|
| Das Feld existiert, hat aber noch keinen Wert | "name": null |
Sagt ausdrücklich „es wurde geprüft und ist leer“ |
| Das Feld ist für diese Ressource irrelevant | Weglassen | Ein regulärer Nutzer sollte etwa kein vipExpireAt haben |
| Partielles Update (PATCH) | Weglassen = nicht ändern | Nur null bedeutet hier „leeren“ |
Der dritte Fall wird am häufigsten übersehen. In einer PATCH-Anfrage:
{ "nickname": null }
bedeutet das „den Nickname leeren“, während nickname aus dem Request-Body wegzulassen „den Nickname nicht anfassen“ bedeutet.
Behandeln Sie beides als „leeren“, löscht ein Nutzer, der seine E-Mail ändert, seinen Nickname.
Noch einmal: Verwenden Sie nicht undefined. Es ist kein gültiger JSON-Wert, und JSON.stringify verwirft den Schlüssel vollständig,
das Verhalten entspricht also „weglassen“, während der Code aussieht wie „ein leerer Wert wurde ausdrücklich übergeben“.
Wie sollten Boolean-Felder benannt werden?
Verwenden Sie die Präfixe is / has / can / should und vermeiden Sie Verneinungen.
{
"isActive": true,
"hasPassword": false,
"canEdit": true
}
Drei Lehren:
- Keine Verneinungen:
isDisabled: falsezwingt den Leser, es einmal zu invertieren;isEnabled: trueist direkter. Doppelte Verneinung (isNotDisabled) ist ein Desaster; - Keine Zahlen für Booleans:
"enabled": 1lässt jeden Konsumenten einmal=== 1schreiben; - Keine Strings:
"enable": "true"ist in JavaScript wahrheitswertig, undif (data.enable)ist auch bei"false"wahr; solche Bugs sind extrem schwer zu verfolgen.
Sollten Enum-Felder Zahlen oder Strings verwenden?
Verwenden Sie Strings, sofern es keine klare Speicher- oder Bandbreitenbeschränkung gibt.
{ "status": "active" }
Verglichen mit { "status": 1 } gewinnen Strings, weil:
- Logs, Packet Captures und Datenbanken direkt lesbar sind, ohne eine Abbildungstabelle nachzuschlagen;
- Ein hinzugefügter Enum-Wert die Bedeutung bestehender Werte nicht ändert (einen Wert mitten in ein numerisches Enum einzufügen verschiebt alles);
- Das Frontend keine
1 → 'active'-Abbildung synchron halten muss.
Die Kosten sind ein paar zusätzliche Bytes, die nach Kompression nahezu vernachlässigbar sind.
Begleitende Praxis: Verwenden Sie für String-Enum-Werte Kleinbuchstaben + Unterstriche (pending_review) und beschränken Sie sie mit enum im Schema,
damit ungültige Werte in der Validierungsphase abgelehnt werden.
2. Konfiguration und Fixtures
Sollte Konfiguration JSON, YAML oder TOML sein?
Wählen Sie nach Zweck, nicht nach Vorliebe:
| Format | Gut für | Schlecht für |
|---|---|---|
| JSON | Programmatisches Lesen und Schreiben, strikt strukturierte Daten, Schema-Validierung | Häufiges manuelles Bearbeiten (Kommentare nicht erlaubt) |
| YAML | Tief verschachtelte Deployment-Konfiguration (K8s) | Von Hand geschriebene Einrückung ist fehleranfällig; yes/no werden zu Booleans |
| TOML | Flache Abhängigkeits- und Werkzeugkonfiguration (Cargo.toml, pyproject.toml) |
Schlechte Lesbarkeit bei tiefer Verschachtelung |
| JSONC | Editor-Konfiguration, die Kommentare braucht (tsconfig.json) |
Systemgrenzen überschreiten |
Ein häufiger Teamfehler ist, Geschäftskonfiguration in YAML zu speichern – YAMLs implizite Typkonvertierung (1.0 wird eine Zahl,
NO wird ein Boolean, 2026-10-30 wird ein Datum) erzeugt über Parser hinweg unterschiedliche Ergebnisse.
Muss die Konfiguration strikt von einem Programm validiert werden, wählen Sie JSON + Schema und dokumentieren Sie Kommentare über ein _comment-Feld oder separate Dokumentation.
Wie sollten Test-Fixtures organisiert sein?
Organisieren Sie nach „eine Basis + minimale Diffs“, nicht nach dem Kopieren eines riesigen JSON für jeden Testfall.
fixtures/
├─ users/
│ ├─ base.json # Baseline user object
│ ├─ no-email.json # Only email is missing
│ └─ invalid-age.json # Only age is out of range
└─ fixtures.ts # Utility functions for loading and shallow merging
import baseUser from './users/base.json';
export function makeUser(overrides: Partial<User> = {}): User {
return { ...baseUser, ...overrides };
}
So muss ein neuer Testfall nur die abweichenden Felder schreiben, und eine Änderung an einem Basisfeld aktualisiert jeden Fall automatisch.
Drei Regeln:
- Fixtures müssen die Schema-Validierung bestehen: das Basis-Fixture selbst sollte gültige Daten sein;
- Keine echten Produktionsdaten exportieren: Telefonnummern, Ausweisnummern und Adressen sind alle personenbezogene Informationen;
- Fixtures gehören in die Versionskontrolle, müssen aber klein bleiben: eine einzelne Datei über ein paar Dutzend KB verdient Beschneidung oder Generierung.
Wie reduzierte ich Merge-Konflikte in JSON-Dateien?
JSON konfligiert leicht: Ein Formatter fließt die ganze Datei auf einmal um, zwei Personen, die je eine Zeile ändern, konfligieren also über die ganze Datei.
Vier Taktiken:
- Formatierung vereinheitlichen und in
.editorconfigeintragen: konsistente Einrückung, Zeilenenden und abschließender Zeilenumbruch vermeiden unbezogene Diffs; - Schlüssel sortieren: einigen Sie sich auf alphabetische Reihenfolge, damit ein neues Feld nicht mit dem Einfügepunkt eines anderen kollidiert;
- Dateien aufteilen: eine riesige
config.jsonnach Domäne in mehrere Dateien aufteilen, und die Konfliktoberfläche schrumpft natürlich; - „Alles neu formatieren“-Commits vermeiden: Trennen Sie Formatierung und Geschäftsänderungen in zwei Commits, damit das Review lesbar bleibt.
Verwendet das Team Lockdateien (pnpm-lock.yaml, package-lock.json), mergen Sie sie nicht von Hand, sondern generieren Sie sie neu:
git checkout --theirs pnpm-lock.yaml
pnpm install
Wie pflege ich Versionskompatibilität?
Das Prinzip lautet „hinzufügen, nicht ändern, nicht entfernen“.
| Änderung | Kompatibilität | Ansatz |
|---|---|---|
| Ein optionales Feld ergänzen | Kompatibel | Hinzufügen; Konsumenten ignorieren unbekannte Felder |
| Ein Pflichtfeld ergänzen | Inkompatibel | Die Version erhöhen oder einen Standardwert liefern |
| Ein Feld entfernen | Inkompatibel | Zuerst als veraltet markieren, Aufrufer beobachten, dann entfernen |
| Den Typ eines Feldes ändern | Inkompatibel | Einen neuen Feldnamen ergänzen und während der Übergangszeit doppelt schreiben |
| Die Enum-Wertemenge ändern | Kommt darauf an | Werte hinzuzufügen ist kompatibel; sie zu entfernen nicht |
Unterstützende Mechanismen:
- Konsumenten verwenden „nachsichtiges Parsen“: unbekannte Felder ignorieren und fehlenden Standardwerte liefern;
- Der Server behält alte Felder eine Weile und liefert einen
Deprecation-Header in der Antwort als Hinweis; - Im Schema neue Felder nicht als
requiredmarkieren und veraltete Felder mitdeprecated: truetaggen.
Wie validiere ich Verträge in CI?
Behandeln Sie das Schema als Teil des Codes und validieren Sie es bei jedem Commit.
# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false
Führen Sie drei Dinge in CI aus:
- Gültigkeit des Schemas selbst: ob die
$schema-Deklaration gültig ist und ob$refauflöst; - Ob die Fixtures zum Schema passen: stellt sicher, dass Testdaten nicht driften;
- Ob der Vertrag rückwärtskompatibel ist: gegen die vorherige Version vergleichen und auf entfernte Felder oder geänderte Typen prüfen.
Der dritte Punkt braucht Werkzeugunterstützung (etwa openapi-diff oder json-schema-diff);
ohne sie vergleichen Sie zumindest Änderungen in den required-Arrays und den properties-Schlüsselmengen mit einem Skript.
3. Kurzreferenz
| Frage | Empfehlung |
|---|---|
| Response-Struktur | {"data":..., "meta":...}, mit HTTP-Statuscodes für Fehler |
| Benennungsstil | camelCase (frontend-orientiert) oder snake_case (datenorientiert), projektweit konsistent |
| Zeitformat | RFC-3339-Strings mit Zeitzone, Feldnamen auf At endend |
| Nullen | null für keinen Wert, irrelevante Felder weglassen, Weglassen in PATCH bedeutet „nicht ändern“ |
| Boolean-Benennung | is/has/can-Präfixe, keine Verneinungen, kein 0/1 |
| Enums | Strings, Kleinbuchstaben mit Unterstrichen, enum im Schema ergänzen |
| Konfigurationsformat | JSON für programmatische Validierung, TOML oder JSONC für manuelles Bearbeiten |
| Fixtures | Eine Basis + Minimal-Diff-Overrides, gültig gegen das Schema |
| Merge-Konflikte | Formatierung vereinheitlichen, Schlüssel sortieren, Dateien aufteilen |
| Versionskompatibilität | Hinzufügen, nicht ändern, nicht löschen; vor dem Entfernen als veraltet markieren |