Как мы делали API для социальной сети (REST API для WEB)

  • Tutorial
Я в своей работе часто сталкиваюсь с разными новыми концепциями, идеологиями и проектами. В основном из-за того, что участвую в разработке разных больших проектов, в каждом из которых встречаю что-то новое. Сегодня я хочу рассказать о своем опыте с REST API. Идеологии, которую мне как-то раз довелось применить при разработке одной социальной сети.

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

Хочу сразу оговориться: REST для меня — это, в первую очередь, идеология, к которой я отношусь трепетно и нежно. И, если кто-то не готов принять ее такой, какая она есть — не стоит читать дальше эту статью. У программиста всегда есть куча полезных дел, которыми можно заняться, пока приходит понимание необходимости писать узнаваемые интерфейсы и прятать за их простотой логику приложений любой сложности…

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

Теперь давайте по порядку.

REST (Representational State Transfer — репрезентативная передача состояния).

Рой Филдинг — описал подход, названный REST, еще в 2000 году, этот подход лег в основу всем известного протокола HTTP. Можно встретить две разновидности написания для этого архитектурного стиля программирования: REST и RESTful, отличия в значениях этих терминов нет, просто RESTful это прилагательное от REST, т. е. RESTful API — это API, отвечающее принципам REST (Прошу прощения у тех, кто знал, но такие вопросы задают достаточно часто).

REST (Representational State Transfer — репрезентативная передача состояния).

Структура REST API


1. Клиент-серверная архитектура

2. Stateless сервер

3. Кешируемость

4. Многослойная структура

5. Единый интерфейс

  • Идентификация ресурсов
  • Взаимодействие с ресурсами через представления
  • Самодостаточные сообщения
  • Гипермедиа в качестве механизма управления состояниями приложения (HATEOAS)

6. Код по требованию (опционально)

Теперь о каждом элементе поподробней.

Архитектура, клиент— сервер. Отделяем логику приложения от различных клиентов, делаем их код более переносимым, а структуру сервера более простой и масштабируемой. Разработка клиентов и сервера может вестись совершенно независимо.

Stateless — сервер. Состояние клиента не хранится на сервере, ни в каком виде, этим занимается исключительно сам клиент. Это упрощает доработку и сопровождение сервера, делает его более стабильным.

Кешируемость. Должна быть разработана четкая система кеширования запросов к серверу, что позволяет значительно улучшить производительность.

Многослойная структура. Наличие/отсутствие промежуточных серверов кеширования, балансировки нагрузки, дополнительного проксирования должно оставаться совершенно незамеченным со стороны клиентов.

Единый интерфейс.

Идентификация ресурсов. Каждый ресурс обладает уникальным идентификатором URI (Uniform Resource Identifier — универсальный идентификатор ресурса). Например:

/users/ae25b8

Что касается ID, советую посмотреть в сторону UUID (universally-unique identifier), его поддерживает большинство баз данных и такой подход поможет обеспечить кросc-системную уникальность идентификаторов. Пример:

/users/550e8400-e29b-41d4-a716-446655440000.

Взаимодействие с ресурсами через представления. Каждый ресурс имеет свой уникальный адрес URI (представление), и несколько глаголов для управления этим представлением.

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

HATEOAS. Hypermedia As The Engine Of Application State.

Гипермедиа в качестве механизма управления состояниями приложения.

Слова — Representational State Transfer должны вызывать в голове появление картинки работы грамотного веб приложения: сеть из веб-страниц (назовем их состояниями), в которой пользователь перемещается кликая по ссылкам (смена состояний) для перехода на следующую страницу (представляющую собой очередное состояние приложения).
Рой Филдинг

Ключ к пониманию HATEOAS удивительно прост — в каждом полученном ответе содержится ссылка на следующий запрос.

Гипермедиа — это тип содержимого ресурса с включенной гипертекстовой разметкой. Гипертекст в данном контексте, как считает сам Филдинг, это одновременное представление информации и элементов выбора.

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

