RESTful Architectural Principles ( REST架构原则)

Roy Fielding的博士论文对REST的阐述确实是对为什么万维网在过去18多年如此流行的解释。随着时间的流逝,程序员们开始意识到他们也可以使用REST的概念构建分布式服务以及对面向服务的体系结构进行建模(SOA)。

SOA旨在使应用开发者将系统设计为可重用的、松耦合的、分布式的服务集合。既然这些服务是发布在网络上的,从概念上来说,讲其组合成更复杂更大的系统时简单可行的。SOA的概念已经存在很长时间。过去开发者们已经使用诸如DCE、CORBA以及Java RMI等技术构建SOA。当下,当你想到SOA,你往往是指基于SOAP的web服务。

尽管REST和传统的构建SOA应用的方式有很多相似的地方,但从更重要的方面来看,他们是完全不同的。你可能认为拥有分布式计算技术背景对理解这种构建web service的新的方式是大有裨益的,但不幸的是,恰恰不是这么回事。原因是,REST的一些概念是很难消化吸收的,如果你已经能写出完善的SOAP或者CORBA的应用程序,这更难。如果你的职业生涯中有这些老技术背景,对你来说,你必须克服老的思维方式带来的内心的包袱。比如我,我花了很多月来阅读、研究、以及和REST技术的信仰者激辩。对你来说,可能比较容易的理解REST,其他一部分人压根不会丢开SOAP和WS-*来学习REST。

下面让我们来学习了解REST架构风格的每一个原则,从而弄明白为什么在构建web服务时,他们是非常重要的。

Addressability(可寻址的能力)

可寻址的能力是说在你的系统中每个事物和资源都可以通过统一标识符进行访问。你或许认为,这个道理傻瓜都明白,但如果你仔细想想,标注的事物标识在很多场景下是没有的。如果你开发实现过便于移植的J2EE应用,你可能知道我要表达的意思。在J2EE中,分布式的或者本地的对服务的标识不是标准化的,这让可移植很难做到。对某个应用来说,这不是什么大问题,但随着SOA的流行,我们正迈进一个分散的应用需要集成和交互的世界。没有一个标准的服务可寻址的能力将增加集成的难度和成本。

在REST的世界里,可寻址的能力通过使用URI获得。当你需要从服务端获取信息时,你在浏览器中输入一个URI。每个HTTP请求必须包含你要请求的对象的URI。URI的格式如下:

scheme://host:port/path?queryString#fragment

scheme是你要使用的协议。对Rest风格的Web服务来说,通常是http或者https。host表示DNS的名字或者IP地址,通常其后面跟着一个可选的端口,数值类型的。host和port标识你的资源在网络上的地址。跟在host和port后面的是路径表达式。路径表达式由以/分割的文本片段组成。可以把路径表达式认为是你计算机里面的文件目录。在路径表达式后面的是一个可选的?。?分割路径表达式和查询串。查询串是一组由name/value式的列表,对与对之间使用&进行分割。下面是一个例子:

http://example.com/customers?lastName=Burke&zipcode=02115

一个特定的参数名称可以在查询串中出现多次。在这种情况下,对同一个参数有多个值。

URI最后面的部分是片段(fragment),由#分割。片段往往拥有指示你要查询的文档的特定位置。

不是所有的字符都运行在URI串中出现。一些字符必须根据下面的准则进行编码。字符a-z,A-Z,0-9,.,-,*,以及_保持不变。空格符号转成+.其他一些字符必须首先使用特定的编码模式转成字节串,然后使用%作为其前缀。

使用唯一的URI来标识你的每一服务,好处是能让你的每个资源都能联系在一起。服务的引用可以内嵌在文档甚至邮件中。比如一个场景是,某人打你所在的公司的求助电话来寻求解决发生在你的面向服务的应用的一个问题。客户支持将包含相应链接的邮件发给开发者来修复这个问题。开发人员可以点击链接来重现该问题。更进一步来说,服务发布的数据也可以组合成更大的数据流。像下面:

<order id="111">
   <customer>http://customers.myintranet.com/customers/32133</customer>
   <order-entries>
     <order-entry>
        <quantity>5</quantity>
        <product>http://products.myintranet.com/products/111</product>
...

上面的例子中,XML片段描述了一个电子商务的订单信息。在里面,我们可以引用各个维度的信息。从这个引用中,我们不仅可以获得链接的客户的信息和客户所买的商品的信息,以及这个数据从哪个服务来的服务的标识。因此我们能准确地知道如何处理这个数据。

The Uniform, Constrained Interface(一致的接口约束)

