Комплекс REST/композит/вложенные ресурсы

Я пытаюсь понять, как лучше всего обращаться с концепциями в API на основе REST. Плоские ресурсы, которые не содержат других ресурсов, не проблема. Где я сталкиваюсь с проблемами, так это в сложных ресурсах.

Например, у меня есть ресурс для комикса. ComicBook имеет всевозможные свойства, такие как author, issue number, date и т. д.

У комикса также есть список 1..n обложек. Эти покрытия являются сложными объектами. Они содержат много информации об обложке: имя исполнителя, дату и даже изображение обложки в кодировке base 64.

Для GET на ComicBook я мог бы просто вернуть комикс и все обложки, включая их изображения в формате base64. Это, вероятно, не имеет большого значения для получения одного комикса. Но предположим, что я создаю клиентское приложение, которое хочет перечислить все комиксы в системе в виде таблицы.
Таблица будет содержать несколько свойств из ресурса ComicBook, но мы, конечно же, не собираемся отображать все крышки в таблице. Возврат 1000 комиксов, каждый с несколькими обложками, приведет к смехотворно большому количеству данных, передаваемых по сети, данных, которые в этом случае не нужны конечному пользователю.

Мой инстинкт состоит в том, чтобы сделать Cover ресурсом, а ComicBook содержать обложки. Итак, теперь Cover — это URI. GET в комиксах теперь работает, вместо огромного ресурса Cover мы отправляем обратно URI для каждой обложки, и клиенты могут получать ресурсы обложки по мере необходимости.

Теперь у меня проблема с созданием новых комиксов. Конечно, я захочу создать по крайней мере одну обложку, когда создам Comic, на самом деле это, вероятно, бизнес-правило.
Итак, теперь я застрял, я либо заставляю клиентов применять бизнес-правила, сначала отправляя Cover, получая URI для этой обложки, затем POSTсоставляя ComicBook с этим URI в списке, или мой POST на ComicBook получает ресурс, выглядящий иначе, чем выдает. Входящие ресурсы для POST и GET являются глубокими копиями, где исходящие GET содержат ссылки на зависимые ресурсы.

Ресурс Cover, вероятно, необходим в любом случае, потому что я уверен, что как клиент я хотел бы в некоторых случаях обратиться к направлению покрытия. Так что проблема существует в общем виде вне зависимости от размера зависимого ресурса. В общем, как вы обрабатываете сложные ресурсы, не заставляя клиента просто «знать», как эти ресурсы составлены?


person jgerman    schedule 18.08.2011    source источник
comment
имеет ли смысл использовать RESTFUL SERVICE DISCOVERY?   -  person treecoder    schedule 18.08.2011
comment
Я пытаюсь придерживаться HATEAOS, что, на мой взгляд, противоречит использованию чего-то подобного, но я посмотрю.   -  person jgerman    schedule 18.08.2011
comment
Другой вопрос в том же духе. Однако право собственности отличается от предложенного вами решения (того, о котором идет речь). stackoverflow.com/ вопросы/20951419/   -  person Wes    schedule 06.01.2014


Ответы (2)


@ray, отличная дискуссия

@jgerman, не забывайте, что только потому, что это REST, не означает, что ресурсы должны быть высечены в камне из POST.

Что вы решите включить в любое данное представление ресурса, зависит от вас.

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

Ваш конкретный случай с обложками немного сложнее, поскольку для обложки действительно требуется комикс, и наоборот.

Однако, если вы рассматриваете сообщение электронной почты как ресурс, а адрес отправителя — как дочерний ресурс, вы, очевидно, все равно можете ссылаться на адрес отправителя отдельно. Например, получить все с адресов. Или создайте новое сообщение с предыдущим адресом отправителя. Если бы электронная почта была REST, вы могли бы легко увидеть, что многие ресурсы с перекрестными ссылками могут быть доступны: /received-messages, /draft-messages, /from-addresses, /to-addresses, /addresses, /subjects, /attachments, /folders , /tags, /categories, /labels и др.

В этом руководстве представлен отличный пример ресурсов с перекрестными ссылками. http://www.peej.co.uk/articles/restfully-delicious.html

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

Пример в вашем случае двоичных данных. Например, вы хотите опубликовать двоичные данные в качестве дочерних ресурсов. Когда вы получаете родительский ресурс, вы можете представить эти дочерние ресурсы как одни и те же двоичные данные или как URI, которые представляют двоичные данные.

Формы и параметры уже отличаются от HTML-представлений ресурсов. Публикация двоичного/файлового параметра, который приводит к URL-адресу, не является натяжкой.

Когда вы получаете форму для нового ресурса (/comic-books/new) или получаете форму для редактирования ресурса (/comic-books/0/edit), вы запрашиваете конкретное для форм представление ресурса. Если вы отправляете его в коллекцию ресурсов с типом содержимого «application/x-www-form-urlencoded» или «multipart/form-data», вы просите сервер сохранить представление этого типа. Сервер может ответить сохраненным HTML-представлением или чем-то еще.

