首先,餐厅和午餐盒的更新绝对应该在一个请求中完成。不要陷入尝试通过 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 识别为用餐者,因此现在我们要访问他的午餐盒。我们自定义媒体类型的规则说,弗兰克午餐盒的 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
我并没有认真考虑构建该网址的最佳方式是什么,因为如果我下周改变主意并实现它
<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!