Jaki jest zalecany wzorzec planowania punktów końcowych REST dla przewidywanych zmian

25

Próba zaprojektowania interfejsu API dla aplikacji zewnętrznych z prognozowaniem zmian nie jest łatwa, ale odrobina przemyślenia może ułatwić życie później. Próbuję ustanowić schemat, który będzie obsługiwał przyszłe zmiany, pozostając kompatybilnym wstecz, pozostawiając programy obsługi wcześniejszych wersji.

Głównym problemem tego artykułu jest to, jaki wzór należy stosować dla wszystkich zdefiniowanych punktów końcowych dla danego produktu / firmy.

Schemat podstawowy

Biorąc pod uwagę podstawowy szablon adresu URL https://rest.product.com/, opracowałem, że wszystkie usługi rezydują /apiwraz z /authinnymi punktami końcowymi nie opartymi na spoczynku, takimi jak /doc. Dlatego mogę ustalić podstawowe punkty końcowe w następujący sposób:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

Punkty końcowe usługi

Teraz same punkty końcowe. Troska o POST, GET, DELETEnie jest głównym celem tego artykułu i jest troska o tych samych działań.

Punkty końcowe można podzielić na przestrzenie nazw i akcje. Każde działanie musi również prezentować się w sposób wspierający zasadnicze zmiany typu zwrotu lub wymaganych parametrów.

Korzystając z hipotetycznej usługi czatu, w której zarejestrowani użytkownicy mogą wysyłać wiadomości, możemy mieć następujące punkty końcowe:

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

Teraz, aby dodać obsługę wersji dla przyszłych zmian API, które mogą być nieaktualne. Możemy dodać podpis wersji po /api/lub po /messages/. Biorąc pod uwagę sendpunkt końcowy, moglibyśmy mieć następujące dla v1.

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

Moje pierwsze pytanie brzmi: jakie jest zalecane miejsce dla identyfikatora wersji?

Zarządzanie kodem kontrolera

Więc teraz ustaliliśmy, że musimy obsługiwać wcześniejsze wersje, dlatego musimy jakoś obsługiwać kod dla każdej z nowych wersji, która z czasem może przestać działać. Zakładając, że piszemy punkty końcowe w Javie, moglibyśmy to zarządzać poprzez pakiety.

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

Ma to tę zaletę, że cały kod został oddzielony przez przestrzenie nazw, w których każda zmiana łamania oznaczałaby, że nowa kopia punktów końcowych usługi. Szkoda polega na tym, że cały kod musi zostać skopiowany, a poprawki błędów, które mają być zastosowane do nowych, a wcześniejsze wersje muszą być zastosowane / przetestowane dla każdej kopii.

Innym podejściem jest tworzenie procedur obsługi dla każdego punktu końcowego.

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

To teraz izoluje wersjonowanie do każdego punktu końcowego i sprawia, że ​​poprawki błędów są kompatybilne z tylnym portem, ponieważ w większości przypadków muszą być zastosowane tylko raz, ale oznacza to, że musimy wykonać nieco więcej pracy dla każdego pojedynczego punktu końcowego, aby to obsługiwać.

Zatem jest moje drugie pytanie: „Jaki jest najlepszy sposób zaprojektowania kodu usługi REST do obsługi wcześniejszych wersji”.

Brett Ryan
źródło

Odpowiedzi:

13

Zatem jest moje drugie pytanie: „Jaki jest najlepszy sposób zaprojektowania kodu usługi REST do obsługi wcześniejszych wersji”.

Bardzo starannie zaprojektowany i ortogonalny interfejs API prawdopodobnie nigdy nie będzie musiał być zmieniany w sposób niezgodny z poprzednimi wersjami, więc naprawdę najlepszym sposobem jest nie mieć przyszłych wersji.

Oczywiście prawdopodobnie nie dostaniesz tego za pierwszym razem; Więc:

  • Wersja interfejsu API, tak jak planujesz (i to jest wersja interfejsu API, a nie poszczególne metody) i hałasuj na ten temat. Upewnij się, że Twoi partnerzy wiedzą, że interfejs API może się zmienić, a ich aplikacje powinny sprawdzić, czy używają najnowszej wersji; i doradzaj użytkownikom aktualizację, gdy będzie dostępna nowsza wersja. Obsługa dwóch starych wersji jest trudna, obsługa pięciu jest nie do utrzymania.
  • Oprzyj się pragnieniu aktualizacji wersji API przy każdym „wydaniu”. Nowe funkcje można zwykle wprowadzić do bieżącej wersji bez niszczenia istniejących klientów; to nowe funkcje. Ostatnią rzeczą, jakiej chcesz, jest to, aby klienci ignorowali numer wersji, ponieważ i tak jest on w większości kompatybilny wstecz. Zaktualizuj wersję interfejsu API tylko wtedy, gdy absolutnie nie możesz przejść do przodu bez zerwania istniejącego interfejsu API.
  • Gdy przyjdzie czas na stworzenie nowej wersji, pierwszym klientem powinna być implementacja poprzedniej wersji kompatybilna wstecz. „Konserwacyjny interfejs API” powinien sam zostać zaimplementowany w bieżącym interfejsie API. W ten sposób nie jesteś zawieszony na utrzymaniu kilku pełnych implementacji; tylko aktualna wersja i kilka „powłok” dla starych wersji. Przeprowadzenie testu regresji dla przestarzałego interfejsu API w stosunku do wstecznego kompatybilnego klienta jest dobrym sposobem na przetestowanie zarówno nowego interfejsu API, jak i warstwy zgodności.
SingleNegationElimination
źródło
3

Pierwsza opcja projektowania identyfikatora URI lepiej wyraża pomysł wersjonowania całego interfejsu API. Drugi można interpretować jako wersjonowanie samych wiadomości. Więc to jest lepsze IMO:

rest.product.com/api/v1/messages/send

W przypadku biblioteki klienta myślę, że użycie dwóch pełnych implementacji w osobnych pakietach Java jest czystsze, łatwiejsze w użyciu i łatwiejsze w utrzymaniu.

Biorąc to pod uwagę, istnieją lepsze metody ewolucji interfejsów API niż przechowywanie wersji. Zgadzam się z tobą, że powinieneś być na to przygotowany, ale myślę o wersjonowaniu jako o ostateczności, którą należy stosować ostrożnie i oszczędnie. Aktualizacja jest stosunkowo dużym wysiłkiem dla klientów. Jakiś czas temu umieściłem niektóre z tych myśli w poście na blogu:

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

W szczególności w przypadku wersji REST API pomocny jest ten post autorstwa Mark Nottingham:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown

Ferenc Mihaly
źródło
3

Innym podejściem do obsługi wersjonowania API jest użycie wersji w nagłówkach HTTP. Lubić

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

Możesz parsować nagłówek i odpowiednio go obsługiwać w backend.

Przy takim podejściu klienci nie muszą zmieniać adresu URL, tylko nagłówek. A to sprawia, że ​​punkty końcowe REST są zawsze czystsze.

Jeśli któryś z klientów nie wysłał nagłówka wersji, albo wysyłasz 400 - Błędne żądanie, albo możesz go obsłużyć za pomocą wersji API kompatybilnej z poprzednimi wersjami .

Sincerekamal
źródło