RESTful Web Services Cookbook——2,识别资源

开发RESTful Web服务的首要步骤之一就是设计资源模型(resource model)。资源模型对所有客户端用来与服务器交互的资源加以识别和分类。

在设计RESTful Web服务的所有工作如资源的识别、媒体类型和格式的选择以及统一接口的应用中,资源的识别是最灵活的部分。

在前一章,虽然可以使用Resource Expert Droid等工具来验证服务器是否提供了正确的HTTP响应,但无法对资源进行同样的验证。因为资源模型无所谓正确与否。重要的是能否正确地使用HTTP的统一接口来实现Web服务。

一,如何从领域名词中识别资源

面向对象设计(object-oriented design)和数据库建模技术(database modeling techniques)都把领域实体(domain entities)作为设计的基础。可以使用同样的技术来识别资源。

(一)识别与操作资源

简单来说,就是分析案例,找到可以用“创建create” 、 “读取read” 、 “更新update” 或“删除delete” 动作来操作的领域名词(domain nouns)。 将每个领域名词都标识为资源。

使用POST, GET, PUTDELETE 来实现上面的操作。

(二)示例:一个管理照片的Web服务

设想一个管理照片的Web服务。客户端可以上传新照片、替换现有的照片、查看或删除照片。

在这个例子中, “照片” 是一个应用领域中的实体。客户端可以对这个
实体执行的动作包括“创建一张新照片” 、 “替换现有的照片” 、 “查看照片” 和“删除照片” 。

将“照片” 识别为资源,这样客户端就可以像下面这样使用HTTP的统一接口来操作这些照片:

  • GET方法获得每张照片的表述。
  • PUT方法更新照片。
  • DELETE方法删除照片。
  • POST方法创建一张新照片。

这里容易给人留下一个印象,即REST只适合CRUD风格(创建、 读取、 更新、 删除) 的应用,但实际上并不是这样

(三)真实的资源

造成上面困惑原因,是这里操作的资源是非常简单的独立个体,所以如果仅限于通过领域名词来识别资源,可能会发现那些一成不变的HTTP方法存在局限性。

但在大多数应用程序中,CRUD操作只是REST接口(interface)的一部分,比如:

  • 寻找从西雅图到旧金山的交通指示。
  • 生成随机数,或把英里转换为千米。
  • 为客户端提供一个方法:在一个请求中,获取最小属性集的用户档案,列出最近10张用户上传的照片以及用户感兴趣的10条新闻。
  • 批准购买软件的申请。
  • 将钱从一个银行账户转到另一个银行账户。
  • 合并两个地址簿。

所有这些用例中,可以很容易地找到领域名词。但在每种情况下,如果将这些名词识别为资源,就会发现相应的动作没有办法映射到GET, POST, PUT和DELETE这样的HTTP方法上。

所以需要额外的资源来处理这些用例。下面会讲相关内容。

二,如何选择资源粒度

(一)为什么需要选择资源粒度

通过上面的例子发现,直接将领域实体映射为资源可能导致资源效率低下且难以使用,所以需要确定合适的资源粒度(granularity of resources)来实现对资源更高效的操作。

(二)如何选择资源粒度

查看你的应用程序的场景,可能会发现几个不同粒度的名词。

以一个社交网络为例,其中交互发生在“用户”的上下文(context)中。 每个用户的数据可能包括活动流、朋友列表、关注者列表、分享链接等。
在这样的应用程序中,是否应该将每个用户建模为粗粒度资源(coarse-grained resource)来封装所有这些数据? 或者应该让资源不那么粗粒度,并提供活动流、朋友、关注者等作为资源?
答案取决于你的 Web 服务的典型客户端是做什么的。
使用前一种方法,用户信息对于客户端来说可能太大而无法处理,而后者可能更灵活。如果大多数客户端将用户的数据下载到用户的计算机上存储它,然后使用一些富客户端呈现它,那么提供包含其所有数据的用户资源是有意义的。

