Najlepsze praktyki dotyczące wersji API? [Zamknięte]

877

Czy są jakieś znane instrukcje lub najlepsze praktyki dotyczące wersji interfejsu API REST usługi internetowej?

Zauważyłem, że AWS robi wersjonowanie według adresu URL punktu końcowego . Czy to jedyny sposób, czy istnieją inne sposoby osiągnięcia tego samego celu? Jeśli istnieje wiele sposobów, jakie są zalety każdego z nich?

Swaroop CH
źródło

Odpowiedzi:

682

To dobre i trudne pytanie. Temat projektowania URI jest jednocześnie najważniejszą częścią interfejsu API REST, a zatem potencjalnie długoterminowym zobowiązaniem wobec użytkowników tego interfejsu API .

Ponieważ ewolucja aplikacji i, w mniejszym stopniu, jej API jest faktem i że jest nawet podobna do ewolucji pozornie złożonego produktu, takiego jak język programowania, projekt URI powinien mieć mniej naturalnych ograniczeń i powinien zostać zachowany z czasem . Im dłuższa żywotność aplikacji i interfejsu API, tym większe zaangażowanie użytkowników aplikacji i interfejsu API.

Z drugiej strony, innym faktem jest to, że trudno jest przewidzieć wszystkie zasoby i ich aspekty, które zostałyby zużyte przez API. Na szczęście nie jest konieczne projektowanie całego API, które będzie używane do Apokalipsy . Wystarczy poprawnie zdefiniować wszystkie punkty końcowe zasobu i schemat adresowania każdego zasobu i instancji zasobu.

Z czasem może być konieczne dodanie nowych zasobów i nowych atrybutów do każdego konkretnego zasobu, ale metoda stosowana przez użytkowników API w celu uzyskania dostępu do określonych zasobów nie powinna ulec zmianie, gdy schemat adresowania zasobów stanie się publiczny, a zatem ostateczny.

Ta metoda ma zastosowanie do semantyki czasownika HTTP (np. PUT powinna zawsze aktualizować / zamieniać) i kodów statusu HTTP, które są obsługiwane we wcześniejszych wersjach API (powinny one dalej działać, aby klienci API, którzy pracowali bez interwencji człowieka, mogli kontynuować pracę tak).

Ponadto, ponieważ osadzenie wersji API w URI zakłóciłoby koncepcję hipermediów jako silnika stanu aplikacji (podanego w rozprawie doktorskiej Roy T. Fieldingsa) poprzez posiadanie adresu zasobu / URI, który zmieniałby się z czasem, doszedłbym do wniosku, że API wersje nie powinny być przechowywane w identyfikatorach URI zasobów przez długi czas, co oznacza, że identyfikatory URI zasobów, na których użytkownicy API mogą polegać, powinny być linkami stałymi .

Oczywiście możliwe jest osadzenie wersji interfejsu API w podstawowym identyfikatorze URI, ale tylko do uzasadnionych i ograniczonych zastosowań, takich jak debugowanie klienta interfejsu API, który działa z nową wersją interfejsu API. Takie wersje API powinny być ograniczone czasowo i dostępne tylko dla ograniczonych grup użytkowników API (np. Podczas zamkniętych bet). W przeciwnym razie zobowiązujesz się tam, gdzie nie powinieneś.

Kilka uwag na temat utrzymania wersji API, które mają na nich datę ważności. Wszystkie platformy / języki programowania powszechnie używane do wdrażania usług sieciowych (Java, .NET, PHP, Perl, Rails itp.) Pozwalają na łatwe powiązanie punktów końcowych usługi internetowej z podstawowym identyfikatorem URI. W ten sposób łatwo jest zebrać i zachować zbiór plików / klas / metod oddzielnie dla różnych wersji API .

Z POV użytkowników API łatwiej jest pracować i łączyć się z określoną wersją API, gdy jest to oczywiste, ale tylko przez ograniczony czas, tj. Podczas programowania.

Z POV opiekuna API łatwiej jest utrzymywać równolegle różne wersje API, wykorzystując systemy kontroli źródła, które przeważnie działają na plikach jako najmniejsze jednostki wersji (kodu źródłowego).

Jednak przy wersjach interfejsu API wyraźnie widocznych w URI istnieje zastrzeżenie: można również sprzeciwić się temu podejściu, ponieważ historia interfejsu API staje się widoczna / widoczna w projekcie URI, a zatem jest podatna na zmiany w czasie, co jest niezgodne z wytycznymi REST. Zgadzam się!

Sposobem na obejście tego uzasadnionego sprzeciwu jest zaimplementowanie najnowszej wersji interfejsu API w ramach podstawowego URI interfejsu API bez wersji. W takim przypadku programiści klienta API mogą wybrać:

  • rozwijać się w stosunku do najnowszej (zobowiązując się do utrzymania aplikacji chroniącej ją przed ewentualnymi zmianami API, które mogą uszkodzić źle zaprojektowanego klienta API ).

  • powiązać z określoną wersją interfejsu API (która staje się widoczna), ale tylko przez ograniczony czas

Na przykład, jeśli API v3.0 jest najnowszą wersją API, następujące dwa powinny być aliasami (tzn. Zachowywać się identycznie dla wszystkich żądań API):

http: // shonzilla / api / klienci / 1234 
http: // shonzilla / api /v3.0 / klienci / 1234
http: // shonzilla / api / v3 / klienci / 1234

Ponadto klienci API, którzy nadal próbują wskazać stary interfejs API, powinni zostać poinformowani o korzystaniu z najnowszej poprzedniej wersji interfejsu API, jeśli używana wersja interfejsu API jest przestarzała lub nie jest już obsługiwana . Dostęp do któregoś z przestarzałych identyfikatorów URI, takich jak te:

http: // shonzilla / api / v2.2 / klienci / 1234
http: // shonzilla / api / v2.0 / klienci / 1234
http: // shonzilla / api / v2 / klienci / 1234
http: // shonzilla / api / v1.1 / klienci / 1234
http: // shonzilla / api / v1 / klienci / 1234

powinien zwrócić dowolny z 30x kodów stanu HTTP wskazujących przekierowanie, które są używane w połączeniu z Locationnagłówkiem HTTP, który przekierowuje do odpowiedniej wersji identyfikatora URI zasobu, który pozostaje tym:

http: // shonzilla / api / klienci / 1234

Istnieją co najmniej dwa kody statusu przekierowania HTTP odpowiednie dla scenariuszy wersjonowania API:

  • 301 Przeniesiono na stałe, wskazując, że zasób z żądanym identyfikatorem URI jest trwale przeniesiony do innego identyfikatora URI (powinien to być bezpośredni link do instancji zasobu, który nie zawiera informacji o wersji interfejsu API). Tego kodu stanu można użyć do wskazania przestarzałej / nieobsługiwanej wersji interfejsu API, informując klienta interfejsu API, że wersjonowany identyfikator URI zasobu został zastąpiony przez bezpośredni link zasobu .

  • 302 Znaleziono wskazujące, że żądany zasób tymczasowo znajduje się w innej lokalizacji, podczas gdy żądany identyfikator URI może być nadal obsługiwany. Ten kod stanu może być przydatny, gdy identyfikatory URI bez wersji są tymczasowo niedostępne i żądanie należy powtórzyć przy użyciu adresu przekierowania (np. Wskazując na identyfikator URI z osadzoną wersją APi) i chcemy poinformować klientów, aby nadal go używali (tj. permalinki).

  • inne scenariusze można znaleźć w rozdziale Przekierowanie 3xx specyfikacji HTTP 1.1

Shonzilla
źródło
142
Używanie numeru wersji w adresie URL nie powinno być uważane za złą praktykę, gdy zmienia się podstawowa implementacja. „Gdy interfejs usługi zmienia się w sposób niezgodny z poprzednimi wersjami, w rzeczywistości powstała zupełnie nowa usługa ... Z punktu widzenia klienta usługa jest niczym więcej niż interfejsem i pewnymi cechami niefunkcjonalnymi. .Jeśli interfejs usługi zmienia się w sposób niezgodny z poprzednimi wersjami, nie reprezentuje ona już wystąpienia oryginalnej usługi, ale jest zupełnie nową usługą. ” ibm.com/developerworks/webservices/library/ws-version
benvolioT
7
Czy zastanawiasz się nad dodaniem nagłówka z numerem wersji, aby mógł zostać sprawdzony przez klientów lub programistów?
webclimber 18.10.10
11
Zobacz także użycie nagłówka Accept w celu wskazania wersji, której oczekuje klient: blog.steveklabnik.com/2011/07/03/…
Weston Ruter,
52
Na koniec: powiedziałbym, że interfejs API, który jest przestarzały i nie jest już obsługiwany, powinien zwrócić 410 Gone, ponieważ przekierowanie może oznaczać, że nowa lokalizacja jest kompatybilna, gdy nie jest. Jeśli interfejs API jest po prostu przestarzały, ale nadal istnieje, Warningopcjonalnym może być nagłówek HTTP odpowiedzi.
Michael Stum
22
Jak radzisz sobie z klientami, którzy już używają stabilnego adresu URL, np. Shonzilla / api / users / 1234 i chcesz uaktualnić do nowej wersji? jak zmusić ich do dodania V2 (starej) do adresu URL?
Dejell
273

