Подтвердить что ты не робот

Дизайн API REST: вложенная коллекция против нового корня

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

Чтобы продемонстрировать концепцию, предположим, что у меня есть коллекции City, Business и Employees. Типичный API может быть построен следующим образом. Представьте, что ABC, X7N и WWW являются ключами, например. Идентификаторы GUID:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/City/ABC/Businesses/X7N                   (returns business X7N)
GET Api/City/ABC/Businesses/X7N/Employees         (returns all employees at business X7N)
PUT Api/City/ABC/Businesses/X7N/Employees/WWW     (updates employee WWW)

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

Вот что должен делать пользователь API:

  • Получить бизнес в городе (GET Api/City/ABC/Businesses)
  • Получить всех сотрудников в бизнесе (GET Api/City/ABC/Businesses/X7N/Employees)
  • Обновить информацию о персональном сотруднике (PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

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

  • Чтобы получить сотрудников в бизнесе, единственным необходимым параметром является ключ к бизнесу (X7N).
  • Чтобы обновить отдельного сотрудника, единственным параметром должен был быть ключ сотрудника (WWW)

Ничто в коде бэкэнд не требует некрупной информации для поиска бизнеса или обновления сотрудника. Таким образом, вместо этого появляются следующие конечные точки:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Businesses/X7N/Employees                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

Как вы можете видеть, я создал новый root для предприятий и сотрудников, хотя с точки зрения домена они являются суб/суб-подсетей.

Ни одно из решений не кажется мне очень чистым.

  • Первый пример запрашивает ненужную информацию, но структурирован таким образом, который кажется "естественным" для потребителя (отдельные элементы из коллекции извлекаются через нижние листья).
  • Второй пример запрашивает только необходимую информацию, но не структурирован "естественным образом" - подколлекции доступны через корни.
  • Индивидуальный корень сотрудника не будет работать при добавлении нового сотрудника, так как нам нужно знать, к какой компании добавить сотрудника, а это значит, что вызов должен, по крайней мере, находиться в корневом каталоге "Бизнес", например POST Api/Businesses/X7N7/Employees, что делает все еще более запутанным.

Есть ли более чистый, третий способ, о котором я не думаю?

4b9b3361

Ответ 1

Я не вижу, как REST добавляет ограничение, что два ресурса не могут иметь одинаковое значение. resourceType/ID - это всего лишь пример самого простого варианта использования, а не лучший способ перейти с точки зрения RESTful.

Если вы внимательно прочитаете пункт 5.2.1.1 диссертации Роя Филдинга, вы заметите, что Филдинг делает различие между значением и ресурс. Теперь ресурс должен иметь уникальный URI, это правда. Но ничто не мешает двум ресурсам иметь такое же значение:

Например, "предпочтительная версия для авторов" учебной статьи - это сопоставление, значение которого меняется со временем, тогда как сопоставление с "статьей, опубликованной в материалах конференции X", является статичным. Это два разных ресурса, , даже если они оба сопоставляются с одним и тем же значением в определенный момент времени. Различие необходимо для того, чтобы оба ресурса можно было идентифицировать и ссылаться независимо. Аналогичным примером разработки программного обеспечения является отдельная идентификация файла исходного кода, управляемого версией, при обращении к "последней ревизии", "номер версии 1.2.7" или "ревизия, включенная в выпуск Orange".

Таким образом, ничто не мешает вам, как вы говорите, изменить корень. В вашем примере a Business - это значение, а не ресурс. Совершенно RESTful создает ресурс, который является списком "каждого бизнеса, расположенного в городе" (как пример Роя, "исправления, включенные в выпуск Orange" ), при этом также "ресурс, который имеет идентификатор x" (например, "номер версии x" ).

Для Employees я бы сохранил API/Businesses/X7N/Employees, так как отношение между бизнесом и его сотрудниками - это состав, и, как вы говорите, Employees может и должен быть доступен только через корень класса Businesses. Но это не требование REST, а другая альтернатива - тоже RESTful.


Обратите внимание, что это идет в паре с применением принципа HATEAOS. В вашем API список предприятий, расположенных в городе, может (и, возможно, с теоретической точки зрения) быть просто списком ссылок на API/Businesses. Но это будет означать, что клиентам придется совершать однократную поездку на сервер для каждого из элементов в списке. Это неэффективно и, чтобы оставаться прагматичным, то, что я делаю, встраивает представление бизнеса в список вместе с ссылкой self к URI, который будет в этом примере API/Businesses.

Ответ 2

Не следует путать REST с приложением определенного соглашения об именах URI.

КАК Имена ресурсов полностью вторичны. Вы пытаетесь использовать соглашения об именовании ресурсов HTTP - это не имеет никакого отношения к REST. Рой Филдинг сам так неоднократно заявляет в документах, цитируемых выше другими. REST не является протоколом, это архитектурный стиль.

Фактически, Рой Филдинг заявляет в своем комментарии к блогу 2008 года (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 6/20/2012):

"API REST не должен определять фиксированные имена ресурсов или иерархии (очевидное совпадение клиент и сервер). Серверы должны иметь свободу контролировать свое пространство имен. Вместо, разрешить серверам инструктировать клиентов о том, как создавать соответствующие URI, например, в HTML-формы и шаблоны URI, определяя эти инструкции в типах медиа и связывании ссылок. "

Итак, по существу:

Проблема, которую вы описываете, на самом деле не проблема REST - концептуально, это проблема ИЕРАРХИЧЕСКИХ КОНСТРУКЦИЙ по сравнению с ОТНОШЕНИЕМ СТРУКТУР.

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

Дело в том, что вы можете просматривать данные с разных углов, и в зависимости от точки зрения, которую вы принимаете, может быть проще всего рассматривать ее как иерархию. Но те же данные можно рассматривать как иерархию с разными уровнями. Когда вы используете имена ресурсов типа HTTP, вы ввели иерархическую структуру, определенную HTTP. Это ограничение, да, но это не ограничение REST, это ограничение HTTP.

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

Подробнее:

Единственные реальные ограничения REST, которые у вас есть, если вы уже решили использовать HTTP с GET PUT и т.д.:

    • Вы не должны предполагать никаких предварительных ( "вне" ) знаний между клиентом и серверами. *

Посмотрите на свое предложение № 1 выше в этом свете. Вы предполагаете, что клиенты знают ключи для городов, которые содержатся в вашей системе? Неправильно - это не успокаивает. Таким образом, сервер должен каким-то образом отобразить список городов как список вариантов. Так вы собираетесь перечислять каждый город в мире здесь? Наверное, нет, но тогда вам придется немного поработать над тем, как вы планируете это делать, что приводит нас к:

    • API REST должен тратить почти все свои описательные усилия на определение типов (типов) мультимедиа, используемых для представления ресурсов и управления состоянием приложения...

Я думаю, что чтение упомянутого блога Роя Филдинга поможет вам значительно.

Ответ 3

В дизайне URL-адреса RESTful-API должно быть совсем неважно - или, по крайней мере, побочная проблема, поскольку обнаружение кодируется в гипертексте, а не в URL-адресе. Посмотрите на связанные ресурсы в REST тег wiki здесь, в StackOverflow.

Но если вы хотите создать удобочитаемые URL-адреса для вашего UC, я бы предложил следующее:

  • Используйте тип ресурса, который вы создаете/обновляете/запрашиваете, как первую часть URL (после вашего префикса API). Поэтому, когда кто-то видит URL-адрес, он сразу же узнает, на какие ресурсы указывает этот URL-адрес. GET /Api/Employees... - единственный способ получить ресурсы Employee из API.

  • Используйте уникальные идентификаторы для каждого ресурса независимо от отношений, которые они находятся в. Поэтому GET /Api/<CollectionType>/UniqueKey должен возвращать действительное представление ресурса. Никто не должен беспокоиться о том, где находится Сотрудник. (Но возвращенный Сотрудник должен иметь ссылки на Бизнес (и для удобства города), к которому принадлежит.) GET /Api/Employees/Z6W возвращает Работника с этим идентификатором независимо от того, где находится.

  • Если вы хотите получить определенный ресурс: Поместите свой параметр запроса в конец (вместо этого в иерархическом порядке, описанном в вопросе). Вы можете использовать строку запроса URL (GET /Api/Employees?City=X7N) или выражение параметра матрицы (GET /Api/Employees;City=X7N;Business=A4X,A5Y). Это позволит вам легко выразить коллекцию всех сотрудников в определенном городе - независимо от бизнеса, в котором они находятся.

Сторона node:

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

Ответ 4

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

GET Api/Businesses?city=ABC                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Employees?businesses=X7N                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

В ваших обоих решениях используется концепция подресурсов REST, которая требует, чтобы субресурс включался в родительский ресурс, поэтому:

GET Api/City/ABC/Businesses

В ответ следует также вернуть данные, предоставленные:

  GET Api/City/ABC/Businesses/X7N                 
  GET Api/City/ABC/Businesses/X7N/Employees 

похож на:

GET Api/Businesses/X7N

который должен возвращать данные, предоставленные:

GET Api/Businesses/X7N/Employees

Это сделает размер ответа огромным, и время, необходимое для генерации, будет увеличиваться.

Чтобы очистить REST API, каждый ресурс должен иметь только один ограниченный URI, который парит ниже шаблонов:

 GET  /resources
 GET  /resources/{id}
 POST /resources
 PUT  /resources/{id}

Если вам нужно сделать ссылки между ресурсами, используйте HATEOAS

Ответ 5

Перейдите к примеру 1. Я не стал бы беспокоиться о ненужной информации с точки зрения сервера. URL должен четко идентифицировать ресурс уникальным образом с точки зрения клиента. Если клиент не знает, что означает /Employee/12, не зная сначала, что это на самом деле /Businesses/X7N/Employees/12, тогда первый URL-адрес кажется излишним.

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