RESTful API - Проектирование под-ресурсов

Я проектирую RESTful API и столкнулся с проблемой, связанной с подресурсами.

Я вижу другие API, использующие полный URL для работы над подресурсами. Возьмите пример, когда в Company has Departments а в Department has Employees.

В начале я думал о реализации всех возможных URL. В результате чего:

Подход А

01. ### COMPANY URLS ###
02. DELETE /companies/{companyId}
03. GET    /companies/{companyId}
04. POST   /companies
05. PUT    /companies/{companyId}
06. 
07. ### DEPARTMENT URLS ###
08. DELETE /companies/{companyId}/departments/{departmentId}
09. GET    /companies/{companyId}/departments/{departmentId}
10. POST   /companies/{companyId}/departments
11. PUT    /companies/{companyId}/departments/{departmentId}
12. DELETE /departments/{departmentId}
13. GET    /departments/{departmentId}
14. PUT    /departments/{departmentId}
15. 
16. ### EMPLOYEE URLS ###
17. DELETE /companies/{companyId}/departments/{departmentId}/employees/{employeeId}
18. GET    /companies/{companyId}/departments/{departmentId}/employees/{employeeId}
19. POST   /companies/{companyId}/departments/{departmentId}/employees
20. PUT    /companies/{companyId}/departments/{departmentId}/employees/{employeeId}
21. DELETE /departments/{departmentId}/employees/{employeeId}
22. GET    /departments/{departmentId}/employees/{employeeId}
23. POST   /departments/{departmentId}/employees
24. PUT    /departments/{departmentId}/employees/{employeeId}
25. DELETE /employees/{employeeId}
26. GET    /employees/{employeeId}
27. PUT    /employees/{employeeId}

Как видите, есть много URL, которые делают то же самое. Пример: 08 дублируется из 12; 09 дублируется из 13; 17 дублируется из 21 и 25...

Я хочу удалить дубликаты, но сохранить последовательность. Таким образом, перепроектированный API с учетом принципа sup-resources are fine but sub-sub-resources are not. Что привело к следующему:

Подход Б

01. ### COMPANY URLS ###
02. DELETE /companies/{companyId}
03. GET    /companies/{companyId}
04. POST   /companies
05. PUT    /companies/{companyId}
06. 
07. ### DEPARTMENT URLS ###
08. DELETE /departments/{departmentId}
09. GET    /departments/{departmentId}
10. GET    /companies/{companyId}/departments
11. POST   /companies/{companyId}/departments
12. PUT    /departments/{departmentId}
13. 
14. ### EMPLOYEE URLS ###
15. DELETE /employees/{employeeId}
16. GET    /employees/{employeeId}
17. GET    /departments/{departmentId}/employees
18. POST   /departments/{departmentId}/employees
19. PUT    /employees/{employeeId}

Мои вопросы

Q1. Подход B считается RESTful? (Я предполагаю, что да)

Q2. Существуют ли подводные камни, которые следует рассмотреть в подходе BI при условии, что документация также предоставлена?

Бонусные баллы, если вы указываете на другие API в соответствии с подходом B.

РЕДАКТИРОВАТЬ

Elad Tabak представил хорошие идеи.

Я увлекаюсь API, используя подход B:

https://developers.google.com/youtube/v3/docs/

https://developer.github.com/guides/getting-started/

https://dev.twitter.com/rest/public

Ответ 1

Оба подхода могут считаться RESTful, если вы не нарушаете ограничения REST, определенные в главе 5 диссертации Роя Томаса Филдинга:

Я не вижу серьезных подводных камней в обоих подходах, но я бы предпочел подход B вместо подхода A: URL-адреса короче, их легче запомнить и не требуется много параметров.


Бонусные баллы: API Spotify и Facebook следуют этому подходу.Конечно, есть и другие API, но это те, которые мне приходили в голову.

Ответ 2

  • REST ничего не говорит о дизайне URL. Любая схема URL, которую вы придумали, - RESTful. Вы должны спросить, хороший ли он. И да, второй подход предпочтительнее первого. Первая - тонна шума для клиентов и огромная проблема обслуживания для владельца. Это также ограничивает гибкость в будущем.

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

Бонусные баллы: запрос на внешние ресурсы выходит за рамки.

Ответ 3

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

Существование

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

A имеют преимущество здесь, потому что это очень ясно - при удалении resouce вы удаляете все его вспомогательные ресурсы.

Конечная точка операции

Другой вопрос, который он поднимает - как мне обновить объект? с какого конца?

Если я хочу удалить сотрудника, достаточно ли обновить компанию новым набором сотрудников? или я должен УДАЛИТЬ сотрудника?

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

A имеют преимущество, когда они очень прямолинейны, как сделать действие - CRUD на URL-адрес сущности. B заставляет пользователя API останавливаться и задаться вопросом, какое действие он может сделать на каком URL.

Но в то же время B имеют преимущество в том, что изменение "родительского контроля" объекта проще (в тех случаях, когда оно имеет значение).

Validation

В A необходимо проверить соответствие URL-адресов, поскольку пользователь может указать идентификатор сотрудника не той компании или отделу. В B такой проблемы нет.

Ответ 4

Итак, это может показаться немного сумасшедшим, но в HTTP/REST нет такой вещи, как "subresource".

В вашей модели домена отдел не может существовать без компании.

Теперь в вашем API вы обнаружите json-представление компании в /companies/{companyId} и json-представление отдела в /companies/{companyId}/departments/{departmentId}.

Это оба "ресурсы". Ресурс, в терминологии HTTP/REST, означает только то, на что указывает URL. Таким образом, "json представительство компании" не является самой компанией.

Дизайн URL-адреса сам по себе представляет собой тупиковый переулок сам по себе - сами URL-адреса могут выглядеть как НИЧЕГО, неважно, читаемы они или нет. Разработчики делают запросы, выбирая URL-адреса в зависимости от названий операций из документации *. Попытка добавить смысл к URL-адресам сама по себе быстро запуталась и на самом деле может добавить путаницу с течением времени.

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

* или гипермедиа (например, https://github.com/kevinswiber/siren)