我正在尝试找出调用 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/