Вы также можете разрешить отправку представления HTML, XML или JSON в коллекцию ресурсов для целей API или аналогичных целей.

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

  • Позволяет отложенное создание обложки
  • Позволяет создавать комиксы с необходимой обложкой
  • Позволяет обложкам быть перекрестными ссылками
  • Позволяет использовать несколько обложек
  • Создать черновик комикса
  • Создание эскизов обложек комиксов
  • Опубликовать черновик комикса

GET /comic-books
=> 200 ОК, получить все комиксы.

GET /comic-books/0
=> 200 OK, Получить комикс (id: 0) с обложками (/covers/1, /covers/2).

GET /comic-books/0/covers
=> 200 ОК, Получить обложки для комиксов (id: 0).

GET /covers
=> 200 ОК, получить все обложки.

GET /covers/1
=> 200 OK, Получить обложку (id: 1) с комиксом (/comic-books/0).

GET /comic-books/new
=> 200 OK, Получить форму для создания комикса (форма: POST /draft-comic-books).

POST /draft-comic-books
title=foo
author=boo
publisher=goo
Published=2011-01-01
=> 302 Найдено, расположение: /draft-comic-books/3, перенаправление на черновик комикса (id: 3) с обложками (бинарный).

GET /draft-comic-books/3
=> 200 ОК, Получить черновик комикса (id: 3) с обложками.

GET /draft-comic-books/3/covers
=> 200 ОК, Получить обложки для проекта комикса (/draft-comic-book/3).

GET /draft-comic-books/3/covers/new
=> 200 OK, Получить форму для создания обложки для проекта комикса (/draft-comic-book/3) (форма: POST /draft-comic- книги/3/обложки).

POST /draft-comic-books/3/covers
cover_type=front
cover_data=(binary)
=> 302 Found, Location: /draft-comic-books/3/covers, перенаправление на новую обложку чернового комикса (/draft-comic-book/3/covers/1).

GET /draft-comic-books/3/publish
=> 200 OK, Получить форму для публикации черновика комикса (id: 3) (форма: POST /published-comic-books).

POST /published-comic-books
title=foo
author=boo
publisher=goo
Published=2011-01-01
cover_type=front
cover_data=(binary)
=> 302 Найдено, Расположение: /comic-books/3, Перенаправление на опубликованный комикс (id: 3) с обложками.

person Alex    schedule 21.09.2011
comment
Я полный новичок в этом, и пытаюсь научиться этому в спешке. Я нашел это чрезвычайно полезным. Однако в других блогах и т. д., которые я читал сегодня, использование GET для выполнения операции (особенно операции, которая не является идемпотентной) будет неодобрительно. Так разве это не должно быть POST /draft-comic-books/3/publish ? - person Gary McGill; 03.08.2012
comment
@GaryMcGill В его примере /draft-comic-books/3/publish возвращает только HTML-форму (не изменяет никаких данных). - person Olivier Lalonde; 02.10.2012
comment
@ Оливье прав. Слово опубликовать используется для обозначения того, что делает форма. Однако, поскольку вы хотите, чтобы глаголы ограничивались HTTP-методами, вы должны размещать сообщения на ресурсе для опубликованных комиксов. ... Если бы это был веб-сайт, вам может понадобиться URI формы для публикации чего-либо. ... Хотя, если бы действие публикации было просто одной кнопкой на странице комикса, эта форма с одной кнопкой могла бы публиковаться непосредственно в URI /published-comic-books. - person Alex; 20.12.2012
comment
@Alex, в запросе POST я бы вместо этого вернул 201 Created с URL-адресом нового ресурса в качестве местоположения в заголовках ответа. - person ismriv; 17.12.2013
comment
Можно ли это сделать: posts/1/comments/2. Является ли 2 абсолютным идентификатором комментария, чтобы comments/2 извлекал точно такой же ресурс? Я предполагаю, что нет, потому что идентификаторы дочерних ресурсов должны быть однозначно связаны с родительским ресурсом. Поэтому, если ресурс доступен через posts/1/comments/2, это означает что-то уникальное для комментариев поста 1, он не должен быть доступен через comments/2, потому что posts/2/comments/2 одновременно потенциально возможен. Поэтому для случая использования комментариев я думаю, что только posts/X/comments является полезным URL-адресом. - person CMCDragonkai; 12.03.2014
comment
Обычно вы хотите всегда использовать уникальные идентификаторы. Таким образом, /posts/1/comments/2 (вложенный) совпадает с /comments/2 (невложенный). Обычно маршруты предпочитают не вложенный формат. То есть /posts/2/comments/2 недействителен, потому что идентификатор комментария 2 не соответствует идентификатору сообщения 2. Как вы сказали, дочерний список должен быть связан с родительским /posts/1/comments. - person Alex; 14.03.2014
comment
@Alex В POST /draft-comic-books/3/covers мне интересно, есть ли в вашем методе контроллера параметр комикса или параметр идентификатора комикса. Я делаю свое первое приложение REST, и я выбрал первое, когда объект (и не только его длинный идентификатор) передается методу контроллера при вставке дочернего объекта. Любой совет по дизайну очень приветствуется. - person Stephane; 31.08.2014
comment
@Stephane, обычно, поскольку обложки являются вложенными ресурсами, идентификатор комикса родительского ресурса 3 уже существует. Например, при создании нового черновика вы должны отправить следующие запросы. 1. POST /draft-comic-books (отправить новые данные комикса) для создания комикса. 2. Это перенаправляет на GET /draft-comic-books/3 для просмотра только что созданной книги. Затем POST /draft-comic-books/3/covers (отправить новые данные обложки), чтобы создать обложку. Это перенаправляет на GET /draft-comic-books/3/covers/1 для просмотра только что созданной обложки. - person Alex; 31.08.2014
comment
@Alex Я полагаю, что перенаправления выполняются на стороне сервера. Мои POST-контроллеры не делают такого перенаправления. Они просто возвращают созданный ресурс в ответ. Вы также делаете перенаправление для PUT? - person Stephane; 01.09.2014
comment
@Stephane, вы можете сделать перенаправление для PUT, если вы следуете шаблону PRG (post-redirect-get). PUT обычно предназначен для обновления ресурса, а не для его создания. - person Alex; 01.09.2014
comment
@Alex Вы бы сделали перенаправление в POST и PUT. Интересно, следует ли мне изменить несколько моих контроллеров и выполнить перенаправление вместо возврата созданного ресурса. Новичок сталкивается с двумя альтернативами, которые работают обе... - person Stephane; 01.09.2014
comment
@Stephane, перенаправления просто упрощают работу контроллеров. Даже для API проще, если контроллер create возвращает местоположение нового контента, а затем позволяет контроллеру show обрабатывать отображение нового контента. Хотя для клиента API приятнее/проще просто получать контент и не заморачиваться с редиректами. - person Alex; 04.09.2014

Отношение к обложкам как к ресурсам определенно соответствует духу REST, особенно HATEOAS. Так что да, запрос GET к http://example.com/comic-books/1 даст вам представление книги 1 со свойствами, включая набор URI для обложек. Все идет нормально.

Ваш вопрос заключается в том, как быть с созданием комиксов. Если бы ваше бизнес-правило заключалось в том, что у книги должно быть 0 или более обложек, у вас не было бы проблем:

POST http://example.com/comic-books

с данными комикса без обложки создаст новый комикс и вернет сгенерированный сервером идентификатор (скажем, он возвращается как 8), и теперь вы можете добавить к нему обложки следующим образом:

POST http://example.com/comic-books/8/covers

с крышкой в ​​теле сущности.

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

  1. Сначала принудительно создайте обложку, теперь, по сути, делая обложку независимым ресурсом, или вы помещаете начальную обложку в тело объекта POST, который создает комикс. Это, как вы говорите, означает, что представление, которое вы создаете POST, будет отличаться от представления, которое вы ПОЛУЧАЕТЕ.

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

Вы должны взвесить эти два варианта против простого разрешения комиксов без обложки.

Какой из трех вариантов выбрать? Не зная слишком много о вашей ситуации, но отвечая на общий вопрос о зависимых ресурсах 1..N, я бы сказал:

  • Если вы можете использовать 0..N для уровня службы RESTful, отлично. Возможно, слой между вашим RESTful SOA может справиться с дальнейшими ограничениями бизнеса, если требуется хотя бы один. (Не уверен, как это будет выглядеть, но, возможно, его стоит изучить... в любом случае конечные пользователи обычно не видят SOA.)

  • Если вы просто должны смоделировать ограничение 1..N, то спросите себя, могут ли обложки быть просто общими ресурсами, другими словами, могут ли они существовать на вещах, отличных от комиксов. Теперь они не являются зависимыми ресурсами, и вы можете сначала создать их и указать URI в своем POST, который создает комиксы.

  • Если вам нужно 1..N, а обложки остаются зависимыми, просто расслабьтесь, чтобы сохранить представления в POST и GET одинаковыми или сделать их одинаковыми.

Последний пункт объясняется так:

<comic-book>
  <name>...</name>
  <edition>...</edition>
  <cover-image>...BASE64...</cover-image>
  <cover-image>...BASE64...</cover-image>
  <cover>...URI...</cover>
  <cover>...URI...</cover>
</comic-book>

Когда вы POST, вы разрешаете существующие uris, если они у вас есть (заимствованные из других книг), но также добавляете одно или несколько начальных изображений. Если вы создаете книгу и у вашего объекта нет начального изображения обложки, верните 409 или аналогичный ответ. В GET вы можете вернуть URI.

Таким образом, в основном вы позволяете представлениям POST и GET «быть одинаковыми», но вы просто решаете не «использовать» обложку в GET и не обложку в POST. Надеюсь, это имеет смысл.

person Ray Toal    schedule 18.08.2011