Почему плохая идея разделять интерфейс между сервером и клиентом?

12

Я читал документацию Spring Cloud Netflix, когда узнал о способе обмена интерфейсами между HTTP-сервером и его клиентом. Они используют этот пример для микросервисов, хотя нет никаких причин, по которым он не может распространяться на обычную связь 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 {
}

Это определяет интерфейс, который используется как сервер (The Spring @RestControllerпревращает его в HTTP-сервер) и клиент (The Feign @FeignClientнастраивает его для использования клиентом HTTP). Реализации классов сервера и клиента можно использовать в отдельных проектах, но при этом использовать один и тот же интерфейс для обеспечения соответствия типов.

Тем не менее, под примером они поставили следующее предостережение:

Примечание. Как правило, не рекомендуется разделять интерфейс между сервером и клиентом. Он вводит тесную связь, а также фактически не работает с Spring MVC в его текущей форме (отображение параметров метода не наследуется).

ОК, так что это не очень хорошо интегрированы прямо сейчас ... но эта часть приходит после того, как предупреждение против обмена кода и введения соединения между сервером и клиентом, который по их мнению , является более важным. Почему они думают, что такой интерфейс - плохая идея?

Без этого вы потеряете возможность гарантировать, что сервер и клиент отправляют друг другу данные, которые они оба могут понять. Вы можете добавить поле к одному, но не к другому и обнаруживать несоответствие только до времени выполнения. На мой взгляд, это не введение связи, а просто выявление связи, которая уже существует. Является ли необходимость сделать серверы полностью независимыми больше, чем необходимость сообщать им, какие типы данных они будут получать?

Бен С
источник
1
Существующая связь в терминах данных / форматирования, обрабатываемых клиентами / серверами, определяется протоколом - частью документации, которую можно использовать в качестве соглашения . Соединение, представленное совместным использованием интерфейса, представляет собой соединение во время компиляции - рассмотрим, что происходит, когда интерфейс изменяется (например, обратно-несовместимым способом), но код клиент-сервер, использующий этот интерфейс, развертывается в разное время. С этим взаимодействием времени развертывания может быть сложнее управлять, особенно в масштабе Netflix.
Касталья
1
Я почти уверен, что я не работаю в масштабе Netflix :), но если интерфейсы изменяются в несовместимом с предыдущим образом стиле, то разве это не сместит ошибку с того, что она была найдена во время компиляции, вместо того, чтобы быть найденной во время выполнения? Считают ли они нормальным допустить сбой нескольких вызовов функций, пока они медленно обновляют все серверы?
Бен С
1
Возможно; зависит от кода клиента. Рассмотрим также другой случай: сначала обновляются серверы , и теперь клиентам приходится иметь дело с (неожиданно)
ошибочными
1
Просто любопытно, разделяя этот интерфейс, ограничивает ли он, на каких языках / стеке вы можете построить клиент?
Джеффо
Да, это файл Java, поэтому вам придется использовать Java. Вы можете быть в состоянии использовать другой язык виртуальной машины Java , но я не пробовал.
Бен С

Ответы:

6

Причина, как указано в комментариях, заключается в том, что это приводит к тесной связи вашей клиентской платформы с вашей серверной платформой. Здесь это означает, что ваш клиент должен использовать язык / платформу, которую вы используете на сервере, чтобы понять ожидаемый контракт вашего сервера. Обратите внимание, что существует разница между совместным использованием одного и того же кода (артефакт определенного языка / платформы) и согласованием конкретного контракта.

Многие проекты вместо этого используют документацию для своих контрактов. Пример запросов и ответов в нейтральном формате (например, JSON) по стандартным протоколам (например, REST). (См. , Например, документы Stripe API ). Потому что нецелесообразно писать контракт на основе кода для каждой возможной клиентской платформы, которую вы можете использовать или разрешить. Третьи используют инструменты управления API для определения нейтральных контрактов .

Ваш пример добавления поля представляет собой отдельную проблему - пример того, почему это важно для версий API-контрактов. Пусть клиенты используют версию, для которой они предназначены. Неверно несовместимая новая версия API существует наряду со старой. Клиент для старой версии продолжает работать до тех пор, пока его команда не приступит к его обновлению или пока вы не удалите старую версию (после периода устаревания / миграции). Смотрите Параллельное изменение .

Следование (неявное указание в) предупреждению помогает клиенту и серверу развиваться способами и темпами, которые имеют смысл для каждого. Если вы можете разумно гарантировать, что ваш сервер и клиент всегда будут использовать один и тот же язык / платформу и развиваться в одном и том же темпе, то использование артефакта кода, специфичного для языка и платформы, так как ваш контракт, вероятно, будет в порядке. Тем не менее, это, вероятно, не является разумным ожиданием, особенно для проектов, нацеленных на Netflix OSS (что специально предназначено для масштабируемости и производительности облачных вычислений, со всей этой необходимой сложностью).

Кейси Спикман
источник
2
Действительно ли клиент должен использовать интерфейс? Я всегда видел такие конструкции как способ облегчить написание клиентов. В конце концов, вы все еще можете написать REST-клиент на другом языке.
Джимми Т.
1
Точно, API все еще будет существовать, с его определениями маршрутов, ничто не мешает созданию клиента на другом языке, но до тех пор, пока вы используете java, вы можете использовать интерфейс
Леонардо