Дизайн RESTful api, HATEOAS и обнаружение ресурсов

Одна из основных идей, лежащих в основе HATEOAS, заключается в том, что клиенты должны иметь возможность запускаться с единого URL-адреса точки входа и обнаруживать все доступные ресурсы и переходы состояний, доступные для них. Хотя я прекрасно вижу, как это работает с HTML и человеком, стоящим за браузером, нажимающим на ссылки и кнопки «Отправить», меня спрашивают, как этот принцип можно применить к проблемам, с которыми мне (не) повезло иметь дело.

Мне нравится, как принцип дизайна RESTful представлен в статьях и учебных статьях, где все имеет смысл, Как получить чашку кофе - хороший тому пример. Я постараюсь следовать соглашению и предложить простой и свободный от утомительных деталей пример. Давайте посмотрим на почтовые индексы и города.

Проблема 1

Допустим, я хочу разработать RESTful api для поиска городов по почтовым индексам. Я придумал ресурсы под названием «города», вложенные в почтовые индексы, так что GET on http://api.addressbook.com/zip_codes/02125/cities возвращает документ, содержащий, скажем, две записи, которые представляют Дорчестер и Бостон.

У меня вопрос: как можно найти такой URL-адрес через HATEOAS? Вероятно, нецелесообразно выставлять индекс всех ~ 40K почтовых индексов в http://api.addressbook.com/zip_codes. Даже если индекс элементов в 40 КБ не является проблемой, помните, что я придумал этот пример, и существуют коллекции гораздо большего размера.

По сути, я хотел бы раскрыть не ссылку, а шаблон ссылки, скорее, вот так: http://api.addressbook.com/zip_codes/{:zip_code}/cities, а это противоречит принципам и основывается на внеполосных знаниях, которыми обладает клиент.

Проблема 2

Допустим, я хочу открыть индекс городов с определенными возможностями фильтрации:

  • GET on http://api.addressbook.com/cities?name=X вернет только города с названиями, соответствующими X.

  • GET on http://api.addressbook.com/cities?min_population=Y вернет только города с населением, равным или превышающим Y.

Конечно, эти два фильтра можно использовать вместе: http://api.addressbook.com/cities?name=X&min_population=Y.

Здесь я хотел бы показать не только url, но также эти два возможных варианта запроса и тот факт, что их можно комбинировать. Это кажется просто невозможным без внеполосного знания клиентом семантики этих фильтров и принципов их объединения в динамические URL-адреса.

Итак, как принципы, лежащие в основе HATEOAS, могут помочь сделать такой тривиальный API действительно RESTful?


person Serge Balyuk    schedule 01.02.2012    source источник
comment
Кстати, я также изучаю варианты управления версиями API, и поэтому заманчиво иметь единую точку входа для каждой версии. Что-то вроде этих строк.   -  person Serge Balyuk    schedule 02.02.2012


Ответы (5)


Предлагаю использовать формы XHTML:

GET /

HTTP/1.1 OK

<form method="get" action="/zip_code_search" rel="http://api.addressbook.com/rels/zip_code_search">
   <p>Zip code search</p>
   <input name="zip_code"/>
</form>

GET /zip_code_search?zip_code=02125

HTTP/1.1 303 See Other
Location: /zip_code/02125

В HTML отсутствует атрибут rel для form.

Прочтите эту статью:

Подводя итог, можно сказать, что есть несколько причин рассматривать XHTML как представление по умолчанию для ваших служб RESTful. Во-первых, вы можете использовать синтаксис и семантику для таких важных элементов, как <a>, <form> и <input>, вместо того, чтобы изобретать свои собственные. Во-вторых, вы получите сервисы, которые во многом напоминают сайты, потому что их будут просматривать как пользователи, так и приложения. XHTML по-прежнему интерпретируется человеком - это просто программист во время разработки, а не пользователь во время выполнения. Это упрощает процесс разработки и позволяет потребителям узнать, как работает ваш сервис. И, наконец, вы можете использовать стандартные среды веб-разработки для создания своих RESTful-сервисов.

Также ознакомьтесь с OpenSearch.


Чтобы уменьшить количество запросов, рассмотрите этот ответ:

HTTP/1.1 200 OK
Content-Location: /zip_code/02125

<html>
<head>
<link href="/zip_code/02125/cities" rel="related http://api.addressbook.com/rels/zip_code/cities"/>
</head>
...
</html>
person Max Toro    schedule 01.02.2012
comment
Это интересно и помогает с проблемой 2, но как вы можете представить проблему 1 в форме? То же самое для особых требований к заголовкам HTTP. Я думаю, что формы XHTML гораздо менее выразительны, чем WADL. - person Chris Dolan; 02.02.2012
comment
@Chris Dolan: Мой пример касался проблемы 1. Другой способ решить эту проблему - использовать шаблоны URI. Я согласен, что WADL более полный, но с XHTML вы получаете пользовательский интерфейс бесплатно. WADL с <?xml-stylesheet?> должен быть милым. - person Max Toro; 02.02.2012
comment
да, но вы проигнорировали его API, который был /zip_codes/{:zip_code}/cities там, где вы определили /zip_code_search?zip_code={:zipcode} - совсем не то же самое и с xhtml немного проще. Тем не менее, я думаю, что на самом деле мы не так уж далеки от согласия. Быстрый поиск выявил некоторые таблицы стилей, которые, я думаю, подойдут: github.com/ipcsystems/wadl-stylesheet и github.com/mnot/wadl_stylesheets (я предпочитаю первое) - person Chris Dolan; 02.02.2012
comment
@Chris Dolan: Обратите внимание, что ответ содержит заголовок Location со ссылкой на /zip_codes/{zip_code}. Чтобы избежать слишком большого количества запросов, ответ может быть OK и содержать представление zip_code с заголовком Content-Location: /zip_codes/{zip_code} и ссылку на города в представлении. - person Max Toro; 02.02.2012