再举一个更简单的例子,一个带地址的用户。
需要使用代理HTTP缓存在内存中保存所有用户的表述,这样客户端可以快速访问这些内容。但是包含有地址的用户资源的表述可能太大了,不适合放入缓存。
所以,将每个用户的地址作为单独的资源更有意义,即使因为减小了粒度使得客户端与服务器的交互更加频繁。

同样,将应用程序中的数据库表或对象模型映射到资源可能不会产生最佳结果。
例如域建模、允许有效的数据访问和处理等许多因素都会影响数据库表和对象模型的设计。
另一方面,因为 HTTP 客户端使用 HTTP 的统一接口通过网络访问资源,因此需要设计资源以适应客户端的使用模式,而不是根据数据库或对象模型中已存在的内容进行设计。

(三)确定合适资源粒度的标准

如何选择资源粒度,最好的方式是从客户端的角度思考问题。

之前的第一个例子表明,粗粒度设计便于富客户端应用程序。而在第二个例子中,更细的资源粒度可以更好地满足缓存的要求。

还需要通过网络效率(network efficiency)、表述的多少(size of representations)以及客户端的易用性(convenience)来指导确定资源的粒度。

因为粒度是个相对概念,需要结合具体环境进行理解,所以不存在确定合适资源粒度的标准。
但是,仔细设计资源粒度,确保将更能缓存、更少修改或不可变数据从更不能缓存、更多修改或可变数据中分离出来,就可以改善客户端和服务器端的效率。

三,如何将资源组织为集合

(一)为什么要将资源组织为集合

把资源组织成集合使得客户端和服务器能够将一组有共性(share some commonality)的资源视为一个资源集合来执行查询,甚至将集合用作创建新资源的模式工厂,目的都是为了方便操作资源。

(二)如何将资源组织为集合

基于特定应用程序的标准来识别相似的资源。
比如,共享同一数据库schema的资源、有相同特性(attribute)或属性(property)的资源或客户端看起来就是相似的资源。

然后为每组集合设计一个表述, 这样它便可以包含集合中所有成员或某些成员的信息了。

(三)示例:社交网络数据集合

一旦将多个相似的资源归集到一个集合资源下, 便可以像一个整体那样来进行引用,比如,可以提交一个GET请求来获取整个集合而不是一个个地获取单个资源。

设想一个社交网络,所有的用户记录共享同一个数据库Schema,网络中的每个用户都有一个好友列表和关注者列表。 好友和关注者是同一数据库中的其他用户。用户是基于个人兴趣进行分类的,例如跑步等。在这个例子里,可以识别出以下集合,它们的成员都是用户资源:

  • 用户资源集合
  • 任意给定用户的好友集合。
  • 给定用户的关注者集合。
  • 有相同兴趣的用户集合。

以下是一个针对用户集合的GET请求和响应的示例:
在这里插入图片描述

  • 表示用户集合。
  • 表示集合中的一个用户资源。

以下是一个针对用户好友集合的GET请求和响应的示例:
在这里插入图片描述
获得集合之后,就可以像下面这样来使用集合资源:

  • 取集合的分页视图,例如浏览一个用户的好友集合,一次10条。
  • 索集合成员,或者获取集合的一个过滤视图,例如查询游泳者的好友。
  • 将集合作为一个工厂,通过向集合资源提交HTTP POST请求来创建新成员资源。
  • 一次向多个资源执行相同操作。

四,何时将资源合并为复合资源

(一)什么是复合资源

上面提到的资源更多是面向开发者而言的,用户在访问像https://www.yahoo.comhttps://www.msn.com这样的网站主页时, 会注意到这些页面是多个信息源(例如新闻、电子邮件、天气预报、娱乐信息、财经信息等)的聚合信息(aggregate information )。

如果根据资源粒度,将单个信息源视为一个资源,为每个主页提供服务就是将这些不同的资源合并为一个单独资源的结果,这个资源的表述形式是一个HTML页面。这样的Web页面就是复合资源(composite resource),也就是说,它们可以从其他资源合并信息。

