Zmiany w API

Najlepsze praktyki wersjonowania API, dla wywołujących

6 min czytania

Wersjonowanie API to praktyka utrzymywania działania starego kontraktu po jego zmianie, żeby wywołujący mogli przechodzić na nowy według własnego harmonogramu, a nie waszego. To zdanie zawiera dwie decyzje, które mają znaczenie: co liczy się jako zmiana kontraktu, i jak długo stary kontrakt nadal działa. To, gdzie żyje numer wersji, o czym jest większość debat o wersjonowaniu, jest najmniej ważne z trzech i najłatwiejsze do zrobienia dobrze.

Kiedy API powinno być wersjonowane?

Wersjonujcie API tylko wtedy, gdy zmiana złamałaby poprawnego wywołującego. Zmiany addytywne, nowe pola, nowe endpointy, nowe opcjonalne parametry, nie potrzebują wersji; wywołujący napisani przeciwko staremu kontraktowi nadal działają, a nowa możliwość po prostu tam jest. Zmiana łamiąca kompatybilność potrzebuje wersji, ponieważ alternatywą jest dowiedzenie się przez wywołującego z błędu. Wersjonowanie każdego wydania, w tym addytywnych, uczy wywołujących, że wersje to szum, i przestają czytać istotne powiadomienia.

Praktyczny test jest taki sam jak w artykule o zmianach łamiących kompatybilność: jeśli wywołujący, który polegał tylko na udokumentowanym zachowaniu, musi coś zmienić, by nadal działać, zmiana potrzebuje wersji. Jeśli nie, wydajcie ją pod obecną wersją i napiszcie wpis changelogu.

Jaki schemat wersjonowania API powinno się użyć?

Użyjcie schematu, który wasi wywołujący najłatwiej zobaczą i ustawią, co dla większości publicznych API to wersja w ścieżce URL lub datowany nagłówek wersji. Cztery powszechne schematy różnią się mniej możliwościami, a bardziej tym, czego wymagają od wywołującego, i to jest właściwa podstawa wyboru.

SchematPrzykładCo musi zrobić wywołującyKto go używa
Ścieżka URL/v2/invoicesZmienić URL przy migracjiWiększość publicznych API REST
Nagłówek wersjiX-GitHub-Api-Version: 2022-11-28Wysłać nagłówek, lub zaakceptować domyślnyGitHub
Datowana wersja kontaStripe-Version: 2026-08-26Ustalić datę na żądanie lub na kontoStripe
Parametr zapytania/invoices?version=2Dodać parametrStarsze API; rzadko wybierane teraz
Typ mediówAccept: application/vnd.example.v2+jsonNegocjować typy treściPuryści; niewielu wywołujących sobie z tym radzi

Ścieżka URL jest najbardziej widoczna i najmniej elastyczna. Każdy wywołujący widzi, w jakiej jest wersji, czytając linię logu, a skok wersji to znajdź-i-zamień. Koszt: cała powierzchnia przesuwa się naraz, nie można zmienić kontraktu jednego endpointu bez wybicia nowej wersji dla wszystkich, więc wersje ścieżek bywają rzadkie i duże.

Nagłówek wersji utrzymuje URL-e stabilne i pozwala serwerowi wybrać domyślną dla wywołujących, którzy niczego nie wysyłają, tak jak działa wersjonowanie API REST GitHuba: wersja nazwana datą w X-GitHub-Api-Version, z najstarszą wspieraną wersją jako domyślną, żeby niewersjonowani wywołujący się nie zepsuli. Koszt: wersja jest niewidoczna w URL i łatwa do zapomnienia w nowym kliencie.

Datowana wersja konta to schemat nagłówka plus jeden dodatek: wersja jest przechowywana przy koncie, więc każde żądanie ją dostaje bez wysyłania niczego. Wersjonowanie API Stripe przypina każde konto do wersji, z jaką zostało utworzone, i pozwala żądaniu nadpisać to Stripe-Version. To schemat najbardziej przyjazny wywołującemu i najbardziej pracochłonny w prowadzeniu, ponieważ serwer musi tłumaczyć między każdą wspieraną wersją a obecną.

