Dlaczego dzielenie interfejsu między serwerem a klientem jest tak złym pomysłem?

12

Czytałem dokumentację Spring Cloud Netflix, gdy dowiedziałem się o sposobie udostępniania interfejsu między serwerem HTTP a jego klientem. Korzystają z tego przykładu w przypadku mikrousług, chociaż nie ma powodu, dla którego nie może ono obejmować ogólnej komunikacji HTTP:

// The shared interface, in a common library
public interface UserService {
    @RequestMapping(method = GET, value = "/users/{id}")
    User getUser(@PathVariable long id);
}

// The controller, on the server
@RestController
public class UserResource implements UserService {
}

// The same interface used for the client
@FeignClient("users")
public interface UserClient extends UserService {
}

Definiuje to interfejs, który jest używany zarówno jako serwer (Spring @RestControllerzamienia go w serwer HTTP), jak i klient (Feign @FeignClientkonfiguruje go do użytku klienta HTTP). Implementacje klasy serwer i klient mogą być używane w oddzielnych projektach, ale nadal używają tego samego interfejsu, aby zapewnić zgodność typów.

Jednak pod tym przykładem podają następujące zastrzeżenie:

Uwaga: Zasadniczo nie zaleca się udostępniania interfejsu między serwerem a klientem. Wprowadza ścisłe sprzężenie, a także faktycznie nie działa z Spring MVC w obecnej formie (mapowanie parametrów metody nie jest dziedziczone).

OK, więc nie jest teraz dobrze zintegrowany ... ale ta część pojawia się po ostrzeżeniu przed udostępnianiem kodu i wprowadzeniem sprzężenia między serwerem a klientem, co według nich jest ważniejsze. Dlaczego uważają, że dzielenie interfejsu w ten sposób jest kiepskim pomysłem?

Bez tego tracisz możliwość zagwarantowania, że ​​serwer i klient wysyłają sobie nawzajem dane, które mogą zrozumieć. Możesz dodać pole do jednego, ale nie do drugiego, i wykryć niezgodność tylko do czasu wykonania. Moim zdaniem nie wprowadza sprzężenia, ale jedynie ujawnia sprzężenie, które już istnieje. Czy potrzeba, aby serwery były całkowicie niezależne, jest większa niż potrzeba poinformowania ich, jakie typy danych otrzymają?

Ben S.
źródło
1
Sprzężenie, które istnieje pod względem danych / formatowania obsługiwanych przez klientów / serwery, jest określane przez protokół - fragment dokumentacji, który można wykorzystać jako konwencję . Sprzężenie wprowadzone przez współużytkowanie interfejsu jest sprzężeniem w czasie kompilacji - zastanów się, co się stanie, gdy interfejs zostanie zmieniony (na przykład w sposób niezgodny wstecz), ale kod klient / serwer używający tego interfejsu jest wdrażany w różnym czasie. To sprzężenie w czasie wdrażania może być trudniejsze do zarządzania, szczególnie w skali Netflix.
Castaglia,
1
Jestem prawie pewien, że nie działam w skali Netflix :) ale jeśli interfejsy zostaną zmienione w sposób niezgodny z poprzednimi wersjami , to czy to nie tylko przeniesie błąd z wykrycia w czasie kompilacji na wykrycie w czasie wykonywania? Czy uważają, że nie można pozwolić, aby kilka wywołań funkcji kończyło się niepowodzeniem, podczas gdy powoli aktualizują wszystkie serwery?
Ben S
1
Możliwie; zależy od kodu klienta. Zastanów się także nad drugim przypadkiem: serwery są najpierw aktualizowane, a klienci muszą teraz poradzić sobie (nieoczekiwanie) z nieudanymi połączeniami ...
Castaglia
1
Ciekawe, czy udostępniając ten interfejs, czy ogranicza on języki, z których możesz zbudować klienta?
JeffO,
Tak - jest to plik Java, więc będziesz musiał używać Java. Państwo może być w stanie używać innego języka JVM, ale nie próbowałem go.
Ben S

Odpowiedzi:

6

Powodem tego jest to, że powoduje ścisłe połączenie platformy klienta z platformą serwera. Oznacza to, że Twój klient musi używać języka / platformy, której używasz na serwerze, aby zrozumieć oczekiwaną umowę serwera. Należy zauważyć, że istnieje różnica między udostępnianiem tego samego kodu (artefaktu określonego języka / platformy) a uzgadnianiem konkretnej umowy.

Wiele projektów używa zamiast tego dokumentacji do swoich umów. Przykładowe żądania i odpowiedzi w neutralnym formacie (np. JSON) w stosunku do standardowych protokołów (np. REST). (Zobacz na przykład dokumenty API Stripe ). Ponieważ niepraktyczne jest pisanie umowy opartej na kodzie dla każdej możliwej platformy klienckiej, której możesz chcieć używać lub na którą zezwalasz. Jeszcze inni używają narzędzi do zarządzania API do definiowania neutralnych kontraktów .

Twój przykład dodania pola jest osobną kwestią - przykład, dlaczego ważne jest, aby wersja umów API była ważna. Pozwól klientom korzystać z wersji, dla której zostały zaprojektowane. Obok starej istnieje nowa, niekompatybilna wersja API. Klient starej wersji kontynuuje działanie, dopóki jego zespół nie zaktualizuje go lub nie wycofa starej wersji (po okresie wycofania / migracji). Zobacz Równoległa zmiana .

Postępowanie zgodnie z (domyślną poradą w) ostrzeżeniem pomaga klientowi i serwerowi ewoluować w sposób i dla każdego z nich. Jeśli możesz w uzasadniony sposób zagwarantować, że Twój serwer i klient zawsze będą współużytkować ten sam język / platformę ORAZ ewoluować w tym samym tempie, wówczas użycie artefaktu kodu specyficznego dla języka i platformy, ponieważ umowa prawdopodobnie będzie w porządku. Jednak prawdopodobnie nie jest to uzasadnione oczekiwanie, szczególnie w przypadku projektów ukierunkowanych na system Netflix OSS (coś specjalnie ukierunkowanego na skalowalność i wydajność chmury, przy całej tej wymaganej złożoności).

Kasey Speakman
źródło
2
Czy klient naprawdę powinien korzystać z interfejsu? Zawsze widziałem takie konstrukcje jako sposób na ułatwienie pisania klientów. Mimo wszystko nadal możesz pisać klienta REST w innym języku.
Jimmy T.,
1
Dokładnie api nadal będzie istniał, z jego definicjami tras, nic nie stoi na przeszkodzie, aby utworzyć klienta w innym języku, ale dopóki używasz java, możesz korzystać z interfejsu
Leonardo Villela