Adres URL NIE powinien zawierać wersji. Ta wersja nie ma nic wspólnego z „pomysłem” żądanego zasobu. Powinieneś pomyśleć o adresie URL jako ścieżce do koncepcji, którą chcesz - a nie o tym, jak chcesz zwrócić przedmiot. Wersja dyktuje reprezentację obiektu, a nie jego koncepcję. Jak powiedzieli inni plakaty, należy określić format (w tym wersję) w nagłówku żądania.

Jeśli spojrzysz na pełne żądanie HTTP dla adresów URL, które mają wersje, wygląda to tak:

(BAD WAY TO DO IT):

http://company.com/api/v3.0/customer/123
====>
GET v3.0/customer/123 HTTP/1.1
Accept: application/xml

<====
HTTP/1.1 200 OK
Content-Type: application/xml
<customer version="3.0">
  <name>Neil Armstrong</name>
</customer>

Nagłówek zawiera wiersz zawierający reprezentację, o którą prosisz („Accept: application / xml”). Właśnie tam powinna iść wersja. Wydaje się, że wszyscy zastanawiają się nad tym, że możesz chcieć tego samego w różnych formatach i że klient powinien móc zapytać o to, czego chce. W powyższym przykładzie klient prosi o JAKĄKOLWIEK reprezentację zasobu XML - nie jest to prawdziwa reprezentacja tego, czego chce. Serwer teoretycznie mógłby zwrócić coś całkowicie niezwiązanego z żądaniem, o ile był to XML, i musiałby zostać przeanalizowany, aby zdać sobie sprawę, że jest on błędny.

Lepszym sposobem jest:

(GOOD WAY TO DO IT)

http://company.com/api/customer/123
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+xml

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+xml
<customer>
  <name>Neil Armstrong</name>
</customer>

Ponadto, powiedzmy, że klienci uważają, że XML jest zbyt szczegółowy i teraz zamiast tego chcą JSON. W innych przykładach musiałbyś mieć nowy adres URL dla tego samego klienta, aby uzyskać:

(BAD)
http://company.com/api/JSONv3.0/customers/123
  or
http://company.com/api/v3.0/customers/123?format="JSON"

(lub coś podobnego). Gdy w rzeczywistości każde żądanie HTTP zawiera poszukiwany format:

(GOOD WAY TO DO IT)
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+json

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+json

{"customer":
  {"name":"Neil Armstrong"}
}

Korzystając z tej metody, masz dużo więcej swobody w projektowaniu i faktycznie przestrzegasz oryginalnej idei REST. Możesz zmieniać wersje bez zakłócania działania klientów lub stopniowo zmieniać klientów wraz ze zmianą interfejsów API. Jeśli zdecydujesz się przestać obsługiwać reprezentację, możesz odpowiedzieć na żądania za pomocą kodu stanu HTTP lub kodów niestandardowych. Klient może również sprawdzić, czy odpowiedź ma poprawny format i zweryfikować XML.

Istnieje wiele innych zalet i niektóre z nich omawiam tutaj na moim blogu: http://thereisnorightway.blogspot.com/2011/02/versioning-and-types-in-resthttp-api.html

Ostatni przykład pokazujący, jak złe jest umieszczanie wersji w adresie URL. Powiedzmy, że chcesz mieć jakieś informacje wewnątrz obiektu i masz wersje różnych obiektów (klienci mają v3.0, zamówienia to v2.0, a obiekt shipto to v4.2). Oto nieprzyjemny adres URL, który musisz podać w kliencie:

(Another reason why version in the URL sucks)
http://company.com/api/v3.0/customer/123/v2.0/orders/4321/
jeremyh
źródło
10
Obsługa niezależnej wersji umowy o dane i wersji umowy o świadczenie usług w nagłówku Zaakceptuj wydaje się nieporządna, podobnie jak w przypadku adresu URL. Czy są jakieś inne opcje ? Również jeśli mam wiele punktów końcowych (mydło, odpoczynek), czy powinno to również zostać zaznaczone w polu Akceptuję i pozwól usłudze routingu na końcu serwera zdecydować o kierunku do właściwego punktu końcowego LUB czy dopuszczalne jest zakodowanie punktu końcowego w adresie URL?
ideafountain
117
Nie mogę się z tym zgodzić, przynajmniej do ostatniego powodu. Wygląda na to, że różne części identyfikatora URI mają różne wersje. Ale nie o to chodzi w wersji API. Chodzi o to, aby mieć JEDNĄ wersję dla CAŁEGO zasobu. Jeśli zmienisz wersje, jest to inny zasób interfejsu API. Dlatego nie ma sensu widzieć company.com/api/v3.0/customer/123/v2.0/orders/4321, a raczej company.com/api/v3.0/customer/123/orders/4321 Nie wersjonujesz żadnej części zasobu, wersjonujesz zasób jako całość.
fool4jesus
90
Semantycznie użycie numeru wersji w nagłówku wydaje się lepsze. Ale jest o wiele bardziej praktyczny przy użyciu adresu URL: mniej podatny na błędy, najlepiej debugowany, łatwo widziany przez programistów, łatwo modyfikowalny w testach pozostałych klientów.
Daniel Cerecedo
7
Myślę, że ZŁE / DOBRE upraszcza pytanie. API oznacza „interfejs programowania aplikacji”, a interfejsy kontroli wersji wydają się być bardzo dobrym pomysłem. Interfejsy API tak naprawdę nie służą wyłącznie do udostępniania zasobów. Odrębne jest to, że niektórzy mówią o interfejsach, a inni o zasobach. Jeśli spojrzysz dokładnie na Google Maps API na karcie sieci, zobaczysz, że zawierają one numer wersji API w adresie URL. Na przykład: maps.google.com/maps/api/jsv2 podczas uwierzytelniania. Jsv2 to numer interfejsu API.
Tom Gruner
6
@Gili: Właściwie nie powinieneś już używać, -xponieważ jest przestarzały przez RFC6648 .
Jonathan W
98

Uznaliśmy, że praktyczne i przydatne jest umieszczenie wersji w adresie URL. Łatwo jest na pierwszy rzut oka stwierdzić, z czego korzystasz. Wykonujemy aliasy / foo do / foo / (najnowsze wersje) w celu ułatwienia użytkowania, krótszych / czystszych adresów URL itp., Zgodnie z sugerowaną odpowiedzią.

Utrzymanie wstecznej kompatybilności na zawsze jest często kosztowne i / lub bardzo trudne. Wolimy powiadomić z wyprzedzeniem o wycofaniu, przekierowaniach takich jak sugerowane tutaj, dokumentach i innych mechanizmach.

Yoav Shapira
źródło
5
Akceptowana odpowiedź może być poprawna i najczystsza. Jednak dla programisty i codziennego użytkownika API jest to z pewnością najłatwiejszy w użyciu i konfiguracji. Najbardziej pragmatyczne podejście. Jak wskazali inni Google i Amazon, również stosują to podejście.
Muhammad Rehan Saeed,
46

Zgadzam się, że wersjonowanie reprezentacji zasobów lepiej odpowiada podejściu REST ... ale jednym dużym problemem z niestandardowymi typami MIME (lub typami MIME, które dołączają parametr wersji) jest słaba obsługa zapisu w nagłówkach Accept i Content-Type w HTML i JavaScript.

Na przykład nie jest możliwe, aby IMO POST używał następujących nagłówków w formularzach HTML5, aby utworzyć zasób:

Accept: application/vnd.company.myapp-v3+json
Content-Type: application/vnd.company.myapp-v3+json 

To dlatego, że HTML5 enctypeatrybut stanowi wyliczenie, więc coś innego niż zwykle application/x-www-formurlencoded, multipart/form-datai text/plainsą nieważne.

... i nie jestem pewien, czy jest obsługiwany we wszystkich przeglądarkach w HTML4 (który ma bardziej luźny atrybut encytpe, ale byłby problem z implementacją przeglądarki, czy typ MIME został przekazany)

Z tego powodu uważam, że najbardziej odpowiednią drogą do wersji jest URI, ale akceptuję, że nie jest to „poprawny” sposób.

Kevsy
źródło
14
Zakładając trasę, w której wersjonowanie zostało zdefiniowane w nagłówkach, można powiedzieć, że formularze HTML korzystające z przesyłania formularzy rodzimych zawsze będą korzystać z najnowszej wersji interfejsu API, ponieważ nie przekażą konkretnej wersji, do której chcą się stosować. Jednak żądania XHR w rzeczywistości umożliwiają zmianę akceptacji i czytanie nagłówków typu zawartości. Tak więc podstawowe formy są naprawdę jedynym problemem.
Kyle Hayes,
Nie jestem pewien, czy zgadzam się, że URI jest najbardziej odpowiedni, ale fakt, że Content-Type nie działa z formularzami, jest naprawdę bardzo ważny.
wprl
2
@Kyle, widziałem gdzieś bloga, który mówi, że jeśli nie podasz wersji w nagłówku żądania, najlepiej jest powrócić z pierwszą wersją interfejsu API, a nie najnowszą, aby uzyskać najlepszą zgodność.
andy,
To naprawdę ma dla mnie wiele sensu, kiedy o tym myślę.
Kyle Hayes
@KyleHayes nie zapomina o elementach iframe, video / embed i innych tagach typu „src / href”.
pllee
21

Umieść swoją wersję w URI. Jedna wersja interfejsu API nie zawsze obsługuje typy z innej, więc argument, że zasoby są migrowane tylko z jednej wersji do drugiej, jest po prostu błędny. To nie to samo, co zmiana formatu z XML na JSON. Typy mogą nie istnieć lub mogły ulec zmianie semantycznej.

Wersje są częścią adresu zasobu. Trasujesz z jednego interfejsu API do drugiego. Ukrywanie adresowania w nagłówku nie jest RESTful.

Sean O'Dell
źródło
13

Istnieje kilka miejsc, w których można przeprowadzić kontrolę wersji w interfejsie API REST:

  1. Jak wspomniano, w URI. Może to być łatwe do wykonania, a nawet estetyczne, jeśli przekierowania i tym podobne są dobrze stosowane.

  2. W nagłówku Accepts:, więc wersja ma typ pliku. Jak „mp3” vs „mp4”. To również zadziała, chociaż IMO działa nieco mniej ładnie niż ...

  3. W samym zasobie. Wiele formatów plików ma osadzone numery wersji, zwykle w nagłówku; pozwala to nowszemu oprogramowaniu „po prostu działać”, rozumiejąc wszystkie istniejące wersje typu pliku, podczas gdy starsze oprogramowanie może działać, jeśli określona jest nieobsługiwana (nowsza) wersja. W kontekście interfejsu API REST oznacza to, że Twoje identyfikatory URI nigdy nie muszą się zmieniać, a jedynie twoja odpowiedź na konkretną wersję przekazanych danych.

Widzę powody, dla których warto zastosować wszystkie trzy podejścia:

  1. jeśli lubisz robić nowe interfejsy API „czystego wycierania” lub w przypadku poważnych zmian wersji, jeśli chcesz mieć takie podejście.
  2. jeśli chcesz, aby klient wiedział, zanim zrobi PUT / POST, czy będzie działać, czy nie.
  3. czy jest w porządku, jeśli klient musi wykonać PUT / POST, aby dowiedzieć się, czy zadziała.
pjz
źródło
8

Wersjonowanie interfejsu API REST jest analogiczne do wersjonowania dowolnego innego interfejsu API. Drobne zmiany można wprowadzić na miejscu, poważne zmiany mogą wymagać zupełnie nowego interfejsu API. Najłatwiej jest zaczynać od zera za każdym razem, czyli wtedy, gdy umieszczenie wersji w adresie URL ma sens. Jeśli chcesz ułatwić życie klientowi, staraj się zachować kompatybilność wsteczną, co możesz zrobić z wycofaniem (trwałe przekierowanie), zasobami w kilku wersjach itp. Jest to bardziej kłopotliwe i wymaga większego wysiłku. Ale właśnie to zachęca REST w „Fajne identyfikatory URI się nie zmieniają”.

Ostatecznie jest tak jak każdy inny projekt API. Porównaj wysiłek z wygodą klienta. Rozważ zastosowanie wersji semantycznej dla swojego interfejsu API, co wyjaśni klientom, w jaki sposób nowa wersja jest kompatybilna wstecz.

Alexander Torstling
źródło