(二)何时将资源合并为复合资源

因为复合资源会从其他资源那里组合信息,所以能减少对该复合资源下的资源进行单独操作时发起的请求操作数量。
尽管一系列请求也能被服务器所接受,但不希望发起请求的操作太过频繁。对客户端而言,如果能只发送一条单一的网络请求来获取呈现页面所需的所有数据,可能会更高效一些。

(三)示例:客户快照

设想在企业应用程序中需要为每个客户提供一个快照页,该页将显示客户信息,例如姓名、联系方式、该客户最近的购买订单汇总以及所有待决定的报价请求。可以识别出以下资源:

  • 带有姓名、联系方式和其他详细信息的客户资源。
  • 每个客户的订单集合。
  • 每个客户的待报价集合。

可以使用三个GET请求来获取这三种资源,用其响应来构建这个客户的快照页:
在这里插入图片描述
但这里我们希望减少请求数量,就需要设计一个复合资源,其中就包括我们需要的所有信息,然后通过一个URI来获取:
在这里插入图片描述

(四)复合资源降低了统一接口的可见性

复合资源的表述中包含了和其他资源相重叠的数据。因此,在提供复合资源前请考虑以下几点:

  • 如果在应用程序中对复合资源的请求很少, 那么提供复合资源可能不是一个好的选择。依赖缓存代理,从缓存中获取这些资源, 也许能让客户端受益匪浅。
  • 如果客户端与服务器之间的网络开销或者服务器和它所依赖的数据存储之间的网络开销很大,那获取大量数据并在服务器上将它们组合成复合资源可能会增加客户端的延时,降低服务器的吞吐量。所以可以在客户端和服务器之间增加一个缓存层,并避免复合资源。在此之前进行一些负载测试(load tests)来验证复合资源是否能起到改善作用。
  • 为每个客户端创建特定目标的复合资源并非是注重实效的做法。选择满足对Web服务最重要的客户端就够了。

五,如何支持处理函数

(一)什么是处理函数

处理函数并不少见,像XE.com等绝大多数网站,都会接受一些输入,然后通过存储在后端服务器上的数据和一些算法来处理这些输入,最后返回结果。完成这个过程的就是处理函数(Processing Functions)。

(二)如何为处理函数提供资源抽象

在前面讨论过如何识别资源以及控制资源粒度等问题,都有一个特点:REST只适用于应用程序领域中的“事物” 或“实体” 资源, 这也是对REST架构约束最常见的理解之一。
尽管在多数情况下这个观点是对的,但在涉及处理函数的场合中似乎并非如此。

就像“真实的资源”中提到的一些例子,尽管使用前面识别资源的概念,会找到一些名词,却无法方便地对其应用统一接口。例如,如果将两个“地点” 标识为资源,却找不到与“寻找交通指示” 等效的HTTP操作。

一种解决方法就是将处理函数也视为一个资源,使用 GET来获取包含处理函数输出的表述,使用查询参数来为处理函数提供输入。

(三)示例:将处理函数视为一个资源

看几个例子:

  • 两地距离:客户端向服务器提交了两个地点的经度和纬度,服务器计算两地距离并将其返回给客户端。
  • 行驶方向:客户端在表单中自由提交两个地点,比如"Seattle, WA”和ttSan Francisco, CA”,服务器返回回一个列表,其中包含行驶路段和转弯方向。
  • 验证信用卡:客户端向服务器提交信用卡信息,例如持卡者姓名、卡号和有效期,服务器返回该卡是否为有效信用卡。

