Каковы лучшие практики для вложенных ресурсов REST?
Насколько я могу судить, каждый отдельный ресурс должен иметь только один канонический путь. Итак, в следующем примере, какими были бы хорошие шаблоны URL?
Возьмем, к примеру, представительство компаний для отдыха. В этом гипотетическом примере каждая компания имеет 0 или более отделов, и каждый отдел имеет 0 или более сотрудников.
Отдел не может существовать без ассоциированной компании.
Сотрудник не может существовать без соответствующего отдела.
Теперь я бы нашел естественное представление шаблонов ресурсов.
-
/companies
Коллекция компаний - принимает предложение для новой компании. Получить для всей коллекции. -
/companies/{companyId}
Индивидуальная компания. Принимает GET, PUT и DELETE -
/companies/{companyId}/departments
Принимает POST для нового элемента. (Создает отдел внутри компании.) -
/companies/{companyId}/departments/{departmentId}/
-
/companies/{companyId}/departments/{departmentId}/employees
-
/companies/{companyId}/departments/{departmentId}/employees/{empId}
Учитывая ограничения, в каждом из разделов, я чувствую, что это имеет смысл, если немного глубоко вложен.
Однако моя трудность возникает, если я хочу перечислить (GET
) всех сотрудников во всех компаниях.
Шаблон ресурсов для этого наиболее точно соответствует /employees
(коллекция всех сотрудников)
Означает ли это, что я должен иметь /employees/{empId}
также потому, что если это так, есть два URI для получения одного и того же ресурса?
Или, может быть, вся схема должна быть сглажена, но это будет означать, что сотрудники - это вложенный объект верхнего уровня.
На базовом уровне /employees/?company={companyId}&department={deptId}
возвращает тот же вид сотрудников, что и самый глубоко вложенный шаблон.
Какая наилучшая практика для шаблонов URL-адресов, в которых ресурсы принадлежат другим ресурсам, но должна быть доступна отдельно?
ОБНОВЛЕНИЕ: см. Мой ответ ниже, чтобы узнать, что я сделал.
Ответы
Ответ 1
То, что вы сделали, правильно. В общем, может быть много URI для одного и того же ресурса - нет правил, которые говорят, что вы не должны этого делать.
И вообще, вам может потребоваться доступ к элементам напрямую или как подмножество чего-то другого - так что ваша структура имеет смысл для меня.
Просто потому, что сотрудники доступны по отделу:
company/{companyid}/department/{departmentid}/employees
Не означает, что они не могут быть доступны и в компании:
company/{companyid}/employees
Что бы вернуть сотрудников для этой компании. Это зависит от того, что требуется вашему потребительскому клиенту - именно это вы и должны разрабатывать.
Но я надеюсь, что все обработчики URL-адресов используют один и тот же код поддержки для удовлетворения запросов, чтобы вы не дублировали код.
Ответ 2
Я пробовал обе стратегии разработки - вложенные и не вложенные конечные точки. Я обнаружил, что:
-
Если вложенный ресурс имеет первичный ключ и у вас нет родительского первичного ключа, вложенная структура требует его получения, даже если система на самом деле не требует его.
-
Вложенные конечные точки обычно требуют избыточных конечных точек. Другими словами, вы чаще всего будете нуждаться в конечной точке дополнительного/служащего, чтобы вы могли получить список сотрудников по отделам. Если у вас есть/сотрудники, что именно делает/компании/отделы/сотрудники покупают вас?
-
Вложенные конечные точки не так хорошо развиваются. Например. вам может не потребоваться поиск сотрудников, но вы можете позже, и если у вас есть вложенная структура, у вас нет выбора, кроме как добавить другую конечную точку. С не-вложенным дизайном вы просто добавляете больше параметров, что проще.
-
иногда ресурс может иметь несколько типов родителей. Результат в нескольких конечных точках возвращает все те же ресурсы.
-
избыточные конечные точки затрудняют запись документов, а также затрудняют изучение api.
Короче говоря, не-вложенный дизайн, по-видимому, позволяет использовать более гибкую и более простую схему конечных точек.
Ответ 3
Я переместил то, что я сделал от вопроса, к ответу, где больше людей, скорее всего, это увидит.
То, что я сделал, это иметь конечные точки создания на вложенной конечной точке. Каноническая конечная точка для изменения или запроса элемента не находится на вложенном ресурсе.
Таким образом, в этом примере (просто перечисление конечных точек, которые меняют ресурс)
-
POST
/companies/
создает новую компанию, возвращает ссылку на созданную компанию. -
POST
/companies/{companyId}/departments
при создании отдела создает новый отдел возвращает ссылку на /departments/{departmentId}
-
PUT
/departments/{departmentId}
изменяет отдел -
POST
/departments/{deparmentId}/employees
создают нового сотрудника, который возвращает ссылку на /employees/{employeeId}
Таким образом, для каждого из коллекций есть ресурсы уровня корня. Однако создание находится в собственном объекте.
Ответ 4
Я прочитал весь ответ выше, но похоже, что у меня нет общей стратегии. Я нашел хорошую статью о лучших практиках в API проектирования в документе Microsoft Documents. Я думаю, что вы должны обратиться.
В более сложных системах может быть заманчиво предоставить URI, которые дать возможность клиенту перемещаться по нескольким уровням отношений, такие как /customers/1/orders/99/products.
Однако этот уровень сложность может быть трудно поддерживать и является негибкой, если отношения между ресурсами изменятся в будущем. Вместо этого попробуйте сохранить URI относительно простыми. Как только приложение имеет ссылку на ресурс, должна быть возможность использовать эту ссылку для поиска предметов связанные с этим ресурсом. Предыдущий запрос можно заменить на URI /customers/1/orders
, чтобы найти все заказы для клиента 1, и затем /orders/99/products
, чтобы найти продукты в этом порядке.
.
Tip
Избегайте использования URI ресурса более сложным, чем collection/item/collection
.
Ответ 5
То, как выглядят ваши URL, не имеет ничего общего с REST. Все идет. На самом деле это "деталь реализации". Так же, как вы называете свои переменные. Все они должны быть уникальными и долговечными.
Не тратьте слишком много времени на это, просто сделайте выбор и придерживайтесь его/будьте последовательны. Например, если вы используете иерархию, вы делаете это для всех своих ресурсов. Если вы используете параметры запроса... и т.д., Так же, как соглашения о присвоении имен в вашем коде.
Почему так? Насколько я знаю, API "RESTful" должен быть доступен для просмотра (вы знаете... "Гипермедиа как движок состояния приложения"), поэтому клиент API не заботится о том, на что похожи ваши URL, пока они действительный (там нет SEO, нет человека, который должен читать эти "дружеские ссылки", кроме как для отладки...)
То, насколько приятный/понятный URL-адрес в REST API, интересует вас, как разработчика API, а не клиента API, как было бы имя переменной в вашем коде.
Самое главное, чтобы ваш клиент API знал, как интерпретировать ваш тип мультимедиа.
Например, он знает, что:
- у вашего типа мультимедиа есть свойство links, в котором перечислены доступные/связанные ссылки.
- Каждая ссылка идентифицируется отношением (так же, как браузеры знают, что ссылка [rel= "stylesheet"] означает, что это таблица стилей, или rel= favico - это ссылка на favicon...)
- и он знает, что означают эти отношения ("компании" означают список компаний, "поиск" означает шаблонный URL для поиска в списке ресурсов, "отделы" означают отделы текущего ресурса)
Ниже приведен пример обмена HTTP (тела в yaml, так как его легче написать):
Запрос
GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml
Ответ: список ссылок на основной ресурс (компании, люди, что угодно...)
HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml
# body: this is your API entrypoint (like a homepage)
links:
# could be some random path https://api.acme.local/modskmklmkdsml
# the only thing the API client cares about is the key (or rel) "companies"
companies: https://api.acme.local/companies
people: https://api.acme.local/people
Запрос: ссылка на компании (используя предыдущий ответ body.links.companies)
GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml
Ответ: частичный список компаний (по элементам), ресурс содержит связанные ссылки, например ссылку, чтобы получить следующую пару компаний (body.links.next), другую (шаблонную) ссылку для поиска (тело)..links.search)
HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml
# body: representation of a list of companies
links:
# link to the next page
next: https://api.acme.local/companies?page=2
# templated link for search
search: https://api.acme.local/companies?query={query}
# you could provide available actions related to this resource
actions:
add:
href: https://api.acme.local/companies
method: POST
items:
- name: company1
links:
self: https://api.acme.local/companies/8er13eo
# and here is the link to departments
# again the client only cares about the key department
department: https://api.acme.local/companies/8er13eo/departments
- name: company2
links:
self: https://api.acme.local/companies/9r13d4l
# or could be in some other location !
department: https://api2.acme.local/departments?company=8er13eo
Итак, как вы видите, если вы идете по ссылкам/отношениям, то, как вы структурируете часть пути ваших URL, не имеет никакого значения для вашего API-клиента. И если вы сообщаете клиенту структуру ваших URL-адресов в качестве документации, то вы не выполняете REST (или, по крайней мере, не уровень 3 согласно "модель зрелости Ричардсона")
Ответ 6
Я не согласен с таким путем
GET /companies/{companyId}/departments
Если вы хотите получить отделы, я думаю, что лучше использовать ресурс /department
GET /departments?companyId=123
Я полагаю, что у вас есть таблица companies
и таблица departments
, а затем классы для их сопоставления на используемом вами языке программирования. Я также предполагаю, что отделы могут быть привязаны к другим объектам, кроме компаний, поэтому ресурс /department просто, удобно иметь ресурсы, сопоставленные с таблицами, а также вам не нужно столько конечных точек, поскольку вы можете повторно использовать
GET /departments?companyId=123
для любого вида поиска, например
GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.
Если вы хотите создать отдел,
POST /departments
должен использоваться ресурс, а орган запроса должен содержать идентификатор компании (если отдел может быть связан только с одной компанией).
Ответ 7
Rails предлагает решение этой проблемы: мелкое вложение.
Я думаю, что это хорошо, потому что когда вы имеете дело непосредственно с известным ресурсом, нет необходимости использовать вложенные маршруты, как обсуждалось в других ответах здесь.