wersjonowanie rest api

Wersjonowanie REST API, czyli z v1 do v2

Chyba każdemu programiście, prędzej czy później zdarzy się, że będzie musiał wprowadzić zmiany w swoich endpoint-ach REST API. Zmiany te okażą się łamać kompatybilność wsteczną, a logika używana jest przez zewnętrznych klientów. Co wtedy? Wtedy v2.

Po co to całe wersjonowanie REST API?

Mogłoby się wydawać, że problem jest błachy, ale czy na pewno? Wyobraźmy sobie sytuację, że ukończyliśmy nasz nowy system, który udostępnia piękne REST API. Po jakimś czasie dostajemy zlecenie nowych funkcjonalności, które bezpośrednio ingerują w już istniejące rzeczy. Przykładowo zmienia nam się endpoint, który służył do wyliczania rocznego przychodu, bo zaczyna on uwzględniać dodatkowe parametry na wejściu, zwraca inny typ danych, czy po prostu zmienił się sposób wyliczania.

Oczywiście, możemy if-ować, że jeżeli wartość x została ustawiona, to licz w sposób A, inaczej w sposób B. Nie oszukujmy się jednak, takie rozwiązanie zapędzi nas w kozi róg. Inne potencjalne wyjście, to zrobienie nowego endpoint-u, z innym URL i zalecenie, aby w nowych implementacjach z niego korzystać. To, jak się pewnie domyślasz także jest kiepskim wyjściem, bo łamiemy semantykę REST, nie ma pojedynczego źródła prawdy, a dodatkowo, co, jeśli zmieni się np. prawo podatkowe i trzeba będzie znowu powtórzyć ten zabieg?

Jedyne słuszne wyjście to wersjonowanie REST API

Zamiast przesadnie rzeźbić, możemy zastosować podejście słuszne, czyli wersjonowanie REST API. Polega ono na implementacji nowych endpoint-ów przy zachowaniu kompatybilności wstecznej, gdyż stare nie są zmieniane. Ważne jest jednak dodanie do zapytania informacji o wersji API, z której chcemy skorzystać, a następnie odpowiednio je obsłużyć po stronie serwera – kierując je do odpowiedniego kontrolera / metody handle’ującej.

Informację o wersji zwyczajowo umieszczamy w jednym z trzech miejsc zapytania:

  • W samym adresie url, jako przedrostek do właściwych endpoint-ów: https://api.orbisbit.com/v2/incomes. Możemy również desygnować subdomenę w różnych wariacjach: https://api.v2.orbisbit.com/incomes lub https://apiv2.orbisbit.com/incomes. Inne wyjście to parametr zapytania https://api.orbisbit.com/incomes?ver=2.
  • Jako wartość nagłówka, która zostanie przekazana wraz z request-em. Przykładowo API-Version: 2.
  • W specyfikacji media type nagłówka Accept. Zwyczajowo w REST API dodajemy nagłówek Accept, który zawiera informację o tym, w jakiej postaci chcemy otrzymać odpowiedź: Accept: application/json. Możemy skorzystać z niego i w wartości, dodatkowo określić wersję REST API, z której chcemy skorzystać: Accept: application/json;v=2.

Która opcja jest najlepsza?

Opcji wersjonowania REST API mamy 5 (liczę wszystkie możliwości). Nasuwa się więc pytanie, która z nich będzie najlepsza? Z mojego doświadczenia wynika, że wersja z własnym nagłówkiem jest łatwiejsza dla klientów i czystsza, gdyż nie zaśmieca właściwego adresu URL. Z tego samego też powodu jest mało odporna na błędy, bo nie widać explicite z jakiej wersji korzystamy. Więc obie opcje mają swoje wady i zalety.

Na pewno nie polecam wersjonowania po wartości query param, gdyż nie jest to ani czytelne, ani wygodne. W kwestii poddomeny, moim skromnym zdaniem jest to przerost formy nad treścią, ale nigdy nie spotkałem się z sytuacją, kiedy takie rozwiązanie byłoby optymalne. Spośród nagłówków, najlepsza semantycznie wydaje się opcja z dopisaniem wersji do wartości Accept. Może ona być jednak ciut trudniejsza w zaimplementowaniu, jednak nie musimy dodawać obsługi nowego nagłówka.

Na stole zostają więc takie opcje:

  • v2 w url, poprzedzające część ścieżki,
  • własny nagłówek,
  • dodanie informacji w wartości nagłówka Accept.

Wybierz tę, która pasuje w Twoim projekcie. Teorię mamy za sobą, teraz, jak to zaimplementować?

Implementacja

Implementacja mechanizmu wybierającego wersję zależy od zastosowanej technologii i framework-a. Możemy wersję określić już na etapie routing-u i jawnie wskazać dla każdego endpoint-u jego wersję:

export class HelloController {
  @Get('v1/hello')
  helloV1(): string {
    return 'hello world from v1';
  }

  @Get('v2/hello')
  helloV2(): string {
    return 'hello world from v2';
  }
}

Jeżeli chcemy zastosować wersjonowanie REST API w nagłówku, bądź wynieść je gdzieś wyżej z adresu, to musimy to zrobić korzystając z narzędzi używanej technologii – czego tutaj niestety nie pokażę. Zademonstruję jednak sposób w jaki mi, najczęściej zdarza się wskazywać, które fragmenty kodu odpowiadają za konkretną wersję.

Bardzo lubię dedykowane klasy kontrolujące pod wersję, a do wskazania, która ma być obsługiwana stosuję dekorator nad klasą.

@Controller({
  version: '1',
})
export class HelloControllerV1 {
  @Get('hello')
  hello(): string {
    return 'hello world from v1';
  }
}

@Controller({
  version: '2',
})
export class HelloControllerV2 {
  @Get('hello')
  hello(): string {
    return 'hello world from v2';
  }
}

Dobrym wyjściem jest także tworzenie osobnej struktury plików i katalogów dla nowych wersji. Przykładowo, struktura folderu rest-api, w naszym module, może wyglądać następująco:

  • api-rest
    • v1
      • controllers
      • requests
    • v2
      • controllers
      • requests

Podsumowanie

  • Jeżeli musimy wprowadzić zmiany w swoich endpoint-ach REST API, które łamią kompatybilność wsteczną, to powinniśmy zastosować wersjonowanie.
  • Należy wymagać od klienta podania informacji o wersji w przesyłanym zapytaniu.
  • Zaleca się umieszczać wartość wersji w: adresie url, własnym nagłówku, w wartości nagłówka Accept.
Autor wpisu

blog@orbisbit.com

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Sprawdź również
  • CQRS – Command Query Responsibility Segregation

    Command Query Responsibility Segregation czyli CQRS. Jest to wzorzec projektowy, który rozdziela zadania odczytu i zapisu do osobnych modeli. Sprawdź ten wpis, aby dowiedzieć się kiedy i jak z niego skorzystać.

    Zobacz wpis

  • GRASP – kolejny zbiór zasad Clean Code do zapamiętania

    Pewnie większość z Was słyszała o zasadach SOLID. Są one bardzo rozpowszechnione i dosyć często stosowane, ale czy słyszeliście o GRASP? General Responsibility Assignment Software Patterns, to kolejna dawka zasad czystego kodu do zapamiętania.

    Zobacz wpis

  • Wzorzec strategia (strategy pattern)

    Jeżeli masz dość if-ologii w swoim kodzie, to konieczne sprawdź czym jest czynnościowy wzorzec projektowy strategia. Pozwala on mądrze obsługiwać różne scenariusze w procesie i jednocześnie być fancy pod względem zasad SOLID.

    Zobacz wpis