将处理函数本身视为资源,将处理结果作为资源的表述:```
在这里插入图片描述

  • 将距离计算器视为一个资源, 距离是它的表述。

在这里插入图片描述

  • 将方向定位器视为一个资源, 方向是它的表述。

在这里插入图片描述

  • 将兴趣点定位器视为一个资源,兴趣点是它的表述。

在这里插入图片描述

  • 将信用卡验证器视为一个资源,验证结果是它的表述。

六,何时及如何使用控制器来操作资源

(一)什么是控制器

控制器是一种能以原子方式修改资源的资源。

从领域模型中也许看不到对这种资源的需求,就RESTful Web服务而言,控制器(controller)能帮助服务器抽象复杂的业务操作,并为客户端提供触发这些操作的途径,改善网络效率,并让服务器以原子操作的形式来实现复杂操作,这能降低客户端与服务器的耦合性。

(二)如何编写以原子方式修改多个资源的操作

尽管在HTTP中,一般建议使用 PUT 来修改资源,但可以为每个独立的操作指定一个控制器资源。用控制器 (controller) 带 POST 方法来修改多个资源。

客户端使用POST方法提交请求来触发操作:

  • 如果操作的结果是创建一个新资源,则返回响应状态码201 (Created),并附加一个带URI的Location头。
  • 如果操作的结果是修改一个或多个现有资源,,返回响应码303 (See Other),并附加一个带URI的Location头。
  • 如果服务器无法提供单个URI指向所有被修改的资源, 则返回响应码200 (0K),并在正文中附上一个表述,客户端能用它来了解操作结果。

(三)一些示例

1,合并地址簿

假设要为用户合并两个地址簿,移动电话上的客户端需要与服务器的地址簿同步所有联系人。

一•种方法是像下面这样使用PUT请求:

  1. 向地址簿资源提交一个GET请求, 从服务器下载完整的地址簿。
  2. 加载本地联系人列表, 与服务器下载的地址簿合并。
  3. 向地址簿资源提交一个PUT请求, 将整个地址簿替换为合并后的新地址簿。

这样虽然能够完成任务,但可能会有如下缺点:

  • 多次发送请求操作,这使得客户端无法有效利用网络。
  • 客户端可能没有足够的计算能力来处理大量数据的合并操作。
  • 服务器上的地址簿数据可能不是所有字段都与客户端有关。
  • 最重要的是合并地址簿内容的应用逻辑应该属于服务器, 而非客户端。

下面是另一种做法:

  1. 客户端从服务器获取地址簿中的每个地址。
  2. 如果该地址能与本地存储中的某个地址吻合,则进行合并,通过提交PUT请求更新它。
  3. 如果在本地存储中有服务器上不存在的新联系人, 向地址簿提交一个POST请求进行添加。

但可能会有如下缺点:

  • 多次发送请求操作,这使得客户端无法有效利用网络。
  • 客户端可能没有足够的计算能力来处理大量数据的合并操作。

更有效的解决方案是使用控制器资源。
针对这个例子,设计一个控制器资源,允许客户端向服务器提交用于合并的地址簿:
在这里插入图片描述
合并后, 服务器将客户端重定向到用户更新后的地址簿。如果需要, 客户端可以获取一份副本。

2,调整书籍价格

一个书店里,店员想要将一本书的税前价格下降15%,并修改税后价格来反映这个折扣。

服务器可以将折扣百分比作为资源, 客户端提交PUT请求来修改当前折扣。在同一请求中, 服务器还可以修改图书总价:

在这里插入图片描述
假设客户端想要提供该书的30天免费在线版本并带有15%折扣。服务器上也维护了一个集合,其中包含所有正在提供30天免费访问的图书,客户端可以提交POST请求将该书添加到集合中:
在这里插入图片描述

如果业务上要求以原子方式完成这两个修改, 那么可以使用控制器资源:
在这里插入图片描述

从这些例子中可以发现一个关键点, 即可能会在将应用程序中的操作映射为统一接口方法时遇到困难。 例如, 在打折的例子中, 服务器将当前折扣值作为资源,这样客户端能使用PUT请求来更新它。 类似的, 服务器将30天免费电子书优惠标识为一个集合, 让客户端使用POST请求向集合中添加新书。 但要把两个任务合并成单个请求时, 并不能明确地将之映射到任意一个HTTP方法上。 控制器在这些情况下是最合适的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值