ruby-on-rails - 在单个 REST 调用中执行多个数据库操作

标签 ruby-on-rails database rest transactions client-server

我正在尝试找出调用 REST 操作的最佳方法,这些操作通过一次调用执行多个操作和多个数据库更新。

在我的数据模型中,我有 Diners、LunchBoxes 和 Foods。 LunchBoxes 只是 Diners 和 Foods 之间的多对多关系,但具有一个计数属性,表示给定 Diner 有多少该类型的食物。

我想设置一个调用,表明用餐者已经吃了他们的一种食物,这相应地增加了用餐者的健康。某些食物比其他食物更有营养,因此可以不同程度地增加用餐者的健康。构成此的操作将是:

  • 将给定食物的 Diner's LunchBox 上的计数属性减少正确的数量
  • 相应地增加晚餐的健康

所以这里需要更新两个表:Diner 和 Lunchbox,它们都在一个事务中。

尝试使用名词,我能想到的最好办法是:

POST/diner/frank/meal

描述一顿饭的 XML 应该是这样的

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

然而,这让我觉得很做作。在 REST 中发布一顿饭应该创建一个 Meal 资源。在这种情况下,我们不仅没有创建 Meal 资源,而且还更新了另外两个资源:Diner 和 LunchBox。

我想一种方法是让客户端在两个单独的调用中处理此问题 - 一个更新 Diner,一个更新 LunchBox。然而,这似乎是错误的,因为我们有多个客户端(HTML、Flash 等)都需要执行此操作。如果我们将来更新用于消费食物的业务逻辑,那么我们将需要在许多客户端而不是单个服务器上进行更改。

其他人是如何解决这个公认的非常基本的问题的?

最佳答案

首先,晚餐和午餐盒的更新绝对应该在一个请求中完成。不要陷入尝试通过 REST API 进行交易的陷阱。

在我们回答您的具体问题之前,让我们先了解一下客户如何与您的服务互动,从而提出您的问题。

客户端应始终从根服务 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 提要的东西。 客户需要将 Frank 标识为用餐者,因此现在我们要访问他的午餐盒。我们的自定义媒体类型的规则说 Frank 的午餐盒的 url 可以在带有 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>

我们得到的是另一种自定义媒体类型,它定义了午餐盒的内容和描述我们可以使用该午餐盒做什么的链接。一旦客户选择了要吃的食物,我们就可以通过查找带有 rel="eat"的链接并跟随该 URL 来识别要跟随的 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。您可以选择最适合您所选 Web 框架的 URL 结构。 URL 结构属于服务器,您应该能够随时更改它,并且对客户端影响很小或没有影响。

如果您采用这种方法,您就可以不再为想出完美的网址而苦恼。这种人为的“RESTful URL”概念比 SOAP 更能阻止人们学习 REST!

关于ruby-on-rails - 在单个 REST 调用中执行多个数据库操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2165989/

相关文章:

ruby-on-rails - 让 rvm 为我创建的每个新 Rails 应用程序使用默认(如全局)gemset 的命令是什么?

ruby-on-rails - 无需测试的Ruby On Rails项目

php - Magento 2 REST API 调用以获取登录的客户 ID

web-services - 未找到 Java 类型 myPackage.B 类和 MIME 媒体类型 application/octet-stream 的消息正文编写器

ruby-on-rails - rspec be nil 和 be_nil 有什么区别

html - 如何适应屏幕全高(使用CSS)?

mysql - 使用 Django 在遗留数据库中处理主键字段

mysql - 数据库结构 - 具有 category_id 的单个设计表(多行)或多个设计类别表(几行)

sql - 使用 SQL 获取和删除查询字符串

java - jersey 和 Resteasy 位于一个 servlet 容器中