一致的接口约束很有可能是有CORBA或者SOAP开发经验的开发者学习REST最难消化的。一致性接口的意思是你在部署你的分布式服务时,你将一直使用应用协议所支持的操作集合中的操作。这意味着,你不必在action参数中指定要调用的web服务的操作。HTTP有一个小的固定的操作方法集合。其中的每个方法有其特定的目的和含义。让我们认识一下他们
GET 
GET是个只读操作。它用于查询服务端的信息。它是幂等的和安全的操作。幂等的意思是不管你执行这个操作多少次,它的结果是一样的。读HTML文档的操作不会改变文档的内容。安全意味着,执行GET不会改变服务端的状态。
PUT
PUT请求往往请求服务端存储消息体的内容。常用于表示插入和更新操作。它也是幂等的。当使用PUT操作时,客户端知道要创建或者更新的资源的标识。它之所以是幂等操作,原因是无论相同信息的PUT操作执行多次,对应服务端来说,结果是一样的。就比如,你在编辑微软的word文档,无论你连续点击多少次保存操作,被保存的文件内容将保存不变。
DELETE
DELETE用于移除资源。它也是幂等的。
POST
POST是HTTP中唯一一个非幂等和非安全的操作。每个POST操作被允许使用唯一的方式来修改服务。你通常不能多次发送相同的POST请求。
HEAD
HEAD和GET是非常相似的,有个不一样的地方是,GET请求返还的消息有消息体,而HEAD只返回响应码和消息头。
OPTIONS
OPTIONS操作用于请求你所感兴趣的的资源的相关信息。它让客户端知道服务端的能力,客户端通过这个操作来获取相关信息而不触发获取资源的操作。
还有其他的HTTP操作,比如TRACE和CONNECT,但当实现REST风格的服务时它们是不重要的。
你现在可能会抓头想:“仅仅使用6个方法能写出分布式服务吗?”但想想看,SQL只有4个操作:SELECT,INSERT,UPDATE和DELETE。JMS和其他面向消息的中间件(MOM)实际上只有2个逻辑操作:发送和接收。
对SQL和JMS来说,复杂的交互操作完全遵循数据模型。可寻址能力和这些操作完全能胜任SQL的数据模型和JMS消息处理。

Why Is the Uniform Interface Important?(为什么一致的接口约束那么重要?)
约束服务接口对于web服务来说,好处大于坏处。让我们看下:

接口友好:如果你有个URI标识一个服务,你能准确的知道这个访问这个服务的方法。你不需要接口定义语言(IDL)来说明哪些方法是可用的。你不需要桩。你所需要的是一个HTTP客户端的类库。如果你有文档,文档有包含了访问很多不同服务的链接,当你得到相关的链接时,你就已经知道调用什么方法来访问这个服务。
互操作性:HTTP是广泛使用的协议。绝大多数编程语言都有HTTP客户端库。
可扩展性:因为REST定义了相关良好定义的方法集合,你可以预期有一个比较不错结果。GET操作是一个很好的例子。当你浏览因特网时,你有没有注意到你第二次访问同一个网页时,是不是更快了。这是因为你的浏览器缓存了相关的页面和图片。HTTP协议对缓存的语义有比较完善的定义。因为GET操作是个幂等的和安全的读操作,浏览器和HTTP代理可以缓存从服务端过来的响应,这会节省网络流量和响应时间。

Representation-Oriented(面向表示的)

REST的第三个架构原则是你的服务需要是面向表示的。每个服务都可通过特定的URI来进行寻址并访问。表示形式在客户端和服务端进行交互。通过GET操作,你将收到服务资源当前状态的表示。PUT或者POST操作将资源的表示传给服务端从而改变服务端的资源状态。
在REST的系统中,客户端和服务端的交互的复杂性存在于传来传去的表示。这些表示形式也许是XML,JSON,YAML,或者其他你能想到的表示形式。
对于HTTP,所谓的表示是请求和响应的消息体。消息体的格式可以是客户端和服务端期望的各种格式。HTTP使用Content-Type的头部来告诉客户端或者服务端它接收到的数据格式。Content-Type的值遵循MIME的格式。MIME的格式是非常简单的:
type/subtype;name=value;name=value...
type是主要的格式,subtype是子类别。可选的部分是name/value的键值对集合,以;分割。以下是例子:
text/plain
text/html
application/xml
text/html; charset=iso-8859-1
HTTP使用MIME获得了一个非常有意思的的特性,就是客户端和服务端直接可以协商消息格式。即便你的浏览器没有用到这个特性,但HTTP的内容协商是构建web服务的一个非常有用的工具。使用Accept的头部,客户端可以列出自己更想要的响应格式。Ajax的客户端想要JSON格式的消息,Java想要XML类型的消息,Ruby使用YAML的格式。另一个比较有用的特性是服务版本化,相同的服务使用相同的URI以及相同的方法,唯一需要变化的是,MIME类型数据。例如,MIME类型在老版本的服务中可以是application/xnd+xml,对新服务的服务的调用交换的MIME类型可以是application/vnd+xml;version=1.1。你可以从第八章得到详细的概念描述。
总之,REST和HTTP对可寻址性、方法选择、数据格式有一个很好的分层设计。你将获得一个更松耦合的协议从而让你的服务和各种各样的客户端之间的交互使用一致的方式。

