FAQ dotyczące współpracy zespołowej: kontrakty, konfiguracja i dane testowe
Podsumowanie: Styl nazewnictwa, formaty czasu, semantyka null, kształty wyliczeń, formaty konfiguracji, organizacja danych testowych i konflikty scalania — dwanaście kompromisów, o które zespoły naprawdę się spierają.

Najważniejsze: ustal to raz w konwencji zespołu — koperta {"data":…,"meta":…} z kodami statusu HTTP, jeden styl nazewnictwa w całej witrynie, znaczniki czasu RFC 3339 ze strefą czasową i wyliczenia łańcuchowe — a potem rozwijaj każde nowe API według tego samego kontraktu, tylko dodając pola, nigdy ich nie zmieniając ani nie usuwając.
Na te pytania nie ma jednej poprawnej odpowiedzi, ale zespół musi mieć jedną — inaczej każde nowe API rozpoczyna tę samą kłótnię.
1. Kontrakty API
Jaką strukturę powinno zwracać API?
Trzy częste kształty, każdy z kosztem:
| Kształt | Przykład | Zalety | Wady |
|---|---|---|---|
| Surowe dane | [{...}, {...}] |
Proste | Nie niesie metadanych |
| Koperta stała | {"code":0,"message":"ok","data":{...}} |
Ujednolicona obsługa błędów | Kod biznesowy powiela status HTTP |
| Koperta zasobu | {"data":{...},"meta":{...},"links":{...}} |
Rozszerzalna, jasna semantyka | Jedna warstwa zagnieżdżenia więcej |
Dla wewnętrznych API biznesowych preferuj „kopertę zasobu + kody statusu HTTP wyrażające wynik”:
{
"data": [{ "id": "1", "name": "示例" }],
"meta": { "total": 128, "page": 2, "pageSize": 20 }
}
Dla błędów używaj kodu statusu HTTP plus strukturalnego ciała błędu, zamiast zagnieżdżać kolejny code:
{
"error": {
"type": "validation_failed",
"message": "请求参数校验失败",
"details": [
{ "path": "/email", "reason": "格式不正确" }
]
}
}
Kluczowy punkt: kody statusu HTTP już wyrażają sukces lub porażkę, więc kod biznesowy jest zbędny. Gdy dwa systemy współistnieją, niespójności takie jak „201, ale code to 500” są niemal nieuniknione.
Unikaj nagich tablic. Przy tablicy na wierzchołku nie możesz dołączyć paginacji, sum ani kursorów, a dodanie ich później oznacza zmianę każdego wywołującego.
Czy nazwy pól powinny używać snake_case czy camelCase?
Wybierz jeden, stosuj w całej witrynie i nigdy nie mieszaj ich w jednym dokumencie JSON.
| Styl | Częsty ekosystem | Przykład |
|---|---|---|
camelCase |
JavaScript / TypeScript / Java | userName, createdAt |
snake_case |
Python / Ruby / Go (częściowo) / kolumny bazy | user_name, created_at |
kebab-case |
Rzadko; klucze z łącznikami są niewygodne w dostępie | user-name |
Kryteria:
- Kto konsumuje ten JSON: wybierz camelCase, gdy dominuje frontend, snake_case, gdy dominują potoki danych i usługi Pythona;
- Czy mapuje się bezpośrednio na kolumny bazy: snake_case oszczędza warstwę konwersji przy przekazywaniu struktur tabel bezpośrednio;
- Ciągłość istniejących systemów: zostaw to, czego używa istniejące API; spójność bije poprawność.
Jeśli naprawdę musisz konwertować, rób to na granicy (brama lub warstwa żądań frontendu), zamiast rozrzucać wywołania toCamelCase po kodzie biznesowym.
Jaki format powinny mieć pola czasu?
Używaj łańcuchów RFC 3339 / ISO 8601 z przesunięciem strefy czasowej:
{
"createdAt": "2026-10-30T11:37:57+08:00",
"updatedAt": "2026-10-30T03:37:57Z"
}
Kluczowe punkty:
- Uwzględnij strefę czasową:
2026-10-30 11:37:57bez strefy nieuchronnie pęknie w scenariuszach między strefami; - Używaj
Zlub przesunięcia konsekwentnie: oba są poprawne, ale nie mieszaj ich w jednym API; - Unikaj numerycznych znaczników czasu: czy to sekundy, czy milisekundy, i czy uwzględniają sekundy przestępne, każdy zespół musi to reinterpretować. Gdy wymagana jest zwięzłość, użyj całkowitoliczbowego znacznika w milisekundach, ale przypnij jednostkę w dokumentacji.
Preferuj nazwy pól z sufiksem At, takie jak createdAt / updatedAt / deletedAt,
aby na pierwszy rzut oka było oczywiste, że wartość jest punktem w czasie, a nie czasem trwania. Dla czasu trwania używaj timeoutMs, ttlSeconds — jednostkę umieść w nazwie.
Czy null powinno być null, czy pominiętym polem?
Zależy, czy „to pole ma znaczenie”:
| Semantyka | Forma | Uwagi |
|---|---|---|
| Pole istnieje, ale nie ma jeszcze wartości | "name": null |
Jasno mówi „sprawdzono i jest puste” |
| Pole nie ma związku z tym zasobem | Pomiń | Na przykład zwykły użytkownik nie powinien mieć vipExpireAt |
| Częściowa aktualizacja (PATCH) | Pomiń = nie zmieniaj | Tylko null znaczy tu „wyczyść” |
Trzeci przypadek jest najczęściej przeoczony. W żądaniu PATCH:
{ "nickname": null }
to znaczy „wyczyść pseudonim”, podczas gdy pominięcie nickname w ciele żądania znaczy „nie dotykaj pseudonimu”.
Jeśli traktujesz oba jako „wyczyść”, użytkownik zmieniający e-mail wytrze swój pseudonim.
Jeszcze raz: nie używaj undefined. To nie jest poprawna wartość JSON, a JSON.stringify całkowicie pomija klucz,
więc zachowanie odpowiada „pominięciu”, podczas gdy kod wygląda jak „jawnie przekazano pustą wartość”.
Jak nazywać pola logiczne?
Używaj prefiksów is / has / can / should i unikaj negacji.
{
"isActive": true,
"hasPassword": false,
"canEdit": true
}
Trzy lekcje:
- Bez negacji:
isDisabled: falsezmusza czytelnika, by raz to odwrócił;isEnabled: truejest bardziej bezpośrednie. Podwójna negacja (isNotDisabled) to katastrofa; - Nie używaj liczb dla wartości logicznych:
"enabled": 1każe każdemu konsumentowi raz napisać=== 1; - Nie używaj łańcuchów:
"enable": "true"jest prawdziwe w JavaScript, aif (data.enable)jest prawdziwe także dla"false"; takie błędy są ekstremalnie trudne do wytropienia.
Czy pola wyliczeniowe powinny używać liczb czy łańcuchów?
Używaj łańcuchów, chyba że istnieje wyraźne ograniczenie przechowywania lub przepustowości.
{ "status": "active" }
W porównaniu z { "status": 1 } łańcuchy wygrywają, ponieważ:
- Logi, przechwycony ruch i bazy danych są czytelne bezpośrednio, bez zaglądania do tabeli mapowania;
- Dodanie wartości wyliczenia nie zmienia znaczenia istniejących wartości (wstawienie wartości w środku liczbowego wyliczenia przesuwa wszystko);
- Frontend nie musi utrzymywać mapowania
1 → 'active'w synchronizacji.
Koszt to kilka dodatkowych bajtów, co po kompresji jest niemal pomijalne.
Praktyka towarzysząca: używaj małych liter i podkreśleń dla łańcuchowych wartości wyliczeń (pending_review) i ogranicz je przez enum w Schema,
aby niepoprawne wartości były odrzucane na etapie walidacji.
2. Konfiguracja i dane testowe
Czy konfiguracja powinna być JSON, YAML czy TOML?
Wybieraj według celu, a nie preferencji:
| Format | Dobre do | Złe do |
|---|---|---|
| JSON | Programowego odczytu i zapisu, ściśle ustrukturyzowanych danych, walidacji Schema | Częstego ręcznego edytowania (komentarze niedozwolone) |
| YAML | Głęboko zagnieżdżonej konfiguracji wdrożeniowej (K8s) | Ręcznie pisane wcięcia są podatne na błędy; yes/no stają się wartościami logicznymi |
| TOML | Płaskiej konfiguracji zależności i narzędzi (Cargo.toml, pyproject.toml) |
Słabej czytelności przy głębokim zagnieżdżeniu |
| JSONC | Konfiguracji edytora wymagającej komentarzy (tsconfig.json) |
Przekraczania granic systemów |
Częstym błędem zespołów jest przechowywanie konfiguracji biznesowej w YAML — niejawne konwersje typów w YAML (1.0 staje się liczbą,
NO staje się wartością logiczną, 2026-10-30 staje się datą) produkują różne wyniki między parserami.
Jeśli konfiguracja musi być rygorystycznie walidowana przez program, wybierz JSON + Schema i dokumentuj komentarze przez pole _comment albo osobną dokumentację.
Jak organizować dane testowe?
Organizuj według „jeden baseline + minimalne diffy”, zamiast kopiować ogromny JSON dla każdego przypadku testowego.
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 };
}
Dzięki temu nowy przypadek testowy musi tylko wypisać różniące się pola, a zmiana pola baseline automatycznie aktualizuje każdy przypadek.
Trzy zasady:
- Dane testowe muszą przechodzić walidację Schema: sam baseline powinien być poprawnymi danymi;
- Nie eksportuj prawdziwych danych produkcyjnych: numery telefonów, numery dowodów i adresy to dane osobowe;
- Dane testowe należą do kontroli wersji, ale muszą pozostać małe: plik powyżej kilkudziesięciu KB zasługuje na przycięcie lub generowanie.
Jak zmniejszyć konflikty scalania w plikach JSON?
JSON łatwo konfliktuje: formater przepływa cały plik naraz, więc dwie osoby zmieniające po jednej linii konfliktują na całym pliku.
Cztery taktyki:
- Ujednolić formatowanie i dodać je do
.editorconfig: spójne wcięcia, zakończenia wierszy i końcowa nowa linia unikają niepowiązanych diffów; - Sortuj klucze: uzgodnij porządek alfabetyczny, aby nowe pole nie kolidowało z czyimś punktem wstawienia;
- Dziel pliki: rozbij jeden gigantyczny
config.jsonna kilka plików według domen, a powierzchnia konfliktów naturalnie się kurczy; - Unikaj commitów „przeformatuj wszystko”: oddziel formatowanie od zmian biznesowych do dwóch commitów, aby przegląd pozostał czytelny.
Jeśli zespół używa plików lock (pnpm-lock.yaml, package-lock.json), nie scalaj ich ręcznie; zamiast tego regeneruj:
git checkout --theirs pnpm-lock.yaml
pnpm install
Jak utrzymywać zgodność wersji?
Zasada brzmi „dodawaj, nie zmieniaj, nie usuwaj”.
| Zmiana | Zgodność | Podejście |
|---|---|---|
| Dodanie pola opcjonalnego | Zgodne | Dodaj; konsumenci ignorują nieznane pola |
| Dodanie pola wymaganego | Niezgodne | Podnieś wersję albo zapewnij wartość domyślną |
| Usunięcie pola | Niezgodne | Najpierw oznacz jako przestarzałe, obserwuj wywołujących, potem usuń |
| Zmiana typu pola | Niezgodne | Dodaj nową nazwę pola i podwójny zapis w okresie przejściowym |
| Zmiana zbioru wartości wyliczenia | Zależy | Dodawanie wartości jest zgodne; usuwanie nie |
Mechanizmy wspierające:
- Konsumenci używają „łagodnego parsowania”: ignorują nieznane pola i dostarczają domyślne dla brakujących;
- Serwer trzyma stare pola przez jakiś czas i zwraca nagłówek
Deprecationw odpowiedzi jako wskazówkę; - W Schema nie oznaczaj nowych pól jako
required, a przestarzałe pola taguj przezdeprecated: true.
Jak walidować kontrakty w CI?
Traktuj Schema jako część kodu i waliduj ją przy każdym commicie.
# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false
Uruchamiaj w CI trzy rzeczy:
- Poprawność samego Schema: czy deklaracja
$schemajest poprawna i czy$refsię rozwiązuje; - Czy dane testowe pasują do Schema: zapewnia, że dane testowe nie dryfują;
- Czy kontrakt jest zgodny wstecznie: porównaj z poprzednią wersją i sprawdź usunięte pola lub zmienione typy.
Trzeci punkt wymaga wsparcia narzędziowego (takiego jak openapi-diff lub json-schema-diff);
bez niego przynajmniej porównaj skryptem zmiany w tablicach required i zestawach kluczy properties.
3. Szybka ściągawka
| Pytanie | Zalecenie |
|---|---|
| Struktura odpowiedzi | {"data":..., "meta":...}, z kodami statusu HTTP dla błędów |
| Styl nazewnictwa | camelCase (frontend-first) lub snake_case (data-first), spójnie w całej witrynie |
| Format czasu | Łańcuchy RFC 3339 ze strefą czasową, nazwy pól kończące się At |
| Null | Użyj null dla braku wartości, pomijaj nieistotne pola, pominięcie w PATCH znaczy „nie zmieniaj” |
| Nazewnictwo logiczne | Prefiksy is/has/can, bez negacji, bez 0/1 |
| Wyliczenia | Łańcuchy, małe litery z podkreśleniami, dodaj enum w Schema |
| Format konfiguracji | JSON do walidacji programowej, TOML lub JSONC do ręcznej edycji |
| Dane testowe | Jeden baseline + nadpisania minimalnego diffu, poprawne względem Schema |
| Konflikty scalania | Ujednolić formatowanie, sortuj klucze, dziel pliki |
| Zgodność wersji | Dodawaj, nie zmieniaj, nie usuwaj; oznacz jako przestarzałe przed usunięciem |