转自:http://hi.baidu.com/zdz8207/blog/item/a0b34c4af3a8e72109f7efc5.html
这阵子正打算用Rails做个东东,所以开始系统地学习起了Rails。巧合的是,大概两周前,dlee邀请我加入Fielding博士关于 REST的那篇论文的翻译团队。可以说Rails和REST这两个最热门的词汇几乎同时挤入了我的生活。随着我对Rails的学习和对 [Fielding]的翻译,我也开始对REST产生了一些不太成熟的想法,写在这里与大家分享,同时也起到抛砖引玉的作用,欢迎大家讨论。
先复习一下REST的基本思想。[Fielding]把REST形式化地定义为一种架构风格(architecture style),它有架构元素(element)和架构约束(constraint)组成。这些概念比较晦涩难懂,而且我们做工程的往往并不需要形而上的理 解。我们只知道,REST是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。REST提出了一些设计概念和准则:
1. 网络上的所有事物都被抽象为资源(resource);
2. 每个资源对应一个唯一的资源标识(resource identifier);
3. 通过通用的连接器接口(generic connector interface)对资源进行操作;
4. 对资源的各种操作不会改变资源标识;
5. 所有的操作都是无状态的(stateless)。
对于当今最常见的网络应用来说,resource identifier是url,generic connector interface是HTTP,第4条准则就是我们常说的url不变性。这些概念中的resouce最容易使人产生误解。resouce所指的并不是数 据,而是数据+特定的表现形式(representation),这也是为什么REST的全名是Representational State Transfer的原因。举个例子来说,“本月卖得最好的10本书”和“你最喜欢的10本书”在数据上可能有重叠(有一本书即卖得好,你又喜欢),甚至完 全相同。但是它们的representation不同,因此是不同的resource。
REST之所以能够简化开发,是因为其引入的架构约束,比如Rails 1.2中对REST的实现默认把controller中的方法限制在7个:index、show、new、edit、create、update和 destory,这实际上就是对CURD的实现。更进一步讲,Rails(也是当今大部分网络应用)使用HTTP作为generic connector interface,HTTP则把对一个url的操作限制在了4个之内:GET、POST、PUT和DELETE。
REST之所以能够提高系统的可伸缩性,是因为它强制所有操作都是stateless的,这样就没有context的约束,如果要做分布式、做集 群,就不需要考虑context的问题了。同时,它令系统可以有效地使用pool。REST对性能的另一个提升来自其对client和server任务的 分配:server只负责提供resource以及操作resource的服务,而client要根据resource中的data和 representation自己做render。这就减少了服务器的开销。
既然REST有这样的好处,那我们应该义无反顾地拥抱它啊!目前一些大牛(像DHH)都已经开始投入到了REST的世界,那我们这些人应该做什么或 者说思考写什么你呢?我觉得我们应该思考两个问题:
1. 如何使用REST;
2. REST和MVC的关系。
第一个问题假设REST是我们应该采用的架构,然后讨论如何使用;第二个问题则要说明REST和当前最普遍应用的MVC是什么关系,互补还是取代?
我们先来谈谈第一个问题,如何使用REST。我感觉,REST除了给我们带来了一个崭新的架构以外,还有一个重要的贡献是在开发系统过程中的一种新 的思维方式:通过url来设计系统的结构。根据REST,每个url都代表一个resource,而整个系统就是由这些resource组成的。因此,如 果url是设计良好的,那么系统的结构就也应该是设计良好的。对于非高手级的开发人员来说,考虑一个系统如何架构总是一个很抽象的问题。敏捷开发所提倡的 Test Driven Development,其好处之一(我觉得是最大的好处)就是可以通过testcase直观地设计系统的接口。比如在还没有创建一个class的时候就 编写一个testcase,虽然设置不能通过编译,但是testcase中的方法调用可以很好地从class使用者的角度反映出需要的接口,从而为 class的设计提供了直观的表现。这与在REST架构中通过url设计系统结构非常类似。虽然我们连一个功能都没有实现,但是我们可以先设计出我们认为 合理的url,这些url甚至不能连接到任何page或action,但是它们直观地告诉我们:系统对用户的访问接口就应该是这样。根据这些url,我们 可以很方便地设计系统的结构。
让我在这里重申一遍:REST允许我们通过url设计系统,就像Test Driven Development允许我们使用testcase设计class接口一样。
OK,既然url有这样的好处,那我们就着重讨论一下如何设计url。网络应用通常都是有hierarchy的,像棵大树。我们通常希望url也能 反映出资源的层次性。比如对于一个blog应用:/articles表示所有的文章,/articles/1表示id为1的文章,这都比较直观。遗憾的 是,网络应用的资源结构永远不会如此简单。因此人们常常会问这样一个问题:RESTful的url能覆盖所有的用户请求吗?比如,login如何 RESTful?search如何RESTful?
从REST的概念上来看,所有可以被抽象为资源的东东都可以使用RESTful的url。因此对于上面的两个问题,如果login和search可 以被抽象为资源,那么就可以使用RESTful的url。search比较简单,因为它会返回搜索结果,因此可以被抽象为资源,并且只实现index方法 就可以了(只需要显示搜索结果,没有create、destory之类的东西)。然而这里面也有一个问题:search的关键字如何传给 server?index方法显然应该使用HTTP GET,这会把关键字加到url后面,当然不符合REST的风格。要解决这个问题,可以把每次search看作一个资源,因此要创建create和 index方法,create用来在用户点击“搜索”按钮是通过HTTP POST把关键字传给server,然后index则用来显示搜索结果。这样一来,我们还可以记录用户的搜索历史。使用同样的方法,我们也可以对 login应用REST,即每次login动作是一个资源。
现在,我们来复杂一些的东东。如何用url表达“category为ruby的article”?一开始可能想到的是/category/ruby /articles,这种想法很直观。但是我觉得里面的category是不需要的,我们可以直接把“/ruby”理解为“category是 ruby”,也就是说“ruby”出现的位置说明了它指的就是category。OK,/ruby/articles,单单从这个url上看,我们能获得 多少关于category的信息呢?显然category隐藏在了url后面,这样做到底好不好,应该是仁者见仁,智者见智了。对于如何表达 category这样的东西,我还没想出很好的方式,大家有什么好idea,可以一起讨论。
另外还有一种url形式,它对应到程序中的继承关系。比如product是一个父类,book和computer是其子类。那么所有产品的url应 该是/products,所有书籍的url应该是/books,所有电脑的url应该是/computers。这一想法就比较直观了,而且再次验证了 url可以帮助我们进行设计的论点。
让我再说明一下我的想法:如果每个用户需求都可以抽象为资源,那么就可以完全使用REST。
由此看来,使用REST的关键是如何抽象资源,抽象得越精确,对REST的应用就越好。因此,如何改变我们目前根深蒂固的基于action的思想是 最重要的。
有了对第一个问题的讨论,第二个问题就容易讨论多了。REST会取代MVC吗?还是彼此是互补关系(就像AOP对于OOP)?答案是It depends!如果我们可以把所有的用户需求都可以抽象为资源,那么MVC就可以推出历史的舞台了。如果情况相反,那么我们就需要混合使用REST和 MVC。
当然,这是非常理想的论断。可能我们无法找到一种方法可以把所有的用户需求都抽象为资源,因为保证这种抽象的完整性(即真的是所有需求都可以)需要 形式化的证明。而且即使被证明出来了,由于开发人员的能力和喜好不同,MVC肯定也会成为不少人的首选。但是对于希望拥抱REST的人来说,这些都没有关 系。只要你开发的系统所设计的问题域可以被合理地抽象为资源,那么REST就会成为你的开发利器。
所以,所有希望拥抱REST的朋友们,赶快训练自己如何带上资源的眼镜看世界吧,这才是REST的核心所在。
robbin以前说过REST能让代码减少到很少的程度(具体到多少忘了)。现在看来似乎有点言过其实。REST远没有想像中的那么强大。在我看来,如果 采用纯REST架构的话,所谓的resource就是经过阉割的OO模型,只有属性,没有行为。为了表现这些被切掉的行为,我必须凭空再造出一个个所谓的 resource来,切掉的行为越多,要造的就越多。这样的话,代码不可能有数量级的减少。再想一下,仅仅为了漂亮的URL,放弃自然和符合人类思维习惯 的OO模型,这样真的值吗。套用T1同学的话来说:辛辛苦苦几十年,一夜回到了解放前。
所以我认为,REST只是在异构系统之间的webservice通讯上有优势,其他时候,还是该匝地匝地比较好。
--------------------------------------------------------------------
但是我觉得REST有点类似于把数据库table直接暴露出来的意思,而把应用逻辑编程任务交给了调用者。灵活是灵活,但实际上只是转移了代码的位置,而 不是把它们封装在下面。一个(或一组)应用是否合适使用REST,还是要看具体情况。
目前RoR的REST实现实际上就是REST和MVC混合方式,从实际开发角度来说,正如作者所言,很多情况难以进行资源的抽象。不过,即便如此,已经足 够有革命性的意义了。
我觉得Rails采用REST混合MVC来实现REST是目前的必然选择。我们大家都已经熟悉了OO和MVC,对于REST这样一个新鲜事物,肯定 会带着以前的眼镜去看。随着时间的推移,REST的思想和应用会逐步成熟,人们对其理解也会越来越深刻。到那时才能真正看出REST能不能、如何完全走出 MVC。
我们在使用UP开发方法时,往往需要识别系统的model和use case。有些书中教我们把用户需求中的名词抽象为use case,而动词则是use case。现在对REST建模有着同样的问题:我们可以很直观地把名词抽象为resource,但是很难处理动词。而MVC恰恰适合于处理动词。这也许就 应了一个老生长谈的思想,没有什么技术是可以覆盖所有方面的,只有适当地混合使用各种技术、扬长避短,才会获得最好的效果。
REST与Ajax的结合也是个很吸引人的话题,试想由RESTful的服务端API与客户端的ajax技术的相互调用,能够产生什么样的场景?很想听听 大牛的看法。
目前我还想想不出REST加Ajax会产生什么样令人震撼的场景。REST的核心是resource,而我觉得对resource的操作最后肯定是通过传 统HTTP请求发送到server端的。比如写一篇blog文章,无论如何利用Ajax,最后应该用传统的HTTP POST请求发送到server以便保存。
REST和Ajax的一个比较有趣的关系是,Ajax也许可以帮助我们解决resource难以建模的问题。就像我说过的那样,对名词的抽象很直观,但是 动词就比较难办。而Ajax正好可以用来处理这些动词,同时保证url不变。当然,如何使用Ajax处理这些动词也是一个可以讨论的话题,我这里只是说有 这个可能性。不过话说回来,即便使用Ajax处理动词的话,如何设计Ajax请求所发送到的url仍然是一个问题。目前来看,最可能的设计还是MVC形式 的。所以一个可能的测量是:对所有名词使用REST架构,对动词尽量使用Ajax实现的MVC。这样至少可以保证url看起来是RESTful的。
理解REST软件架构
一种思维方式影响了软件行业的发展。REST软件架构是当今世界上最成功的互联网的超媒体分布式系统。它让人们真正理解我们的网络协议HTTP本来面貌。 它正在成为网络服务的主流技术,同时也正在改变互联网的网络软件开发的全新思维方式。AJAX技术和Rails框架把REST软件架构思想真正地在实际中 很好表现出来。今天微软也已经应用REST并且提出把我们现有的网络变成为一个语义网,这种网络将会使得搜索更加智能化。
REST与HTTP协议
REST软件架构是由Roy Thomas Fielding博士在2000年首次提出的。他为我们描绘了开发基于互联网的网络软件的蓝图。REST软件架构是一个抽象的概念,是一种为了实现这一互 联网的超媒体分布式系统的行动指南。利用任何的技术都可以实现这种理念。而实现这一软件架构最著名的就是HTTP协议。通常我们把REST也写作为 REST/HTTP,在实际中往往把REST理解为基于HTTP的REST软件架构,或者更进一步把REST和HTTP看作为等同的概念。
今天,HTTP是互联网上应用最广泛的计算机协议。HTTP不是一个简单的运载数据的协议,而是一个具有丰富内涵的网络软件的协议。它不仅仅能够对于互联 网资源进行唯一定位,而且还能告诉我们对于该资源进行怎样运作。这也是REST软件架构当中最重要的两个理念。而REST软件架构理念是真正理解HTTP 协议而形成的。有了REST软件架构理念出现,才使得软件业避免了对HTTP协议的片面理解。只有正确的理论指导,才能避免在软件开发的实际工作过程中少 走弯路。
REST与URI(资源定位)
REST软件架构之所以是一个超媒体系统,是因为它可以把网络上所有资源进行唯一的定位,不管你的文件是图片、文件Word还是视频文件,也不管你的文件 是txt文件格式、xml文件格式还是其它文本文件格式。它利用支持HTTP的TCP/IP协议来确定互联网上的资源。
REST与CRUD原则
REST软件架构遵循了CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新 (Update)和销毁(DELETE)就可以完成对其操作和处理了。其实世界万物都是遵循这一规律:生、变、见、灭。所以计算机世界也不例外。这个原则 是源自于我们对于数据库表的数据操作:insert(生)、select(见)、update(变)和delete(灭),所以有时候CRUD也写作为 RUDI,其中的I就是insert。这四个操作是一种原子操作,即一种无法再分的操作,通过它们可以构造复杂的操作过程,正如数学上四则运算是数字的最 基本的运算一样。
REST与网络服务
尽管在Java语言世界中网络服务目前是以SOAP技术为主,但是REST将是是网络服务的另一选择,并且是真正意义上的网络服务。基于REST思想的网 络服务不久的将来也会成为是网络服务的主流技术。REST不仅仅把HTTP作为自己的数据运输协议,而且也作为直接进行数据处理的工具。而当前的网络服务 技术都需要使用其它手段来完成数据处理工作,它们完全独立于HTTP协议来进行的,这样增加了大量的复杂软件架构设计工作。REST的思想充分利用了现有 的HTTP技术的网络能力。在德国电视台上曾经出现过一个这样的五十万欧元智力题:如何实现网络服务才能充分利用现有的HTTP协议?该问题给出了四个答 案:去问微软;WSDL2.0/SOAP1.2;WS-Transfer;根本没有。这个问题告诉我们HTTP并不是一个简单的数据传来传去的协议,而是 一个聪明的会表现自己的协议,这也许是REST = Representational State Transfer的真正含义。
实际上目前很多大公司已经采用了REST技术作为网络服务,如Google、Amazon等。在Java语言中重要的两个以SOAP技术开始的网络服务框 架XFire和Axis也把REST作为自己的另一种选择。它们的新的项目分别是Apache CXF 和Axis2 。Java语言也制定关于REST网络服务规范:JAX-RS: Java API for RESTful Web Services (JSR 311)。相信还会出现更多与REST相关的激动人心的信息。
REST与AJAX技术
尽管AJAX技术的出现才不到两年时间,但是AJAX技术遵循了REST的一些重要原则。AJAX技术充分利用了HTTP来获取网络资源并且实现了 HTTP没有的对于异步数据进行传输的功能。AJAX技术还使得软件更好地实现分布性功能,在一个企业内只要一个人下载了AJAX引擎,其它企业内部的人 员,就可以共享该资源了。AJAX技术遵守REST准则的应用程序中简单和可伸缩的架构,凡是采用AJAX技术的页面简洁而又丰富,一个页面表现了丰富多 彩的形态。
AJAX技术还使用了一种不同于XML格式的JSON文件格式,这个意义在哪里呢?在REST软件架构下我们不能对于XML文件进行序列化处理,这样程序 员必须要使用自己的XML绑定框架。而以序列化的JavaScript对象为基础的JSON已经获得了广泛认可,它被认为能以远比XML更好的方式来序列 化和传输简单数据结构,而且它更简洁。这对REST是一个极大贡献和补充。
当前的网络应用软件还违背了REST的“无状态服务器”约束。REST服务器只知道自己的状态。REST不关心客户端的状态,客户端的状态自己来管理,这 是AJAX技术的应用之地。通过AJAX技术,可以发挥有状态网络客户机的优势。而REST的服务器关心的是从所有网络客户端发送到服务器操作的顺序。这 样使得互联网这样一个巨大的网络得到有序的管理。
REST与Rails框架
Ruby on Rails框架(简称Rails或者Rails框架)是一个基于Ruby语言的越来越流行的网络应用软件开发框架。它提供了关于REST最好的支持,也是 当今应用REST最成功的一个软件开发框架。Rails框架(从版本1.2.x起)成为了第一个引入REST作为核心思想的主流网络软件开发框架。在 Rails框架的充分利用了REST软件架构之后,人们更加坚信REST的重要性和必要性。Rails利用REST软件架构思想对网络服务也提供了一流的 支持。从最直观的角度看待REST,它是网络服务最理想的手段,但是Rails框架把REST带到了网络应用软件开发框架。这是一次飞跃,让REST的思 想从网络服务的应用提升到了网络应用软件开发。利用REST思想的simply_restful插件已经成为了Rails框架的核心内容。
REST安全性
我们把现有基于SOAP的网络服务和基于REST/HTTP网络服务作个比喻,前者是一种传统的寄信方式,而后者是现代网络的电子邮件方式。要是是寄信和 电子邮件都有病毒存在的话,传统的寄信被送到对方就很危险,而电子邮件是开发的,电子邮件供应商比如Google为我们检查了电子邮件是否有病毒。这里并 不是说明SOAP网络服务消息包含义病毒,而是说明HTTP是无法处理SOAP信息包究竟好不好,需要额外的软件工具解决这一问题,包括防火墙也用不上和 管不了。
REST/HTTP网络服务的信息包可以被防火墙理解和控制。你可以按照操作和链接进行过滤信息包,如你可以规定从外部来的只能读取(GET操作)自己服 务器的资源。这样对于系统管理员而言使得软件管理更为简单。REST的安全性还可以利用传输安全协议SSL/TLS、基本和摘要式认证(Basic und Digest Authentication)。除了这些REST自身的安全性功能外,还可以利用像基于信息的Web Services Security(JSR 155)作为REST不错的补充
但是 REST 到底是什么呢?论文我看不懂,不过找到一篇更简单易懂的东西:《Building Web Services the REST Way》。
根据这篇文章,我整理了一下我自己对 REST 的理解:
REST 首先只是一种架构样式,不是一种标准。这点和 Ajax 类似,两者都是利用现有的成熟技术。
在 REST 的定义中,一个 Web 应用总是使用固定的 URI 向外部世界呈现(或者说暴露)一个资源。
URI 是英文 Uniform Resource Identifier 的缩写,中文翻译“通用资源标志符”。
“通用资源标志符”是指唯一标识一个资源(xhtml 文件、图片、css 样式表)的字符串。当然了,RFC 中定义的 URI 复杂得多,不过我们此处将 URI 想象成一个人的身份证号码就行了(你不能有两个同时有效的身份证号码,一个号码也不可能同时对应两个人)。而我们天天挂在嘴边的 URL 地址就是 URI 的一种表现形式(个人理解,有错请纠正)。
知道什么是 URI 后,我们来看一个实际例子:
http://www.example.com/photo/logo 指向 example.com 网站(可以视为一个 Web 应用)中类型为 photo,名字为 logo 的资源。我们用浏览器访问这个 URI,看到的将可能是一个 xhtml 文档,其中用 <img src=”……” /> 来显示实际的照片。
http://www.example.com/photo/logo 很容易让你想到 URL 重写。事实上,这个地址很可能会在服务器内部处理为 http://www.example.com/photo.php?name=logo 这样的地址。photo.php 是服务器端的一个动态脚本文件,根据 name 参数生成 xhtml 文档返回给浏览器。
现在假设我们要获取这张照片的 XML 文档。XML 文档中包含照片的文件名、文件大小、拍摄日期等等信息。也就是说我们要获取“同一个资源的不同表现形式的数据”。对于这个要求,我们可以很容易的用另一个 URL 地址达到:http://www.example.com/xml/logo 。
但是,这就违背了“URI 唯一标识一个资源”的定义。如果我们要获取同一个资源的多种表现形式,那么就要使用更多的 URL,从而给一个资源指定了多个不同的 URI。
而在 REST 中,不管是获取照片的 xhtml 文档还是 XML 文档,或者照片文件本身,都是用同一个 URI,就是 http://www.example.com/photo/logo 。
那这是怎么办到的呢?Ruby On Rails 中是通过分辨 HTTP Request Header 信息来分辨客户端是想要取得资源的哪一种表现形式的数据。
当我们用浏览器访问一个网址时,浏览器会构造一个 HTTP 请求。这个请求有一个头信息,其中包括了本次请求接受何种类型的数据。通常浏览器发送的 HTTP 请求头中,Accept 的值都是 */*,也就说接受服务器返回的任何类型的数据。
看到这里,聪明的家伙应该知道了。只要我们指定一个特定的 Accept 参数,那么服务器就可以通过判断该参数来决定返回什么类型的数据。所以在一个采用 REST 架构的应用中,要获取同一个资源的不同表现形式的数据,只需要使用不同的 HTTP 请求头信息就行了。
如果考虑为 Web 应用增加 Web Services,这种技术的价值就体现出来了。比如我写了一个 Delphi 程序,现在只需要构造一个包含 Accept: text/xml 的 HTTP 请求头,然后将请求发送到 http://www.example.com/photo/logo 就可以了。返回的结果就是一个 XML 文档,而不是 xhtml 文档。
因为我们的 HTTP 请求头信息有不同的状态,从而可以获得不同的数据,所以叫做“具象状态传输”
—————————————
除了上面的用法,REST 还有进一步的扩展。
我们在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有 HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的 CRUD(创建、读取、更新和删除)操作:
• POST: 创建
• GET: 读取
• PUT: 更新
• DELETE: 删除
经过这样的一番扩展,我们对一个资源的 CRUD 操作就可以通过同一个 URI 完成了:
http://www.example.com/photo/logo (读 取)
仍然保持为 [GET] http://www.example.com/photo/logo
http://www.example.com/photo/logo/create (创 建)
改为 [POST] http://www.example.com/photo/logo
http://www.example.com/photo/logo/update (更 新)
改为 [PUT] http://www.example.com/photo/logo
http://www.example.com/photo/logo/delete (删 除)
改为 [DELETE] http://www.example.com/photo/logo
从而进一步规范了资源标识的使用。
通过 REST 架构,Web 应用程序可以用一致的接口(URI)暴露资源给外部世界,并提供对资源的操作服务。这对于以资源为中心的 Web 应用来说非常重要。例如照片共享网站、用户社区等。
—————————————
Ruby On Rails 1.2 版对 REST 有很好的支持,但要在 PHP 中应用 REST 还需要解决不少问题:
• 如何在服务端判断 PUT、DELETE 请求方法;
• 如何获取用 PUT、DELETE 请求方法中传递的数据;
• 如何获取 HTTP 请求头信息中的 Accept 参数值;
• 如何在浏览器端发起 PUT 和 DELETE 请求。
不过我仔细看了 PHP 文档,我觉得上面几个问题都是可以解决的。
服务端综合使用 $_SERVER[’HTTP_ACCEPT’]、$_SERVER[’REQUEST_URI’]、$_SERVER[’REQUEST_METHOD’]、$_SERVER[’QUERY_STRING’] 这些变量应该可以搞定前面三个问题。而第四个问题则可以用 JavaScript 的 XMLHttpRequest 对象来实现。
不过我想 REST 的真正价值在于 Web Services,而不是通过浏览器操作的应用程序。