Parametr zapytania i typ mediów oba działają i oba zawodzą test widoczności na różne sposoby: parametr zapytania łatwo ginie przy budowaniu URL, a wersja typu mediów jest niewidoczna dla prawie każdego narzędzia, którym wywołujący by debugował. Schemat Stripe oparty na datach to najlepiej znany przykład podejścia z datą, a jak Stripe wersjonuje swoje API go omawia.

Jak wersjonowanie API wygląda w praktyce?

W praktyce wersja to nazwany zestaw zachowań, a serwer mapuje każde żądanie na jedno z nich. Kroki są takie same, niezależnie od tego, który schemat niesie nazwę.

  1. Nazywajcie wersje datą lub liczbą całkowitą, nie wersją semantyczną. API webowe to nie pakiet. Wywołujący nie mogą przypiąć wersji minor URL, więc v2 lub 2026-08-26 mówi wszystko, czego potrzebuje wywołujący, a wersjonowanie semantyczne sugeruje obietnicę kompatybilności, której schemat nie może spełnić.
  2. Trzymajcie wersję poza ścieżkami kodu, które się nią nie przejmują. Wersja powinna wybierać warstwę tłumaczenia na krawędzi, nie rozgałęziać logikę biznesową. Dwie pełne kopie bazy kodu to sposób, w jaki wersja kończy bez utrzymania.
  3. Dajcie każdej wersji domyślną i dokument. Wywołujący, którzy nie wysyłają wersji, dostają najstarszą wspieraną, nigdy najnowszą, żeby nieprzypięty klient nie zepsuł się w dniu wydania. Każda wersja ma stronę mówiącą, co zmieniło się od poprzedniej.
  4. Ustawcie okno wsparcia i opublikujcie je. Wytyczne Google dotyczące wersjonowania, AIP-185, wymagają rozsądnego, dobrze zakomunikowanego okresu przejściowego i zalecają 180 dni nawet dla funkcjonalności beta. Wybierzcie okno, zapiszcie je, i stosujcie bez renegocjacji na każdą wersję.
  5. Wycofujcie wersje tak, jak wycofujecie endpointy. Wersja po swoim oknie dostaje takie samo traktowanie jak każde zdeprecjonowane API: ogłoszenie, nagłówek Sunset (RFC 8594) w każdej odpowiedzi, przypomnienie w połowie drogi dla pozostałych wywołujących, i data usunięcia, która się trzyma.

Czym są v1 i v2 w API REST?

v1 i v2 to nazwy dla dwóch kontraktów, które ten sam serwer wspiera jednocześnie. v2 istnieje, ponieważ coś w v1 nie mogło zostać zmienione bez złamania jego wywołujących, więc zmiana trafiła do nowego kontraktu, a stary nadal działał. Numery nie sugerują, że v2 jest kompletne lub że v1 jest martwe; obie te rzeczy są prawdziwe tylko, jeśli dokumentacja tak mówi. v3, który pojawia się co kwartał, to znak, że wersjonowane są zmiany addytywne, lub że kontrakt nigdy nie był zaprojektowany, by wchłaniać zmianę. gRPC rozwiązuje ten sam problem inaczej: zmiany API gRPC i Protobuf opisuje wersjonowanie przez nazwę pakietu w pliku .proto zamiast ścieżki URL, oraz format wire, w którym zmiana nazwy pola jest darmowa, ale zmiana jego numeru to zmiana łamiąca kompatybilność, której żadna wywołująca REST nie rozpoznałaby jako ryzykownej.

Co powinna ogłaszać zmiana wersji?

Zmiana wersji powinna ogłaszać, co się łamie, kogo dotyczy, jak migrować, i jak długo poprzednia wersja nadal działa. Wpis ma taką samą formę jak każdy inny wpis o zmianie łamiącej kompatybilność, plus jedna linia deklarująca okno wsparcia. Oto jeden dla API wersjonowanego nagłówkiem:

Wersja API 2026-11-01 jest dostępna. Wersja 2025-06-15 jest wspierana do 1 listopada 2027. Nowość w 2026-11-01: GET /invoices zwraca amount w najmniejszych jednostkach jako liczbę całkowitą zamiast ciągu dziesiętnego, a zdeprecjonowane pole customer_name jest usuwane na rzecz obiektu customer. Dotyczy wywołujących na 2025-06-15, którzy parsują amount jako ciąg, co jest domyślne dla nieprzypiętych klientów utworzonych przed czerwcem 2025. Migracja: parsujcie amount jako liczbę całkowitą i czytajcie nazwę z customer.name. Przypnijcie X-Api-Version: 2026-11-01, gdy będziecie gotowi. Nic się nie zmienia dla wywołujących, którzy nie przypinają wersji.

Ostatnie zdanie jest tym, które pozwala większości czytelniczek przestać czytać, i należy do każdego ogłoszenia wersji. Strona przykłady changelogu zawiera wpisy z API, które wersjonują w ten sposób, a różnica między dobrymi a resztą leży głównie w tym ostatnim zdaniu.

Kto jest informowany, gdy wersja się zmienia?

Wszyscy na starej wersji, indywidualnie, i changelog dla wszystkich innych. Zmiana wersji to jedyny przypadek, w którym “opublikowaliśmy coś na ten temat” gwarantowanie pomija dokładnie tych wywołujących, którzy mają znaczenie: tych, którzy przypięli wersję dwa lata temu i od tego czasu nie przeczytali notatki o wydaniu. Dane o użyciu odpowiadają, kim oni są; powiadomienie musi do nich dotrzeć tam, gdzie jest ich kod, w nagłówkach odpowiedzi i w wiadomości do właścicielki konta.

W pętli, którą prowadzimy, wpis ogłaszający wersję jest przygotowywany z pull requesta, który ją wydaje, recenzowany przez osobę, i publikowany na kanale i widgecie, gdzie wersjonowany klient może go odczytać jako JSON. Każdy, czyja opinia z widżetu prosiła o tę zmianę lub zgłaszała błąd, który ona rozwiązuje, i stała się issue na GitHubie zamykanym przez ten pull request, jest informowany w tym issue, gdy tylko wpis wchodzi na żywo. Mechanizm jest taki sam jak dla każdego wpisu; skok wersji to po prostu wpis z najwyższą stawką.

FAQ

Czy każda zmiana API powinna dostać nową wersję? Nie. Tylko zmiany łamiące kompatybilność. Zmiany addytywne są wydawane pod obecną wersją z wpisem changelogu. Wersjonowanie zmian addytywnych trenuje wywołujących, by ignorowali wersje.

Czy wersjonowanie URL jest lepsze niż nagłówkiem? Wersjonowanie URL jest łatwiejsze do zobaczenia dla wywołujących i trudniejsze dla was do ewoluowania stopniowo; wersjonowanie nagłówkiem jest odwrotnie. Dla publicznego API z wieloma małymi klientami wersjonowanie URL zawodzi rzadziej. Dla dużego API z warstwą tłumaczenia datowana wersja nagłówkowa lepiej się skaluje.

Ile wersji powinno być wspieranych naraz? Tak mało, jak pozwala wasze okno wsparcia, i nigdy nieograniczona liczba. Dwie lub trzy równoczesne wersje to normalne; więcej zwykle oznacza, że wersje nie są wycofywane.

Co powinny dostawać niewersjonowane żądania? Najstarszą wspieraną wersję, żeby istniejący nieprzypięci klienci nadal działali, z nagłówkiem odpowiedzi mówiącym im, którą wersję dostali.


Twierdzenia techniczne w tym artykule nie zostały niezależnie zweryfikowane. Jeśli coś się nie zgadza, daj nam znać, a poprawimy to.

Powiązane w changeloop: Dokumentacja dla deweloperów, Przykłady changeloga

changeloop
Zespół, który tworzy changelog zamykający pętlę. Użytkownicy o coś proszą, Twój zespół to dostarcza, proszący się dowiaduje.