Communicate Statelessly(无状态通信)

REST风格的第四个特性是无状态。这里说无状态,但不是说你的应用不能有状态。无状态意味这客户端的会话数据不保存在服务端。服务只会记录和管理它暴露的资源的状态。如果需要会话相关的数据,它通常由客户端来保持,当服务需要时由请求中携带。服务端不保持客户端的会话信息以便于扩展,在集群场景下,如果保存客户端的会话信息,有很多昂贵的复制成本。不保持服务端的信息,扩展是比较容易做到的,你所要做的是增加机器。
服务端不保存会话的机制,你可能认为是很难想象的,但如果回到12-15年之前,这也是合理可行的不难想象的。那时,大部分的分布式应用有一个胖的GUI客户端,使用Visual Basic,Power Builder或者Visual C++构造RPC作为数据库前端的中间层。服务端是无状态的的,只是简单地处理数据。胖客户端保存了所有的会话状态。胖客户端在升级、打补丁等方面通常是比较难的。但web应用解决了这个难题,应用可以从中心服务端传输给浏览器,由浏览器进行渲染操作。现在,随着Ajax、Flex和Java FX的流行,浏览器已经有足够的能力像90年代中叶胖客户端一样维持他们的会话状态。我们又回到了以前,无状态便于扩展的中间层盛行的时代。可笑的事情发生了。老古董的技术又被翻出来,你懂的,人类社会的发展其实是循环往复的,而现在老的技术思想又找到了生存的土壤,而且会成长的更好。

HATEOAS(使用超媒体作为应用状态自动机)

REST的最后一个原则是,使用超媒体作为应用的状态自动机。超媒体是以文档为中心同时内嵌了指向其他服务和信息链接。我已经在Addressablity(可寻址)的章节中讨论了使用内嵌在数据格式中的超链接来将服务联系在一起。
超媒体和超链接的一个用处是将分散的资源聚合在一起。超链接让我们引用或者聚合更多的信息且同时不让响应膨胀。在Addressablity(可寻址)的章节的例子
<order id="111">
   <customer>http://customers.myintranet.com/customers/32133</customer>
   <order-entries>
     <order-entry>
        <quantity>5</quantity>
        <product>http://products.myintranet.com/products/111</product>
...
在这个例子中,内嵌在文档中的链接使根据需要扩充更多表示的信息变得可行。聚合不是HATEOAS的全部概念,更让人感兴趣的是HATEOAS中的状态机的概念。
The engine of application state(应用状态机)
如果你在亚马逊网站上买过书,你会跟随一系列链接填写几个表单,然后买单。你通过点击每次响应回来包含的链接,来进行状态转换,来完成订单。服务端通过在给浏览器的响应中包含链接来引导客户进行选购。
这和传统的分布式应用的工作方式是不同的。老的应用通常会包含他们已知的服务列表,他们会通过一个中央的目录服务器来定位服务在网络上的位置。HATEOAS是不同的,因为从服务端返回的响应告诉你新的交互点在哪里。
例如,我们想知道web仓库中的产品列表,我们发起一个GET请求 http://example.com/webstore/products,接到下面的响应:
<products>
  <product id="123">
      <name>headphones</name>
      <price>$16.99</price>
   </product>
   <product id="124">
      <name>USB Cable</name>
     <price>$5.99</price>
   </product>
...
</products>
这里有个可能的问题是,我们可能会把数目成百上千的产品目录发给客户端,而客户端呢傻乎乎的一直在接收响应。我们可以只请求产品列表中的前5个。比如发下面的请求

<products>
   <link rel="next" href="http://example.com/webstore/products?startIndex=5"/>
   <product id="123">
      <name>headphones</name>
      <price>$16.99</price>
   </product>
...
</products>
客户端可以特别标明查询的产品目录的起始位置
<products>
   <link rel="previous" href="http://example.com/webstore/products?startIndex=1"/>
   <link rel="next" href="http://example.com/webstore/products?startIndex=5"/>
   <product id="128">
      <name>stuff</name>
      <price>$16.99</price>
   </product>
...
</products>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值