Это решение приходит на ум, но я не уверен, что действительно рекомендовал его: вместо возврата URL-адреса ресурса верните URL-адрес WADL, описывающий конечную точку. Пример:

<application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <grammars/>
  <resources base="http://localhost:8080/cities">
    <resource path="/">
      <method name="GET">
        <request>
          <param name="name" style="query" type="xs:string"/>
          <param name="min-population" style="query" type="xs:int"/>
        </request>
        <response>
          <representation mediaType="application/octet-stream"/>
        </response>
      </method>
    </resource>
  </resources>
</application>

Этот пример был автоматически сгенерирован CXF из этого кода Java:

import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

public class Cities {
    @GET
    public Response get(@QueryParam("name") String name, @QueryParam("min-population") int min_poulation) {
        // TODO: build the real response
        return Response.ok().build();
    }
}
person Chris Dolan    schedule 01.02.2012
comment
вау, спасибо за развернутый ответ! WADL - разумный путь, но разве это не приблизит нас сразу к домену SOAP? Я думал, что HATEOAS противоположен этому, как я понял из примера GET coffee и других подобных статей. - person Serge Balyuk; 01.02.2012
comment
Как я уже сказал, я не рекомендую это решение! Это всего лишь одна возможность. Я не думаю, что это так сложно, как WSDL / SOAP. Да, он более формален, чем zip_codes / {: zip_code} / cities, но имеет гораздо большую гибкость и машиночитаемый. Неявный момент HATEOAS - машиночитаемость REST API, верно? Что ж, я считаю это еще одним шагом в этом направлении. - person Chris Dolan; 01.02.2012

Отвечая на вопрос 1, я предполагаю, что ваша единственная точка входа - http://api.addressbook.com/zip_codes, и намерение состоит в том, чтобы позволить клиенту пройти всю коллекцию почтовых индексов и, в конечном итоге, получить города, связанные с ними.

В этом случае я бы заставил ресурс http://api.addressbook.com/zip_codes возвращать перенаправление на первую страницу почтовых индексов, например:

http://api.addressbook.com/zip_codes?start=0&end=xxxx

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

Это позволит клиенту сканировать весь список почтовых индексов, если он того пожелает.

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

http://api.addressbook.com/zip_codes/02125

И тогда будет вопрос о том, следует ли включать информацию о городе в представление, возвращаемое URL-адресом почтового индекса, или ссылку на него, в зависимости от необходимости.

Теперь у клиента есть выбор: просмотреть весь список почтовых индексов, а затем запросить почтовый индекс (а затем города) для каждого, или запросить страницу с почтовыми индексами, а затем запросить детализацию до части.

person Keith Bishop    schedule 17.04.2012

Я столкнулся с этими же вопросами - поэтому я проработал практический пример, который решает обе эти проблемы (и некоторые из них, о которых вы еще не думали). http://thereisnorightway.blogspot.com/2012/05/api-example-using-rest.html?m=1

По сути, решение проблемы 1 состоит в том, что вы меняете свое представление (как говорит Рой, тратите свое время на ресурс). Вам не нужно возвращать все zip-архивы, просто сделайте так, чтобы ваш ресурс содержал подкачку. Например, когда вы запрашиваете новостные страницы с новостного сайта - он дает вам сегодняшние новости и ссылки на другие, даже если все статьи могут находиться под одной и той же структурой URL-адресов, т. Е. ... статья / 123 и т. д.

Проблема 2 немного неудобна - в http есть немного используемая команда под названием OPTIONS, которую я использовал в примере, чтобы в основном отразить возможности URL-адреса - хотя вы могли бы решить эту проблему и в представлении, это было бы намного сложнее. По сути, он возвращает настраиваемую структуру, которая показывает возможности ресурса (включая необязательные параметры).

Дайте мне знать, что вы думаете!

person jeremyh    schedule 12.06.2012

Мне кажется, вы пропустили URL-адрес закладки. Это первый URL-адрес, а не тот, по которому можно получить города или почтовые индексы.

Итак, вы начинаете с ab: = http: //api.addressbook.com

Эта первая ссылка возвращает список доступных ссылок. Так работает Интернет. Вы переходите на www.yahoo.com и начинаете щелкать ссылки, не зная, куда они ведут.

Итак, из исходной ссылки ab: вы получите другие ссылки, и они могут иметь ссылки REL, которые объясняют, как следует обращаться к этим ресурсам или какие параметры могут быть отправлены.

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

Я согласен с вами в отношении «внеполосного знания клиентом семантики этих фильтров». Мне трудно купить, что машина может просто адаптироваться к тому, что есть, если у нее нет какой-то заранее заданной спецификации, такой как HTML. Более вероятно, что клиент создан разработчиком, который знает все возможности, а затем кодирует приложение, чтобы «потенциально» ожидать, что эти ссылки будут доступны. Если ссылка доступна, программа может использовать логику, реализованную разработчиком, прежде чем задействовать ресурс. Если его там нет, он просто не выполняет ссылку. В конце концов, возможные пути прокладываются до начала обхода приложения.

person suing    schedule 01.02.2012