Выполнение нескольких операций с базой данных за один вызов REST - PullRequest
3 голосов
/ 30 января 2010

Я пытаюсь найти лучший способ вызвать действия REST, которые выполняют несколько действий и несколько обновлений базы данных от одного вызова.

В моей модели данных есть Diners, LunchBoxes и Foods. LunchBoxes - это просто отношения «многие ко многим» между Diners и Foods, но с атрибутом count, который говорит, сколько из этого типа пищи имеет данный Diner.

Я хочу установить звонок, который показывает, что Закусочная съела одну из своих Пищ, что соответственно увеличивает здоровье Закусочной. Некоторые продукты питания более питательны, чем другие, и, следовательно, увеличивают здоровье Дайнера в разной степени. Действия, которые составляют это, будут:

  • Уменьшите количество атрибутов на LunchBox закусочной для данного блюда на правильное количество
  • Соответственно увеличить здоровье Дайнера

Таким образом, здесь необходимо обновить две таблицы: Diner и Lunchbox, обе в рамках одной транзакции.

Попытка использовать существительные, лучшее, что я мог придумать, было:

POST / закусочная / откровенная / еда

Где XML, описывающий еду, будет что-то вроде

<meal>
  <food>
    <id>apple</id>
  </food>
  <count>2</count>
</meal>

Однако, это кажется мне довольно надуманным. Размещение еды в REST должно создать ресурс еды. В этом случае мы не только не создаем ресурс Meal, но и обновляем два других ресурса: Diner и LunchBox.

Полагаю, один из подходов состоит в том, чтобы клиент обрабатывал это в двух отдельных вызовах - один для обновления Diner, а другой для обновления LunchBox. Однако это кажется неправильным, потому что у нас есть несколько клиентов (HTML, Flash и т. Д.), Которым всем необходимо выполнить это действие. Если в будущем мы когда-нибудь обновим бизнес-логику, которая используется для потребления продуктов питания, нам потребуется внести это изменение на многих клиентах, а не на одном сервере.

Как другие подошли к этой, по общему признанию, довольно основной проблеме?

Ответы [ 2 ]

1 голос
/ 30 января 2010

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

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

Клиент всегда должен запускаться с URL-адреса корневого сервиса.

GET /DiningService
Content-Type: application/vnd.sample.diningservice+xml
200 OK

<DiningService>
 <Link rel="diners" href="./diners"/>
 <Link rel="lunchboxes" href="./lunchboxes"/>
 <Link rel="foods" href="./foods"/>
</DiningService>

Я не знаю, как ваши пользователи будут взаимодействовать с клиентским программным обеспечением, но давайте предположим, что сначала нам нужно определить, кто будет есть. Мы можем получить список посетителей из поиска в ответе ссылки с rel = "diners" и перейти по этой ссылке.

GET /DiningService/diners
Content-Type: application/vnd.sample.diners+xml
200 OK

<Diners>
 <Diner Name="Frank">
  <Link rel="lunchbox" href="./Frank/lunchbox"/>
 </Diner>
 <Diner Name="Bob">
  <Link rel="lunchbox" href="./Bob/lunchbox"/>
 </Diner>
</Diners>

Возвращается список посетителей. Я решил создать пользовательские типы носителей для простоты, но вам может быть лучше использовать что-то вроде каналов Atom для этих списков. Клиент должен идентифицировать Фрэнка как посетителя, и теперь мы хотим получить доступ к его ланч-боксу. Правила нашего нестандартного типа мультимедиа гласят, что ссылка на ланч-бокс Фрэнка можно найти в элементе ссылки с rel = "lunchbox". Мы получаем этот URL из ответного документа и следуем за ним.

GET /DiningService/Frank/lunchbox
Content-Type: application/vnd.sample.lunchbox+xml
200 OK

<Lunchbox>
 <Link rel="diner" href="/DiningService/Frank"/>
 <Food Name="CheeseSandwich" NutritionPoints="10">
          <Link rel="eat" Method="POST" href="/DiningService/Frank?food=/DiningService/Food/CheeseSandwich"/>
 </Food>
 <Food Name="CucumberSandwich" NutritionPoints="15">
  <Link rel="eat" Method="POST" href="/DiningService/Frank?food=/DiningService/Food/CucumberSandwich"/>
 </Food>
</Lunchbox>

Что мы получаем, так это еще один пользовательский тип мультимедиа, определяющий содержимое ланч-бокса и ссылки, описывающие, что мы можем сделать с этим ланч-боксом. Как только клиент выберет еду для еды, мы можем определить URL-адрес, по которому нужно перейти, найдя ссылку с rel = "eat" и следуя этому URL. В данном случае это пост.

POST /DiningService/Frank?food=/DiningService/Food/CucumberSandwich
Content-Type: None
200 OK

Я не особо задумывался о том, как лучше всего структурировать этот URL, потому что, если я передумаю на следующей неделе и сделаю это

<Link rel="eat" Method="POST" href="/DiningService/Frank/Mouth?food=/DiningService/Food?id=759"/>

или даже

<Link rel="eat" Method="POST" href="/DiningService/Food/CheeseSandwich?eatenBy=Frank"/>

это действительно не имеет значения для клиента, потому что он продолжит искать ссылку с rel = "eat" и будет следовать URL. Вы выбираете любую структуру URL, наиболее удобную для выбранной вами веб-структуры. Структура URL принадлежит серверу, и вы должны иметь возможность изменять ее всякий раз, когда это мало или совсем не влияет на клиента.

Если вы воспользуетесь этим подходом, вы можете перестать беспокоиться о том, чтобы найти идеальный URL. Это искусственное понятие «URL-адрес RESTful» сделало больше для предотвращения изучения REST людьми, чем когда-либо SOAP!

1 голос
/ 30 января 2010

Если вы посмотрите на food как действие на Diner , а не на сам ресурс, это будет иметь больше смысла. Тем не менее, я хотел бы изменить имя на глагол, например, eat . При моделировании системы REST некоторые решения, которые вы принимаете, будут произвольными. С теоретической точки зрения это действие может выполняться как на Diner , так и на LunchBox . Я склонен моделировать в соответствии с тем, как используется мое приложение, и что подходит для пользовательского интерфейса и что легче объяснить третьей стороне в документации и т. Д.

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

Действие будет действовать в закусочной, принимая список продуктов и их количество.

В рельсах теперь будет что-то вроде

# routes.rb
map.resources :diner, :member => {:eat => :post}

#controller
def eat
  @diner = Diner.find(params[:id])
  @diner.eat(params[:foods])
  respond_to ...
  end
end

Вы заметите, что я фактически ввел логику в модель. Я предполагаю, что модель Diner имеет связь с моделью LunchBox. Метод eat увеличит здоровье и изменит количество пищи в соответствующем LunchBox. Таким образом, вы можете аккуратно инкапсулировать всю логику.

UPDATE Я думаю, что это довольно распространенная модель - иметь ресурсы с некоторыми конкретными именованными операциями. Я часто просто добавляю действия к своему контроллеру, но придерживаюсь общей структуры REST, выставляя эти действия с использованием HTTP и соглашений Rails.

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

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

...