作者 | Damon 爱数AnyShare引擎研发部-开发经理
本篇内容是对《RESTful Web Services》书中内容的精要摘录
目录
URI(Universal Resource Identifier)
两个无可争议的用途
不要丢失面向资源的设计
REST与Web
《RESTful Web Services》
Web基础技术
- HTTP应用协议、URL命名标准、XML标记语言。
Web背后的一套设计原则
- 表示性状态转移(Representational State Transfer),或简称为REST。
REST这个词的由来
- 客户端通过“在PUT或POST请求里附加一个表示(representation)”来对资源状态进行处理(DELETE请求也差不多,只是没有表示而已),而服务器通过“在响应客户端的GET请求时附上表示”来处理应用状态。
《Java RESTful Web Service实战》
REST
- REST是一种分布式应用的架构风格,也是一种大流量分布式应用的设计方法论。REST是由(构成了Web基础架构的)HTTP、URI等规范的主要设计者Roy Fileding博士在其2000年的博士论文(《架构风格与基于网络的软件架构设计》)中提出的。
REST的实现
- REST就是Web本身的架构风格,是设计、开发Web相关规范、Web应用、Web服务的指导原则。不符合REST风格要求的架构和技术,很难在Web这个生态系统中得到繁荣发展。
- HTTP本身就是一种REST风格的应用协议,以REST风格来使用HTTP,才是最高效的使用方式。
- HTTP+URI+XML是REST的基本实现形式,但不是唯一的实现形式。
《架构风格与基于网络的软件架构设计》
开发 REST 的动机
- 为 Web应该如何运转创建一种架构模型,使之成为 Web 协议标准的指导框架。
REST这个词的由来
- REST 的第一版开发于 1994 年 10 月和 1995 年 8 月之间,起初是作为当我编写 HTTP/1.0规范和最初的 HTTP/1.1 建议时,用来沟通各种 Web 概念的一种方法。它在随后的 5 年中以迭代的方式不断改进,并且被应用于各种 Web 协议标准的修订版和扩展之中。
- REST 最初被称作“HTTP 对象模型”,但是那个名称常常引起误解,使人们误以为它是一个 HTTP 服务器的实现模型。
- 这个名称“表述性状态转移”是有意唤起人们对于一个良好设计的 Web 应用如何运转的印象:一个由网页组成的网络(一个虚拟状态机),用户通过选择链接(状态转移)在应用中前进,导致下一个页面(代表应用的下一个状态)被转移给用户,并且呈现给他们,以便他们来使用。
面向资源的架构
如何实现REST式的API
极限面向对象
ROA的3个概念
资源(resource)
- 任何事物,只要具有被引用的必要,他就是一个资源(resource)。如果你的用户“想为它创建一个超文本链接,关于它作一些断言,获取或缓存它的表示,在其他表示里包含它的一部分,标注它,等等”,那么你应该将它作为一个资源。
- 通常,一个资源就是某个可以存放在计算机上并体现为比特流的事物,比如:一个文档、数据库里的一条记录,或者运行某算法的结果,等等。
URI(Universal Resource Identifier)
- 统一资源标识符(Universal Resource Identifier)
- URI既是资源的名称,也是资源的地址。如果一则信息没有URI,它就不能算是一个资源,也不能算真正在Web上,而只能算作描述另一个资源的一些数据。
表示(representations)
- 资源是表示(representations)的来源,表示只是关于资源当前状态的一些数据。因为大多数资源本身就是一些数据项(就像“待解决bug列表”一样),所以对一个资源来说,最容易想到的一个表示就是这些数据本身。服务器可以采用XML文档、网页或逗号分割文本(comma-separated text)格式来表达“待解决bug列表”。“2004年第4季度的销量”可以采用数字或图表的形式来表达。许多新闻网站在发布文章时,既提供富含广告的格式,也提供简单、便于打印的格式。它们是同一资源的不同表示。
ROA的4大特性
可寻址性
-
如果一个Web服务将其数据集里有价值部分作为资源(resource)发布出来,那么该应用就是可寻址的(addressable)。每个资源都有唯一的URI——其实,URI正是“统一资源标识符(Universal Resource Identifier)”的缩写。
统一接口
- 客户端与资源之间的所有交互,都是通过为数不多的几个HTTP方法进行的。任何资源都将暴露这些方法的一个子集,而且这些方法无论被哪个资源暴露,都具有相同的意义。
无状态性
-
在REST式服务里,我们区分两种状态:资源状态(即关于资源的信息)和应用状态(即关于客户端在应用中所处状态的信息)。资源状态(resource state)保存在服务端,而且只能以表示(representation)的形式发给客户端。应用状态(application state)保存在客户端;当它能够用于创建、修改或删除一个资源时,它将作为POST、PUT或DELETE请求的一部分发送给服务器,成为资源状态。
-
若一个REST式服务从不保存任何应用状态,那么就称它为“无状态的(stateless)”。在一个无状态的应用里,服务是按当前的资源状态来独立处理各个客户端请求的。假如客户端希望服务器在处理请求时参考某个应用状态,那么客户端必须把这个应用状态作为请求的一部分发给服务器。比方说,客户端为每个请求附上认证证书。
-
客户端通过“在PUT或POST请求里附加一个表示(representation)”来对资源状态进行处理(DELETE请求也差不多,只是没有表示而已),而服务器通过“在响应客户端的GET请求时附上表示”来处理应用状态——这正是“表示性状态转移(Representational State Transfer,REST)”这个词的由来。
连通性
-
服务器可以通过“在表示里给出链接与表单(links and forms)”来引导应用状态(application state)的变迁。因为这里的链接与表单起到“将资源互相连接起来”的作用,所以我称之为“连通性(connectedness)”。Roy Fielding的博士论文将此称为“将超媒体作为应用状态的引擎(hypermedia as the engine of application state)”。
-
若一个服务具有较好的连通性,那么它的客户端只要通过跟随链接和提交表单就能推进应用状态了。若一个服务在连通性方面做得不够好,那么它的客户端只有按事先规定的规则来构造它要访问的URI。现在的human web连通性都很好,因为你只要跟随链接就可以从一个网站的主页进入到该网站的大部分页面。不过,目前的programmable web在连通性方面还有待改善。
-
服务器还可以通过在表示里给出链接与表单来引导资源状态(resource state)的变迁。表单(forms)通过“告诉客户端服务器将接受什么样的表示”来引导客户端用PUT或POST请求来修改资源的状态。
统一接口
客户端与资源之间的所有交互,都是通过为数不多的几个基本HTTP方法进行的。
任何资源都将暴露这些方法的一个子集,而且这些方法无论被哪个资源暴露,都具有相同的意义。
具体参见《RESTful Web Services》第四、八章,统一接口部分。
GET、HEAD
-
GET请求用于获取关于资源的信息。这些信息将以报头(headers)和表示(representation)的形式返回给客户端。客户端在发送GET请求时不需提供表示(representation)。
-
HEAD请求跟GET请求差不多,只是它返回报头、不返回表示(representation)而已。
PUT、DELETE
-
PUT请求用于设定资源状态。客户端通常会在发送PUT请求时提供一个表示,服务器将根据此表示来创建或修改资源的状态。
-
DELETE请求用于删除资源。客户端在发送DELETE请求时无需提供表示。
POST(append)
-
POST请求的作用是为已有资源创建一个从属资源。这里的已有资源,可以是新建资源在数据结构意义上的父资源(就像树根与树叶的关系),也可以是一个专门用于生成其他资源的“工厂(factory)”资源。POST请求所携带的表示(representation)描述了该新资源的初始状态。
-
POST请求除了用于新建整个资源,还可用于往已有资源的状态里添加数据。
POST(process)
- 如果觉得需要为HTTP增添其他方法或特性,你可以重载POST(参见后面的“重载POST”一节),不过可能需要另外增加一种资源。假如想为HTTP增添事务性支持(transactional support),那么你很可能需要把事务(transactions)暴露为可被创建、更新和删除的资源。
安全性
-
GET请求和HEAD请求应当是安全的(safe):它们不应导致服务器状态发生任何改变。
-
服务器也许会出于自己的目的而改变状态(比如记录请求日志或增加访问计数器等),但这些改变不应归咎于客户端。
-
向一个URI做任意次GET请求,应当跟未做GET请求的效果(就服务器状态来说)是一样的。
幂等性
-
PUT或DELETE请求应当是幂等的(idempotent)。向一个URI发送多次PUT或DELETE请求,应该跟只做过一次请求的效果一样。应该避免用PUT请求对资源状态作相对的改动,比方说“把value增加5”。因为这样的话,发送10次PUT请求,将跟只做一次请求存在巨大的差别。PUT请求应该把资源状态设为特定的值。
-
GET和HEAD这两个安全的方法,天然具备幂等性。
-
用于创建资源的POST请求,既不是安全的,也不是幂等的。
-
重载的POST(overloaded POST)请求,它是否具备安全性,幂等性是不一定的。因为重载的POST可被用于实现任何操作,所以客户端无法判断。你可以通过POST Once Exactly令POST具备幂等性(参见第9章)。
新建资源:PUT vs. POST
你可以允许客户端通过PUT或/和POST来创建资源。
PUT
-
只有当客户端能够自己构造出新资源的URI时,才能使用PUT来创建资源。
-
比如:在Amazon S3里,一个S3桶(bucket)的URI路径为/{bucket-name};由于桶名(bucket name)是由客户端自己决定的,所以客户端可以通过“自己构造URI,然后向该URI发送PUT请求”来创建一个桶。
POST
-
对于Rails Web服务里的资源,其URI一般是这样的:/{database-table-name}/{database-ID}。客户端可以事先知道数据库表名,但无法事先知道新资源的ID——该ID要直到往数据库里保存记录时才能确定;所以,客户端必须通过向一个位于/{database-table-name}的“工厂”资源发送POST请求来创建资源,让服务器为新资源分配URI。
重载POST
除了创建新资源和往表示里增添数据,POST还可用于其他用途。你可以用POST把一个资源变成一个RPC式小型消息处理器。
资源在收到一个重载POST(overloaded POST)请求时,可以在收到的表示里寻找另外的方法信息(method information),并据此执行一定的操作。
通过重载的POST,该资源将拥有比统一接口更丰富的词汇。
两个无可争议的用途
-
用于为Web浏览器这种不支持PUT和DELETE的客户端模拟HTTP统一接口
-
用于绕过URI的最大长度限制。虽然HTTP标准没有限定URI的最大长度,但许多客户端和服务器都对此有一定限制:比如Apache就拒绝处理URI长度超过8KB的HTTP请求。若客户端由于URI长度限制的原因,无法向http://www.example.com/numbers/111111发出GET请求,那么它可以采取“向http://www.example.com/numbers?_method=GET发送POST请求,并把‘1111111’放在实体主体里”的办法来绕过此限制。
不要丢失面向资源的设计
-
假如统一接口真的不适合你的服务,或者不值得为之努力,那么你可以放心地采用重载的 POST (overloaded POST),不过不要因此丢失面向资源的设计。
-
你暴露的每个URI仍然应该是一个资源,即可能被客户端链接到的事物。
-
许多Web应用会给“通过重载POST暴露的操作(operation)”新分配个URI,比方说/weblog/myweblog/rebui1d-index;这种URI并不好,它不该把方法信息(rebuild-index)放在URI里。
-
应该为已有资源(/weblog/myweblog)暴露重载的POST,并把方法信息(method=rebuild-index)放在表示(representation)里。
-
这样,/webog/myweb1og就像资源的样子了,虽然它不完全符合统一接口。它可以响应GET、PUT、DELETE,还可以通过重载的POST响应"rebuild-index"这个“方法”。它仍是一个面向对象意义上的对象。
- 按照经验,加入一个URI只支持重载的POST,而不支持GET和POST方法的话,那么它就不是一个资源——它多半是一个RPC式服务。
ROA设计
步骤
根据问题创建REST式资源的通用步骤
- 规划数据集
- 把数据集划分为资源,对于其中每种资源:
- 用URI为该资源命名
- 暴露一个统一接口的子集
- 设计来自客户端的表示
- 设计发给客户端的表示
- 用超链接和表单把资源与已有资源联系起来
- 考虑有哪些典型的事件经过
- 考虑可能出现哪些错误情况
规划数据集
- “我有一个巨牛叉的idea,就差一个程序员了!”
- 对服务的初步设想:提供什么服务、包含哪些数据
- 比如:地图服务支持查找地图、搜索地点、导航等,包含各种地图数据(公路地图、行政地图等)
把数据集划分为资源
- “程序员有了,从划分资源开始!”
- 比如:地图服务,星球列表、星球上的一个点、星球上符合一定搜索条件的地点列表
用URI为该资源命名
- 即给资源设计URI
- 比如:
- 星球列表:http://maps.example.com/;
- 星球上的一个点:http://maps.example.com/radar/Earth/65.9,7.00;
- 星球上符合一定搜索条件的地点列表:http://maps.example.com/Earth?show=Springfield;
暴露一个统一接口的子集
- 用哪个HTTP方法更合适:GET、HEAD、PUT、POST、DELETE
设计来自客户端的表示
- JSON等
设计发给客户端的表示
- JSON等
用超链接和表单把资源与已有资源联系起来
- 考虑连通性
考虑有哪些典型的事件经过
- 考虑HTTP报头和HTTP响应代码
- 如:条件HTTP GET,两个响应报头(Last-Modified和ETag)和两个请求报头(IfModified-Since和If-None-Match);201响应代码+报头Location
考虑可能出现哪些错误情况
- 在哪些情况下,响应应该返回错误代码而不是表示
- 如:状态码:3xx、4xx、5xx,报头:Location
资源设计
资源分类
预定义的一次性(one-off)资源
- 比如服务的主页,或者指向其他资源的静态链接列表。这类资源的数量不多,它可能是面向对象系统里的一个类(class),或者面向数据库的系统里的一个数据库表(database table)。
大量(或无数个)对应于各数据项的资源
- 此类资源可能对应于面向对象系统里的一个对象(object),或面向数据库的系统里的一条数据库记录(database row)。
大量(或无数个)对应于一个可能的算法输出结果的资源
- 此类资源可能对应于一个面向数据库的系统里的查询结果。搜索结果列表和已过滤的资源列表均属此类资源。
资源之间的关系
如何体现两个人结婚这个过程?
设想我的服务里有Alice和Bob这两个资源,它们分别代表现实世界中的两个人。我的服务为这两个资源分配了GET并为它们提供了反映各自资源状态的表示(representation)。比方说有一天Alice和Bob结婚了,这在服务里应如何表示呢?
- 我们应该再提出一个用以表达婚姻关系的资源,用它来反映两个资源之间关系。
- 这样,客户端就可以通过向“婚姻关系”资源发送PUT请求或者通过向“结婚登记”资源发送POST请求来声明两人结婚的事实了。
- PUT或POST请求里的表示(representation)含有指向Alice和Bob的URIs,用以表明结婚的是这两个人。
- 服务器可以对结婚的条件施加限制:对于不符合条件的请求,返回错误消息;对于符合条件的请求,创建一个代表此婚姻关系的新资源。
- 这样,其他资源就可以链接到这个资源了,该资源跟其他资源一样也响应统一接口,客户端可以获取(GET)或删除(DELETE)该资源。
异步操作
HTTP是基于一种同步的(synchronous)请求-响应模型的。客户端打开服务器的一个lnternet套接字(socket),然后发送请求,直到服务器返回响应后再关闭套接字。若客户端不关心返回结果,可以提前关闭套接字。但如果客户端要获取响应的话,那就必须保持套接字直到服务器返回响应。
问题是,不是所有的操作都能在我们所期望的时间内完成。有些操作可能需要数小时甚至数天才能结束,如此一来,HTTP请求肯定会超时。即便不超时,这样一直把套接字维持在打开状态又有什么意义呢?就没有办法通过HTTP暴露异步操作了吗?
注意
- 《RESTful Web Services》中关于异步操作的描述,采用Location报头和202 Accepted状态码的实现方式,这很可能是错误的
- rfc7231中关于Location报头的使用描述只提及了201和3xx状态码,并未提及202状态码:https://tools.ietf.org/html/rfc7231#section-7.1.2
- mozilla的描述中明确指出Location报头只在状态码为201或3xx时才有意义:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location
201状态码实现方式
- 把操作划分为两次或更多次同步请求。第一次请求用以创建异步任务,其后的请求用以让客户端查询任务的当前状态。
- 请求:POST /queue HTTP/1.1\r\nHost: jobs.example.com
- 响应:201 Created\r\nLocation: http://jobs.example.com/queue/job11a4f9
- 服务器创建了一个新的“任务”资源,并为之单独分配了一个URI。
- 该异步操作现在正在处理中,客户端可以向该“任务”资源的URI发送GET请求,以查询该任务现在的处理进度——即获取该“任务”资源的当前状态。
- 当操作处理结束时,该资源的表示将提供运行结果。
- 客户端可以在得到运行结果以后,删除(DELETE)该“任务”资源。
- 客户端也可以在任务结束前,通过向该“任务”资源发送DELETE请求来取消该操作。
202状态码实现方式
- 向资源发起操作,在响应中包含请求的当前状态并提供一个监视器来告知用户完成该请求的预估时间
- 具体参考rfc7231:https://tools.ietf.org/html/rfc7231#section-6.3.3
批量操作
- 假如你的客户端要批量创建资源,你可以暴露这样一个工厂资源:它可以接受包含一组(而不是一个)资源表示(representation)的文档,并根据这些表示创建多个资源。
- 让客户端一次修改多个资源的一种方法是:把要修改的资源集合暴露为一个资源。例如,可以用http://www.example.corn/sets/resource1;subdir/resource2来指代下面这个包含两个资源的资源集合:一个位于http://www.example.com/resource1的资源和一个位于http://www.examp1e.com/subdir/resource2的资源。
- 向该“集合”资源发送DELETE请求,就可以一次删除集合里的所有资源。
- 同理,向该“集合”资源发送PUT请求、并附上各个资源的表示,就可以一次修改集合里的所有资源。
- 也许你要问,对批量操作的HTTP响应代码是什么样的?比如,在一次批量PUT操作中,有的操作成功了,有的操作失败了,这怎么办?返回响应代码200(“OK”),还是500(“Internal Server Error”)?
- 一种方案是:令批量操作生成一系列异步任务(asynchronous jobs),见上一节异步操作。另一种方案是:采用WebDAV的扩展HTTP响应代码:207(“Multi-Status”)。
- 返回响应代码207,意味着响应代码(如200,500等)列表在实体主体里。这个实体主体是一个XML文档,它告诉客户端哪些操作成功了、哪些操作失败了。这种方案并不理想,因为它把关于发生了什么状况的信息从响应代码(response code)里移到了响应实体主体(response entity-body)里。这跟重载的POST(overloaded POST)把方法信息(method information)从HTTP方法里移到请求实体主体(request entity-body)里差不多。不过,由于批量操作中的各操作可能具有不同的响应代码,所以你没有多少选择。
- 注意:207状态码不是标准状态码,可能存在浏览器兼容问题,不建议使用。建议用重载POST(参见上面的“重载POST”一节)。
事务
- 启动一个事务,获得事务ID
- 后续步骤均表明事务ID
- 提交事务ID,完成事务,具体参照AnyShare文件上传。
单文件上传
分片上传
URI设计
路径变量:父子关系
- 路径变量(path variables)被用于分隔一个层次结构或有向图的元素。
- 比如/weblogs/myweblog/entries/100就是从一般到特殊的路径:从博客列表到一个特定的博客,再到该博客的文章列表,再到一篇特定的文章。其中每个路径变量,在一定意义上都隶属干前一个路径变量。
- 举例:数据库表中的每条记录都从属于这张表,记录的每个字段都从属于这条记录,/table-name/record_id/field1
路径变量:并列关系
- 标点符号被用于在同一层次上分隔多项数据。
- 如果各项数据的次序有关紧要,就采用逗号,就像经纬度一样:/Earth/37.0,-95.2。
- 如果次序无关紧要,就采用分号,比如:/color-blends/red;blue。
- 以上为《RESTful Web Services》中的原文,实际操作中采用OAS 3.0标准,以逗号分隔:https://swagger.io/docs/specification/serialization/
- 举例:数据库表中的每条记录都从属于这张表,记录的每个字段都从属于这条记录,字段之间是并列关系,/table-name/record_id/field1,field2
查询变量
- 若路径变量与标点符号均解决不了问题,或者如果你要往一个算法里代入参数的话,那么可以采用查询变量(query variables)。
- 如果两个URIs只在查询变量上有差别,这表明它们只是为同一算法设置了不同的参数。
要点说明
- URI标识的是资源(resource),而不是对资源的操作(operation)。
- 所以说,把操作名称放在URI里是不合适的。
- 假如有一个像/obj ect/do-operation这样的URI,那么你有论为RPC式架构的危险。
- 没人希望链接到do-operation这样的URI,因为人们希望链接到一个对象(object)而不是操作(operation)。
- 操作(operation)应通过统一接口来暴露,或者实在不行的话,通过重载的POST来暴露(不过你要确保你的URI标识的是对象,而不是对象上的操作)。
误区
算法结果数据不是被处理数据的从属数据,例如:
- 用户组数据,URI设计:
- 所有用户组
- GET /groups
- 某一用户组
- GET /groups/{id}
- 某一用户组的特定信息:name,notes
- GET /groups/{id}/{fields}
- fields可以为name或者remark或者两者组合,用逗号分隔
- 搜索用户组算法的URI:
- 错误URI设计:
- GET /groups/search
- /groups表示所有用户组数据集,其从属数据必须为某一用户组/groups/{id},搜索用户组算法结果数据显然不是/groups的从属数据,仅仅是应用于用户组数据的算法
- 正确URI设计:
- GET /search-group
- URI表示算法结果数据
深入理解
可寻址性的重要性
- 可寻址性(addressability)意味着你的服务的每个重要方面都可以被外界所访问。
- 你的服务的每个重要方面都有个唯标识符——URI。
- 这些标识符可被收藏,可在应用之问传递,还可用于指代实际的资源。
- 可寻址性(addressability)令别人可以用你的服务构建 mashups,将它用于你从未想到过的场合。
- 我在第4章把URI比作电子表格里的单元格地址(cell addresses)和命令行解释器里的文件路径(file paths),Web跟电子表格和命令行解释器一样,具有强大的功能。
- 每则信息都有一个可以指代真实事物的、结构化的名称(name)。
无状态性的重要性
- 无状态性(statelessness)是最重要的一条简单化假设(simplifying assumption)。客户端的每个请求,都包含理解该请求所需的全部应用状态(application states)。这些信息不应保存在服务器上,也不应蕴含于之前的请求之中。每个请求,都是按照当前的资源状态单独处理的。
- 这样,提升应用的规模将十分简单。如果一个服务器无法处理所有请求,那么只要另设一台负载均衡服务器(load balancer),让它分担一半请求即可。哪一半?无所谓,因为每一 个请求都是自包含的(self-contained)。你可以采用简单的循环算法,也可以随机地把请求分配到不同的服务器上。如果两台服务器还不够的话,那就再添置一台服务器,依此类推。即便其中某台服务器宕机了,还有其他服务器可以处理请求。如果你的应用是无状态的,那么你不需要在各台服务器之间作协调工作,比如通过分享内存或制造”服务器亲缘性(server affinity)",以确保把一个“会话(session)”内的每一个请求都交给同一台服务器处理。你可以不断添置新的Web服务器,直到访问资源成为瓶颈。那时,你得采用数据库复制、镜像等措施来保存资源状态。
- 无状态的应用还具有更高的可靠性。假设客户端的一个请求超时了,无状态性意味着客户端只要把该请求重发一遍就行了,而不必担心“会话”进入无法恢复的错误状态。倘若那是一个POST请求,客户端恐怕要担心那个请求对资源状态作了什么操作,不过那是另一码事。应用状态时刻都在客户端的完全控制之下。
统一接口的重要性
- 要是你跟我说“我已经在 http://www.example.com/myresource 暴露了一个资源”,虽然这句话没有告诉我那个资源是什么,不过它让我知道我可以对该资源作些什么操作。我知道如何获取它的表示 (GET),也知道如何删除它(DELETE),还大致知道如何修改它的状态(PUT),以及如何创建它的从属资源(POST)。
- 虽然还有一些我所不知道的细节,比如:该资源实际支持上述操作中的哪些?(理论上,我可以通过发送OPTIONS请求获知一个资源支持哪些操作。但是目前很少有资源支特OPTIONS请求。 )该资源提供和接受什么格式的表示?该资源代表真实世界中的什么事物?不过,各个资源的工作方式基本一样,可以通过统一的客户端来访问它们。这是Web成功的主要原因。
- 统一接口施加的约束(GET和HEAD应当是安全的,PUT和DELETE应当是幂等的)增加了HTTP的可靠性。如果你的请求没有成功,只需重发一次即可,而不必担心产生副作用。POST请求是唯一的例外。
- 统一接口的强大之处,并不在于它所暴露的具体方法,而在于其统一性(uniformity)——大家采用同样的方法来处理所有事物。
连通性的重要性
- 设想网页(web pages)不是为你提供超链接(hyperlinks),而是通过文字描述告诉你如何构造其他网页的URIs——那是多么令人恼火的事。如今的大部分REST式Web服务便是这样:资源没有彼此连接起来。这令Web服务较面向人类的网站难以使用,而且这意味着programmable web失去Web的某些属性(如Google PageRank)。
- 一个服务应当是自描述的(self-describing),而不应依赖辅助的文字描述,来告诉程序员如何编写客户端。而且,依赖于URI构造规则的客户端是比较脆弱的。一旦服务器改变了规则,所有客户端都得重写。当资源之间的关系比较复杂且不稳定时,缺乏连通性还会造成一些潜在的问题。即便URI构造规则不变, 这些问题也可能导致客户端无法工作。
- 连通性令客户端可以处理会不断变化的关系。链接不仅隐藏了如何为给定资源构造URI的规则,还体现了资源如何相互关联的规则。
- URI的构造规则复杂、资源之间的关系不稳定,则连通性显得尤为重要!
参考资料
- 《RESTful Web Services》