Код по требованию. Опциональный элемент структуры. Позволяет получать программный код для последующего его исполнения на клиенте.

Таким образом, если ваше приложение отвечает всем требованиям (ограничениям), описанным выше, оно смело может называться RESTful. Исключение представляет лишь код по требованию — этот параметр опционален.

Хороший API просто понять и легко применять.



Основы REST через HTTP.


Ресурс — уникальный объект, доступный по уникальному URL.

Основные URL должны быть понятны без документации.

Для начала, желательно все URL адреса вашего API начинать с префикса, например:

/api/

это поможет упростить сопровождение API в будущем. Хорошим вариантом может оказаться префикс в имени домена:

api.example.com/users/ae25b8

Существует 2 основных типа ресурса в архитектуре REST: коллекция и элемент коллекции.

Коллекция представляет собой набор самостоятельных и самодостаточных элементов.

Пример ссылки на коллекцию пользователей:

/api/users

Элемент коллекции пользователей, или конкретный пользователь, в таком случае, может быть представлен в виде:

/api/users/ae25b8

Существительные — хорошо, глаголы — плохо.


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

Каждым ресурсом в RESTful API управляет несколько определенных минимально необходимых глаголов. Для большинства случаев достаточно 4 основных глагола (HTTP метода):

GET — получить

POST — создать

PUT — изменить

DELETE — удалить

GET, PUT, DELETE — идемпотентны.

Идемпотентность означает, что сколько бы раз мы не вызывали такой метод — результат будет один и тот же.

Ресурс POST GET PUT DELETE
/users Создать пользователя Показать список всех пользователей Обновить список всех пользователей Удалить всех пользователей
/users/ae25b8 Ошибка Показать Василия Пупкина Если есть, обновить Пупкина, если нет Ошибка Удалить Василия Пупкина


Связи.


Если необходимо показать иерархическую связь между объектами, делаем так.

Коллекция машин пользователя:

/api/users/ae43bc/cars

Конкретная машина:

/api/users/ae43bc/cars/c7b45e

Не стоит писать длинные адреса — это плохо:

/api/users/ae43bc/cars/c7b45e/door/346ec3b

Такие адреса нелегко читать и искать в документации, часто это вообще не имеет смысла — идентификаторы уникальные и «/cars/c7b45e» абсолютно однозначно относится к «/users/ae43bc». Данный вариант следует сократить:

/api/cars/c7b45e/door/346ec3b

Ошибки.


Следует различать 2 основных семейства статус кодов (HTTP Status Code):

4xx — проблема возникла на стороне пользователя и он сам может ее исправить, правильно введя необходимую для запроса информацию.

5xx — проблема возникла на сервере и для ее решения пользователь может отправить запрос к службе поддержки.

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

Пример хорошо написанного ответа на ошибку:

HTTP Status Code: 401

