Принципы проектирования REST: ссылки на связанные объекты вместо вложенных объектов

#rest

#rest

Вопрос:

Мы с моей командой проводим рефакторинг REST-API, и у меня возник вопрос. Для краткости давайте предположим, что у нас есть база данных SQL с 4 таблицами: преподаватели, учащиеся, курсы и классные комнаты. Прямо сейчас все отношения между элементами представлены в REST-API посредством ссылки на URL связанного элемента. Например, для курса у нас могло бы быть следующее

 { "id":"Course1", "teacher": "http://server.com/teacher1", ... }
  

Кроме того, если задать список курсов, на которые GET позвонили /courses , я получу список ссылок, как показано ниже:

 {
   ... //pagination details
  "items": [
   {"href": "http://server1.com/course1"},
   {"href": "http://server1.com/course2"}...
 ]
}
  

Все это красиво и чисто, но если мне нужен список названий всех курсов с именами преподавателей, и у меня 2000 курсов и 500 преподавателей, я должен сделать следующее:

  • Примерно 2500 запросов только для чтения данных.
  • Реализовать объединение между преподавателями и курсами
  • Оптимизируйте с помощью кэширования и т.д., Чтобы я сделал это как можно быстрее.

Моя проблема в том, что этот метод создает большой сетевой трафик с тысячами вызовов REST-API и что мне приходится повторно реализовывать естественное объединение, которое база данных выполняла бы намного эффективнее. Коллеги говорят, что такой подход является стандартным способом реализации REST-API, но тогда относительно простой запрос становится большой проблемой.

Поэтому мой вопрос: 1. Это неправильно, если мы помещаем информацию об учителе в курсы. 2. Должен ли список элементов, например GET /courses , возвращать список ссылок или список элементов?

Редактировать: После некоторых исследований я бы сказал, что модель, которую я имею в виду, в основном соответствует той, что показана в jsonapi.org . Хороший ли это подход?

Ответ №1:

Моя проблема в том, что этот метод создает большой сетевой трафик с тысячами вызовов REST-API и что мне приходится повторно реализовывать естественное объединение, которое база данных выполняла бы намного эффективнее. Коллеги говорят, что такой подход является стандартным способом реализации REST-API, но тогда относительно простой запрос становится большой проблемой.

Ваши коллеги потеряли сюжет.

Вот ваша эвристика — как бы вы поддержали этот вариант использования на веб-сайте?

Вероятно, вы сделали бы это, определив новую веб-страницу, которая создает необходимый вам отчет. Вы запускаете запрос, вы получаете результирующий набор для генерации множества HTML-файлов, и та-да! Клиент имеет необходимую ему информацию в стандартизированном представлении.

REST-API — это то же самое, с большим акцентом на машинную читаемость. Создайте новый документ со схемой, чтобы ваши клиенты могли понять семантику документа, который вы им возвращаете, сообщите клиентам, как найти целевой uri для документа, и вуаля.

Создание новых ресурсов для обработки новых вариантов использования — это обычный подход к REST.

Ответ №2:

Да, я полностью думаю, что вам следует разработать что-то похожее на jsonapi.org . Как эмпирическое правило, я бы сказал «предпочитаю решение, которое требует меньше сетевых вызовов». Это особенно верно, если количество сетевых вызовов будет на порядок меньше. Конечно, это не устраняет необходимость ограничивать размер запроса / ответа, если это становится необоснованным.

Реальные решения должны иметь надлежащий баланс. Чистый API хорош до тех пор, пока он работает.

Итак, в вашем случае я бы сделал что-то вроде:

 GET /courses?include=teachers
  

Или

 GET /courses?includeTeacher=true
  

Или

 GET /courses?includeTeacher=brief|full
  

В последнем ответе может содержаться только идентификатор преподавателя для brief и полные данные преподавателя для full .

Ответ №3:

Моя проблема в том, что этот метод создает большой сетевой трафик с тысячами вызовов REST-API и что мне приходится повторно реализовывать естественное объединение, которое база данных выполняла бы намного эффективнее. Коллеги говорят, что такой подход является стандартным способом реализации REST-API, но тогда относительно простой запрос становится большой проблемой.

Вы действительно измеряли накладные расходы, генерируемые каждым запросом? Если нет, то откуда вы знаете, что накладные расходы будут слишком большими? С точки зрения объектно-ориентированных программистов, может показаться плохим выполнять каждый вызов самостоятельно, однако в вашем дизайне отсутствует одно важное свойство, которое помогло Интернету вырасти до его текущего размера: кэширование.

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

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

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

Хотя некоторые типы мультимедиа, такие как HAL JSON, jsonapi, … позволяют встраивать в ответ содержимое, собранное из связанных ресурсов, встраивание содержимого имеет некоторые потенциальные недостатки, такие как:

  • Использование кэша может быть низким из-за смешивания данных, которые быстро меняются, с данными, которые являются более статичными
  • Сервер может вычислять данные, которые клиенту не понадобятся
  • Один сервер вычисляет весь ответ

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

  1. Это неправильно, если мы помещаем информацию об учителе в курсы.

Это не так, но это может быть не идеально, как объяснено выше

  1. Должен ли список элементов, например, GET / courses, возвращать список ссылок или список элементов?

Это зависит. Нет правильного или неправильного.

Поскольку REST — это просто обобщение модели взаимодействия, используемой в Web, в основном те же концепции применимы и к REST. В зависимости от размера «элемента» может быть полезно вернуть краткое описание содержимого элементов и добавить ссылку на элемент. Похожие вещи выполняются и в Интернете. Для списка студентов, зачисленных на курс, это может быть имя и его аттестат зрелости, а также ссылка, по которой можно запросить дополнительные сведения об этом студенте, сопровождаемые именем связи, которые придают фактической ссылке некоторый семантический контекст, который клиент может использовать, чтобы решить, имеет ли смысл вызывать такой URI или нет.

Такие имена связей либо стандартизированы IANA, распространенными подходами, такими как Dublin Core или schema.org или пользовательские расширения, как определено в RFC 8288 (веб-ссылки). Для вышеупомянутого списка студентов, зачисленных на курс, вы могли бы, например, использовать about название отношения, чтобы подсказать клиенту, что дополнительную информацию о текущем элементе можно найти, перейдя по ссылке. Если вы хотите включить разбивку на страницы, также можно и, вероятно, следует использовать first , next prev и last и так далее.

На самом деле это то, что представляет собой HATEOAS. Связывание данных вместе и присвоение им значимых имен отношений для создания своего рода семантической сети между ресурсами. При простом встраивании объектов в ответ такие семантические графики может быть сложнее создавать и поддерживать.

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