У меня есть три вопроса о дизайне REST API, которые, я надеюсь, кто-то может пролить немного света. Я много часов искал, но нигде не нашел ответов на свои вопросы (может быть, я просто не знаю, что искать?).
Вопрос 1
Мой первый вопрос касается действий / RPC. Некоторое время я разрабатывал REST API, и я привык думать о вещах с точки зрения коллекций и ресурсов. Тем не менее, я столкнулся с парой случаев, когда парадигма, кажется, не применяется, и мне интересно, есть ли способ согласовать это с парадигмой REST.
В частности, у меня есть случай, когда изменение ресурса вызывает генерацию электронной почты. Однако позже пользователь может указать, что он хочет повторно отправить электронное письмо, которое было отправлено ранее. При повторной отправке электронного письма ни один ресурс не изменяется. Состояние не изменяется. Это просто действие, которое должно произойти. Действие связано с конкретным типом ресурса.
Целесообразно ли смешивать какой-либо вызов действия с URI ресурса (например /collection/123?action=resendEmail
)? Было бы лучше указать действие и передать ему идентификатор ресурса (например /collection/resendEmail?id=123
)? Это неправильный путь? Традиционно (по крайней мере, с HTTP) выполняемое действие является методом запроса (GET, POST, PUT, DELETE), но в действительности они не допускают пользовательских действий с ресурсом.
вопрос 2
Я использую часть строки запроса в URL для фильтрации набора ресурсов, возвращаемых при запросе к коллекции (например /collection?someField=someval
). Затем в моем контроллере API я определяю, какое сравнение он будет делать с этим полем и значением. Я обнаружил, что это действительно не работает. Мне нужен способ, позволяющий пользователю API указывать тип сравнения, который он хочет выполнить.
Лучшая идея , которую я придумал до сих пор , чтобы позволить пользователю API , чтобы указать его в качестве придатка к имени поля (например , /collection?someField:gte=someval
- чтобы указать , что он должен возвращать ресурсы туда , где someField
больше или равно (> =) все , что someval
есть Является ли это хорошей идеей? Плохой идеей? Если да, то почему? Существует ли лучший способ, позволяющий пользователю указать тип сравнения для выполнения с данным полем и значением?
Вопрос 3
Я часто вижу URI, которые выглядят примерно так, /person/123/dogs
чтобы получить person
s dogs
. Я обычно избегал чего-то подобного, потому что в конце я понимаю, что, создавая такой URI, вы на самом деле просто получаете доступ к dogs
коллекции, отфильтрованной по определенному person
идентификатору. Это было бы эквивалентно /dogs?person=123
. Есть ли когда-нибудь веская причина для того, чтобы REST URI был больше двух уровней ( /collection/resource_id
)?
Ответы:
Я предпочел бы смоделировать это по-другому, с набором ресурсов, представляющих электронные письма, которые должны быть отправлены; отправка будет обработана внутренними службами в надлежащее время, после чего соответствующий ресурс будет удален. (Или пользователь может УДАЛИТЬ ресурс раньше, вызывая отмену запроса на отправку.)
Что бы вы ни делали, не помещайте глаголы в название ресурса! Это существительное (а часть запроса - это набор прилагательных). Существующие глаголы странники ОТДЫХ!
Я бы предпочел указать общее предложение фильтра и использовать его в качестве необязательного параметра запроса для любого запроса для извлечения содержимого коллекции. Затем клиент может точно указать, как ограничить возвращаемый набор любым желаемым способом. Я бы также немного беспокоился об обнаружении языка фильтра / запроса; чем богаче вы его делаете, тем сложнее его обнаружить произвольным клиентам. Альтернативный подход, который, по крайней мере теоретически, имеет дело с этой проблемой обнаруживаемости, состоит в том, чтобы разрешить создание подресурсов ограничения коллекции, которые клиенты получают, размещая документ, описывающий ограничение ресурса коллекции. Это все еще небольшое злоупотребление, но, по крайней мере, это то, что вы можете явно обнаружить!
Такой вид обнаружения - одна из тех вещей, которые я нахожу наименее сильной в REST.
Когда вложенная коллекция действительно является подфункцией сущностей-членов внешней коллекции, целесообразно структурировать их как подресурс. Под «подфункцией» я подразумеваю что-то вроде композиционных отношений UML, где уничтожение внешнего ресурса естественным образом означает уничтожение внутренней коллекции.
Другие типы коллекции могут быть смоделированы как перенаправление HTTP; таким образом, на него
/person/123/dogs
действительно можно ответить, выполнив 307, на которую перенаправляется/dogs?person=123
. В этом случае коллекция на самом деле не является композицией UML, а скорее агрегацией UML. Разница имеет значение; это важно!источник
resendEmail
действие можно обработать, создав коллекцию и разместив в ней POSTing, это кажется менее естественным. На самом деле я ничего не храню в базе данных, когда электронная почта отправляется повторно (нет необходимости). Ни один ресурс не изменяется, поэтому это просто действие, которое либо успешно, либо неудачно. Я не мог вернуть идентификатор ресурса, который существует за пределами срока действия вызова, что делает такую реализацию хаком вместо того, чтобы быть RESTful. Это просто не операция CRUD.Понятно, что я немного запутался в том, как правильно использовать REST, основываясь на том, как крупные компании разрабатывают свои API-интерфейсы для REST.
Вы правы в том, что REST является системой сбора ресурсов. Это означает представительский государственный трансферт. Не очень хорошее определение, если вы спросите меня. Но основными понятиями являются 4 HTTP VERB и отсутствие состояния.
Важно отметить, что у вас есть только 4 VERBS с REST. Это GET, POST, PUT и DELETE. Ваш
resend
пример будет добавить новый глагол для REST. Это должен быть красный флаг.Вопрос 1
Важно понимать, что вызывающая сторона вашего REST API не должна знать, что выполнение
PUT
вашей коллекции приведет к генерации электронной почты. Это пахнет утечкой для меня. То, что они могли знать, - то, что выполнениеPUT
могло привести к дополнительным задачам, которые они могли бы запросить позже. Они узнают об этом, выполнивGET
недавно созданный ресурс. ЭтоGET
вернет ресурс и всеTask
идентификаторы ресурса, связанные с ним. Позже вы можете запросить эти задачи, чтобы определить их статус и даже отправить новоеTask
.У вас есть несколько вариантов.
REST - Подход, основанный на ресурсах задач
Создайте
tasks
ресурс, в котором вы можете отправлять конкретные задачи в вашу систему для выполнения действий. Затем вы можетеGET
выполнить задание на основеID
возвращенного значения, чтобы определить его состояние.Или вы можете смешать
SOAP over HTTP
веб-сервис, чтобы добавить RPC в вашу архитектуру.Запросы для всех задач для конкретного ресурса
GET http://server/api/myCollection/123/tasks
пример ресурса задачи
PUT http://server/api/tasks
==> возвращает идентификатор задачи
223334
GET http://server/api/tasks/223334
REST - использование POST для запуска действий
Вы всегда можете
POST
добавить дополнительные данные к ресурсу. На мой взгляд, это нарушит дух REST, но все равно будет соответствовать.Вы можете сделать POST похожим на это:
POST http://server/api/collection/123
{ "action" : "send-email" }
Вы будете обновлять ресурс 123 из коллекции дополнительными данными. Эти дополнительные данные, по сути, являются действием, указывающим бэкэнду отправить электронное письмо для этого ресурса.
Проблема, с которой я столкнулся, заключается в том,
GET
что ресурс на этом ресурсе вернет эти обновленные данные. Тем не менее, это решит ваши требования и все равно будет RESTful.SOAP - веб-служба, которая принимает ресурсы, полученные от REST
Создайте новый WebService, в котором вы можете отправлять электронные письма на основе предыдущего идентификатора ресурса из REST API. Я не буду вдаваться в подробности о SOAP, поскольку первоначальный вопрос касается REST, и эти две концепции / технологии не следует сравнивать, так как это яблоки и апельсины .
вопрос 2
У вас также есть несколько вариантов здесь:
Похоже, что многие крупные компании, публикующие REST API, предоставляют
search
коллекцию, которая на самом деле является просто способом передачи параметров запроса для возврата ресурсов.GET http://server/api/search?q="type = myCollection & someField >= someval"
Который возвратил бы коллекцию полностью определенных ресурсов REST, таких как:
Или вы можете разрешить что-то вроде MVEL в качестве параметра запроса.
Вопрос 3
Я предпочитаю подуровни, чем возвращаться и запрашивать другой ресурс с параметром запроса. Я не верю, что есть какое-то правило, так или иначе. Вы можете реализовать оба способа и позволить вызывающим абонентам решать, какой из них более уместен, исходя из того, как они впервые вошли в систему.
Ноты
Я не согласен с читаемостью комментариев от других. Несмотря на то, что некоторые могут подумать, REST все еще не для потребления человеком. Это для машинного потребления. Если я хочу видеть свои твиты, я использую обычный веб-сайт Twitters. Я не выполняю REST GET с их API. Если я хочу программно что-то сделать со своими твитами, тогда я использую их REST API. Да, API должны быть понятными, но вы
gte
не так уж и плохи, просто они не интуитивно понятны.Другая важная вещь в REST - это то, что вы должны иметь возможность начинать с любого заданного пункта в вашем API и переходить ко всем другим связанным ресурсам, БЕЗ знания точного URL других ресурсов заранее. Результаты
GET
VERB в REST должны возвращать полный REST URL ресурсов, на которые он ссылается. Таким образом, вместо запроса, возвращающего идентификаторPerson
объекта, он возвращает полный URL-адрес, такой какhttp://server/api/people/13
. Тогда вы всегда можете программно перемещаться по результатам, даже если URL изменился.Ответ на комментарий
На самом деле в реальности существуют вещи, которые не должны создавать, читать, обновлять или удалять (CRUD) ресурс.
Дополнительные действия могут быть предприняты на ресурсах. Типичные реляционные базы данных поддерживают концепцию хранимых процедур. Это дополнительные команды, которые могут быть выполнены для набора данных. REST по своей сути не имеет этой концепции. И нет причин, по которым это следует. Эти типы действий идеально подходят для RPC или SOAP Web Services.
Это общая проблема, которую я вижу с REST API. Разработчикам не нравятся концептуальные ограничения, окружающие REST, поэтому они приспосабливают его к тому, что им нравится. Это ломает это от того, чтобы быть RESTful сервисом все же. По сути, эти URL-адреса становятся
GET
вызовами для псевдо-REST-подобных сервлетов.У вас есть несколько вариантов:
POST
дополнительных данных к ресурсу для выполнения действия.Если бы вы использовали параметр запроса, какой HTTP VERB вы бы использовали для повторной отправки электронной почты?
GET
- Повторно ли отправляется электронное письмо И возвращаются ли данные ресурса? Что если система кэширует этот URL и обрабатывает его как уникальный URL для этого ресурса. Каждый раз, когда они нажимают на URL, они пересылают письмо.POST
- На самом деле вы не отправляли новые данные на ресурс, только дополнительный параметр запроса.Исходя из всех данных требований, решение проблемы
POST
с ресурсом сaction field
данными POST.источник
Вопрос 1: Целесообразно ли смешивать какой-либо вызов действия с URI ресурса [или], лучше ли указать действие и передать ему идентификатор ресурса?
Хороший вопрос. В этом случае я бы посоветовал вам использовать последний подход, а именно указать действие и передать ему идентификатор ресурса. Таким образом, когда ваш ресурс впервые изменяется, он, в свою очередь, вызывает
/sendEmail
действие (примечание: не нужно называть его «повторно») как отдельный запрос RESTful (который вы можете позже вызывать снова и снова, независимо от того, какой ресурс изменен). ).Вопрос 2: относительно использования оператора сравнения, например, так:
/collection?someField:gte=someval
Хотя это технически нормально, это, вероятно, плохая идея. Одним из ключевых принципов REST является удобочитаемость. Я бы посоветовал вам просто передать оператор сравнения в качестве другого параметра, например:
/collection?someField=someval&operator=gte
и, конечно, спроектировать свой API так, чтобы он обслуживал случай по умолчанию (в случае, еслиoperator
параметр не указан в URI).Вопрос 3: Есть ли когда-нибудь веская причина для того, чтобы REST URI имел глубину более двух уровней?
Ага; для абстракции. Я видел пару REST API, которые используют уровни абстракции через несколько уровней URI, например:
/vehicles/cars/123
или/vehicles/bikes/123
которые, в свою очередь, позволяют работать с полезной информацией, относящейся как к коллекциям, так/vehicles
и к/vehicles/bikes
коллекциям. Сказав это, я не большой поклонник этого подхода; вам редко придется делать это на практике, и есть вероятность, что вы могли бы перепроектировать API, чтобы использовать только 2 уровня.И да, как показывают комментарии выше, в будущем было бы лучше разделить ваши вопросы на отдельные посты;)
источник
/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
.Для вопроса 2, другая альтернатива может быть более гибкой: рассмотрите каждый поиск ресурс, который пользователь создает перед использованием.
Допустим, у вас есть «поисковый» контейнер, там вы делаете
POST /api/searches/
со спецификацией запроса на контент. это может быть JSON, XML или даже документ SQL, что вам проще. Если запрос анализируется правильно, новый поиск создается как новый ресурс с собственным URI, скажем,/api/searches/q123/
Затем клиент может просто
GET /api/searches/q123/
получить результаты запроса.Наконец, вы можете либо попросить клиента удалить запрос, либо очистить его после закрытия сеанса.
источник
Нет, это не подходит, так как IRI предназначены для идентификации ресурсов, а не операций (однако ppl некоторое время использует этот метод переопределения методов , в случаях, когда использование не POST и GET методов не поддерживается). Что вы можете сделать, так это найти подходящий метод HTTP или создать новый. POST может быть вашим другом в этих случаях (ppl использует его, если они не могут найти подходящий метод и запрос не является поисковым). Еще один подход к получению ресурсов от отправки электронной почты и так
POST /emails
может отправлять письма без создания реального ресурса. Btw. Структура URI не несет семантики, поэтому с точки зрения REST не имеет значения, какой тип URI вы используете. Важны метаданные (например, отношение ссылок ), назначенные ссылкам, которые вы отправляли клиентам.Вам не нужно создавать собственный язык запросов. Я бы предпочел использовать уже существующий и добавить некоторое описание запроса к метаданным ссылки. Возможно, вам следует использовать тип носителя RDF (например, JSON-LD) или использовать пользовательский тип MIME (на самом деле, не существует формата, не поддерживающего RDF, который поддерживает это). Использование существующих стандартов позволяет отделить ваш клиент от сервера, это то, к чему относится унифицированное ограничение интерфейса.
Как я упоминал ранее, структура URI не имеет значения с точки зрения REST. Вы можете использовать,
/x71fd823df2
например. Это по-прежнему имеет смысл для клиентов, поскольку они проверяют метаданные, назначенные ссылкам, а не структуру URI. Основное назначение URI - идентификация ресурсов. В стандарте URI утверждается, что путь содержит иерархические данные, а запрос содержит неиерархические данные. Но это может быть очень субъективным, что является иерархическим. Вот почему вы встречаете многоуровневые URI и URI с длинными запросами.Вы должны прочитать хотя бы ограничения REST из диссертации Fielding , стандарта HTTP и, возможно, веб-API третьего поколения от Markus.
источник