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ą /api
wraz z /auth
innymi 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
, DELETE
nie 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ę send
punkt 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”.