{«status»: 401, «message»: «Authentication Required», «code»: 20003, «more_info»: «http://www.example.com/docs/errors/20003»}

Помните! Вы пишете API для таких же разработчиков как и Вы.

Используйте необходимый минимум статус кодов в приложении.

Иногда может быть достаточно 3:

  1. 200 OK
  2. 400 Bad Request (некорректный запрос)
  3. 500 Internal Server Error (внутренняя ошибка сервера)

Если мало, дополняйте по мере необходимости:

  1. 201 Created (успешно создан)
  2. 304 Not Modified (данные не изменились)
  3. 404 Not Found (не найден)
  4. 401 Unauthorized (не авторизован)
  5. 403 Forbidden (доступ запрещен)

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

В некоторых случаях полезно иметь параметр для подавления статус кода ошибки, что бы клиент всегда, при необходимости, мог получить код 200, например.

PUT /api/users/de840a?supress_status_code=200

Это добавит нелишней гибкости Вашему API.

Версионность.


Обязательно указывайте номер версии, даже если не планируете изменение интерфейса — все может быстро измениться.

Версию можно указать в строке адреса:

/api/v2/users/ae43bc

или в параметрах запроса:

/api/users/ae43bc?v=2

Нет смысла делать длинными названия версий, вставлять в них точки: v1.03

Версии интерфейса должны меняться максимально редко, в то время как внутреннюю логику работы API можно менять, как только появилась необходимость. В реальности настоящая версия API может быть, например, v2.034-beta2, но версия интерфейса, и, соответственно, представленная в адресе версия будет просто 2.

Постраничная выдача.


Любая коллекция, какой бы маленькой, по Вашему мнению, она не была должна отдаваться постранично. Определитесь с форматом выдачи коллекции, например, Content-Type: application/json {«data»:{}, «paging»: {«limit»: 50, «offset»: 0, «total»: 150}} старайтесь всегда использовать одинаковый формат во всех ответах приложения — облегчите жизнь и себе и разработчикам клиентского ПО.

Стоит выбрать какие-то дефолнные значения для параметров «limit», «offset», и, скорее всего, ограничивать максимальное значение для «limit».

Прячьте все сложные компоненты запроса за «?».


Если в Вашем GET запросе необходимо использовать различные фильтры — поместите их за знаком вопроса (в параметрах URL):

GET /api/users?limit=10&offset=4&age=30&height=160&weight=120

Отдавайте пользователю только то, что он хочет.


Позвольте клиенту получать только те поля в запросе, которые ему нужны:

GET /api/users/ae43bc?fields=fitst_name,last_name,age,gender,finger_count

Формат отдаваемых данных.


Не ограничивайтесь каким-то одним форматом. Сделайте опционально несколько, например, json и xml. Таким легким способом можно значительно упростить разработку для клиентов, и отпадет необходимость выбора чего-то одного. Формат возвращаемых данных можно описывать как в HTTP заголовках, так и в строке запроса:

ACCEPT: application/json

GET /api/users/ae43bc.json

И обязательно стоит выбрать какой-то формат по умолчанию.

Поиск.


Это один из немногих видов ресурса, которому суждено остаться глаголом. Имею в виду глобальный поиск.

GET /api/search?q=some+text+to+find

Опять же в зависимости от системы используемого поискового движка можно применять различные фильтры.

Какой-то локальный поиск, в пределах коллекции, можно осуществить и простыми фильтрами:

GET /api/users?q=some+users+to+find

Авторизация.


Используйте, по возможности, последнюю версию OAuth — OAuth 2.0 — эта система хорошо известна разработчикам, и хорошо себя зарекомендовала. Зачем выдумывать велосипед?

Документация.


Это один из самых важных аспектов хорошего API. Любой, кто, когда-либо сталкивался с написанием клиентских скриптов, со мной согласится. Часы, потраченные на хорошую документацию, с лихвой окупятся долгими месяцами работы службы поддержки. Фокус прост — опишите сжато и четко все получаемые и отдаваемые данные, а также назначение методов. Помните! Вы пишете для программистов. Не стоит описывать какие-то очевидные моменты. Приведите все отдаваемые статус коды; перечислите все принимаемые параметры опишите их, где необходимо; давайте ссылки на более подробный материал; приводите примеры получаемых данных, если это уместно, с их описанием.

Ссылки.


Старайтесь отдавать в ответах ссылки на все связанные ресурсы, если хотите соответствовать принципу HATEOAS, и называться RESTful. За это Вас будут очень любить разработчики клиентских программ — им не придется самим генерировать эти ссылки.

Теперь самое главное!


Фасад Паттерн для API.

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

Помните! Интерфейс API должен быть максимально простым и понятным, только так можно достичь счастья и гармонии.

Пару слов в заключение


В результате мы с командой успешно сделали социальную сеть, в которой применили REST API. Сеть, к слову, была на PHP, в частности Symfony2, хотя этот подход применим и на других технологиях. Надеюсь, моя первая статья была Вам интересна. Буду рад услышать вопросы и мнения в комментариях.

Приглашаю на курсы по веб-разработке от бизнес-школы Digitov, которые веду Я: Хочу стать Junior PHP Developer! (для новичков), Symfony 2. Гибкая разработка (для специалистов), а также, которые ведут мои коллеги: Разработка веб-приложений на Python / Django (для новичков) и Ruby on Rails. По рельсам к профессиональной разработке (для новичков). Подписывайтесь на курсы сейчас и сможете купить их со скидкой

P.S. Чтобы получать наши новые статьи раньше других или просто не пропустить новые публикации — подписывайтесь на фан страницы SECL Group: Facebook, VK, и Twitter

Оригинал статьи: http://secl.com.ua/article_rest_api_web.html

Автор: Сергей Харланчук, Senior PHP Developer / Team Lead, компания «SECL GROUP» / «Internet Sales Technologies»

SECL Group

44,17

Делаем стартапы успешными!

Поделиться публикацией
Комментарии 52
    +2
    Буквально недавно читал статью на которую наткнулся по запросу «API best practices»… Знаете, там 90% материала изложенного в той статье совпадает с Вашим.
      +1
      Вот она, http://info.apigee.com/Portals/62317/docs/web%20api.pdf Достаточно известный материал для людей кто хоть раз сталкивался с разработкой API, тоже сразу заметил знакомый текст.
        0
        да очень много похожих частей… так сказать — творческий перевод :) с переименованием dog в user
          0
          Отличная книжка.
          –4
          Возможно. У меня свой опыт, а в целом тема не новая, информации много разной.
          0
          Всегда было непонятно, каким статусом отвечать на успешный DELETE. Что порекомендуете?
            0
            204 — Обычно
              0
              Спасибо. Как-то я не подумал про этот код.
                +1
                А разве DELETE — идемпотентен? Если ресурс уже удален, разве запрос не должен вернуть 404?
              0
              Спасибо, отличная статья.
                +1
                Всегда думал что PUT — создать а POST — изменить…
                  +1
                  Спасибо за минусы
                  Уточняю что по стандартам PUT предполагает создание ресурса при его отсутствии тогда как POST такого не предполагает.
                    0
                    В статье дается вариант несколько адаптированный для большей конкретизации и упрощения модели использования. В стандарте PUT применяется для добавления сущности, если она отсутствует и для обновления уже имеющейся. POST для создания
                      0
                      В стандарте какой версии? POST — это создание, PUT — обновление (полное), или создание (если ресурс не существует), а PATCH — частичное изменение существующего ресурса. Стандарт HTTP 1.1.

                      Но для API, мне кажется больше подходит Rails подход. POST — создание, PATCH — обновление. Это упрощает API, плюс при таком подходе, PUT становится частным случаем PATCH.
                        0
                        HTTP 1.0 PUT как таковой отсутствует.
                          0
                          Да, моя ошибка, не собран сегодня.
                      0
                      The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:


                      The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request. If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem. The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return a 501 (Not Implemented) response in such cases.

                    0
                    Спасибо!
                    Не совсем понятно для чего нужен supress_status_code. Не могли бы вы привести какой-нибудь пример?
                      0
                      В зависимости от контекста может возникать необходимость не выводить ошибки, например, если удаляется отсутствующая запись, а в каких-то случаях может понадобиться ошибка (в зависимости от задачи) — это делает API гибче. Никогда не знаешь, в чем у другого программиста может возникнуть потребность и при какой ситуации.
                        +1
                        Какой-нибудь экзотический клиент может не обрабатывать тело ответа (с закодированными дополнительными сведениями об ошибке) в случае ошибочного HTTP STATUS.
                        0
                        У меня есть вопрос. Не могли бы вы по подробней описать использование паттерна «Фасад» для разработки API? Мне не совсем понятно, как и где его следует применять при столько ограниченном наборе глаголов. Я был бы признателен за небольшой жизненный пример.
                          0
                          Паттерн «Фасад», есть смысл использовать при разработке любого API. То есть перед началом программирования хорошо иметь представление о будущем функционале в конкретной и наглядной форме. Это может значительно упростить структуризацию кода и сократить его дублирование. «Фасад» скорее является грамотным подходом к проектированию опять таки любого API. Все сущности и связи в проекте желательно предусмотреть до начала программирования.
                            0
                            Насколько я понял, тут автор имеет ввиду, что вы сперва продумываете интерфейс и создаете заглушки для него — url, тип ответа, структуру ответа, а затем уже можно программировать внутренности, не переживая за то, что какой то клиент не сможет сделать запрос, потому что вы поменяли url, или необходимы дополнительные параметры.
                            +2
                            Всегда было интересно, как быть с глаголами, если требуется реализовать набор примитивных действий над ресурсом (activate, block, rename, etc), что проще и читабельнее?

                            PUT /api/users/de840a?status=blocked
                            или
                            PUT /api/users/de840a/block
                              0
                              Считают, что для реализации подобного рода действий возможно применять глаголы, но их количество должно быть минимальным.
                              P.S.: Второй вариант мне больше импонирует. Первый скорее похож на фильтр по полю «status».
                                +3
                                Может так:

                                PATCH /api/users/de840a
                                status=blocked
                                  0
                                  Да PATCH здесь вполне приемлемое решение.
                              0
                              где тэг «на правах рекламы»?
                              • НЛО прилетело и опубликовало эту надпись здесь
                                  –1
                                  Зачем ломать мозг, чтобы понять ФП? Каким образом связано ФП и Haskell в плане суждения об общем по частному? Вы уверены, что хотя бы правильно понимаете понятие ФП?

                                  Чтобы построить хороший RPC нужно ломать мозг еще сильнее, так как RPC не имеет никаких ограничений, и полностью полагается на добросовестность разработчика в том плане, что он будет проектировать API, а не лепить бекенд код так, как ему удобно (что очень легко делать в RPC ввиду отсутствия ограничений, подобных тем, что присутствуют в REST).
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                  +4
                                  О, рест это такая прелесть. Меня всегда интересовала идеология, что на метод GET мы ничего не меняем и только получаем данные. А вот конкретный пример — запрашиваем статью по ее ID. И есть у нас поле «число просмотров». Как предлагаете его инкрементить?
                                    +3
                                    Подразумевается, что при GET запросе, мы не получаем от пользователя данных для записи.
                                    Внутренние счетчики обновлять никто не мешает.

                                    * Но вообще все это сильно зависит от бизнес логики.
                                    Строго говоря, какое нибудь мобильное приложение, может закешировать статью и не вызывать каждый раз GET при множественных просмотрах.
                                    Если показатель числа просмотров важен — сделайте отдельный POST метод для статистики просмотров.
                                      0
                                      Ну логика простая. Мобилочка показывает статьи сайта, например. Типа браузер. И кэш тут особо-то и ни при чем, ибо все равно хотя бы раз надо же запросить. И, предположим, при выводе есть такой глазик с числом просмотров.

                                      Просто такой вот простой пример меня лично ставит в тупик и ломает самый простой догмат рест.
                                        +2
                                        Есть сайт. У него статьи. У статьи помимо присущих данных, есть read-only поле счетчика просмотров, которое инкрементирует внутренняя бизнес-логика приложения, при запросе на просмотр например. Пользователь данные не отправляет.

                                        При запросе коллекции статей — мы получаем список статей с их счетчиками. В чем проблема?
                                    0
                                    Вот есть у меня 10 идентификаторов пользователей. И мне для построения тела страницы необходимы подробные данные о них. Как мне поступить?
                                    1. десять раз сделать get в котором я запрашиваю данные по одному пользователю,
                                    2. или сделать один get, в котором в параметрах передаются их идентификаторы (GET /api/users/123/321/567/432/865/6432/54322/8654/9876/439)
                                      –1
                                      Здесь тема о построении серверной части. Если вы уверены, что всем нужны такие таблицы, делайте метод для возврата уже таблицы. Если нет — то 10 GET запросов. На самом деле 10 паралельных запросов, это не так долго.
                                      Если просто у вас система работы с пользователями, то делайте, чтобы метод принимал массив.
                                      Скорее всего у вас не сферическое API в вакууме.
                                        +1
                                        Мне кажется оптимальным будет вариант в духе:

                                        GET: api.example.com/users?ids=1,2,3
                                          0
                                          Учитывая контекст — веб и как следствия браузер как клиент, быстрее будет одним коннектом через keep-alive.
                                            0
                                            Почему контекст — веб? Айфон, андроид, винфон. И почему не websockets? Но тогда это уже явно не REST.
                                          +1
                                          Всю сколько-нибудь сложную логику стоит выносить за знак "?".
                                          Например, как-то так:
                                          GET /api/users?ids=123,321,567,432
                                          0
                                          HTTP Status Code: 401

                                          {«status»: 401, «message»: «Authentication Required», «code»: 20003, «more_info»: «http://www.example.com/docs/errors/20003»}

                                          Все эти status в body ответа дублирующие header это нарушение REST.

                                          Используются ли cookie, и если та, то для чего?
                                          Авторизация/аутентификация. OAuth, круто. Но как конкретно передаются токены (http header only? cookie? query string?)?
                                            –1
                                            Не вижу особой проблемы в дублировании заголовков. В конце концов основная цель это удобство использования API, не всегда можно предусмотреть все варианты использования сервиса, такое дублирование может сделать его более гибким и расширяемым.
                                            Cookies это, преймущественно, дело клиента который будет писаться под созданный API.
                                            Токен от клиента обычно передается в header.
                                              +1
                                              Я тоже не считаю это сильно критичным. Просто если уж говорить про REST как идеологию, то при трепетном отношение к ней нужно констатировать, что подобные практики — отступление от REST-а.
                                            0
                                            Предпринимались ли попытки использования range для постраничного вывода?
                                              0
                                              Еще одна хорошая статья про REST в копилку: www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
                                              0
                                              И вы бы с ценами на курсы определились что ли… Когда будут известны?
                                                0
                                                Подавайте заявку, Вам сообщат.
                                                –1
                                                Можете подсказать по статусам для ошибок?
                                                Какие статусы выдавать в случаях:
                                                1. Ошибка пользователя, ее необходимо показать. Например «Вы ввели некорректную дату» или «форамат логина неверный»
                                                2. Ошибка для developer'а. Например «Не передан параметер id. Без него зарезервировать билет невозможно»
                                                  +1
                                                  В обоих случаях обычно используется статус «400 Bad Request» с соответствующим описанием необходимых для исправления действий
                                                  0
                                                  На мой взгляд отображение зависимостей между объектами не должно выполняться через построение иерархии объектов в URL, т.к. в дальнейшем это может привести к тому что объект будет доступен по нескольким уникальным URL, что в итоге противоречит принципам REST. И приводит к неприятным эффектам с кешированием.
                                                  Поясню на вашем же примере:
                                                  /api/users/ae43bc/cars/c7b45e

                                                  Допустим два пользователя могут иметь одну и ту же машину во владении (муж и жена) или более приземленный вариант разделяемый документ между двумя пользователями. Соответственно получаем один и тот же ресурс доступен по двум уникальным URL
                                                  /api/users/ae43bc/cars/c7b45e
                                                  /api/users/bc11aa/cars/c7b45e

                                                  /api/users/ae43bc/docs/c7b45e
                                                  /api/users/bc11aa/docs/c7b45e

                                                  В итоге клиенту придется кэшировать два объекта или анализировать содержимое сущности для оптимизации кэширования.

                                                  На мой взгляд более правильный метод реализации иерархии объектов как раз выполняется через принцип HATEOAS. Т.е. в указанном выше примере у обоих пользователей в содержимом сущности должна быть коллекция ссылок на машины которыми они владеют, e.g.:

                                                  {
                                                      ...
                                                      cars:[
                                                          "/cars/c7b45e"
                                                      ]
                                                      ...
                                                  }

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое