超媒体到底是什么?!
如果你有幸听说过 REST 架构风格,那么你也可能听说某些人认为统一接口是最重要的约束,尤其是该接口在制约资源上能被调用的方法方面。但是,你没有意识到的是,对于统一接口还有很多东西。特别是一个被笨拙地冠以“超媒体即应用状态引擎(hypermedia as the engine of application state)”名字的子约束,它可以认为是 REST 最重要的约束,在某种意义上它独力塑造了我们所熟知的 RESTful 系统的大部分的“形状”。
本文中我们将深入讨论这个约束,试图去搞清楚它的含义和理解它的价值。
定义
不幸的是, REST 论文没有在这个约束上展开,除了它的名字和对它实际应用的描述:
所以,模型应用是一个从一个状态迁移到下一个状态的引擎,迁移是通过对当前表述性集合中可选状态进行检查和选择而完成的。
尽管它给我们提供了一个有用的描述,但是在我看来,它不能帮助我们真正理解约束本身的作用域;约束真正允许的是什么,不允许的又是什么。由此开始,接下来还值得注意的是我们能从约束自身名字中获取些什么信息。
“应用状态”指的是一个状态,它决定了用户在完成一个任务的流程中所处的“位置”。例如,在做个人银行业务的时候,用户是正在浏览帐户余额,还是在填写账目付款单,或者是在订购新的支票?它们每个都是不同的应用状态。有些人错误地认为“状态”是指资源状态,在上面例子里,资源是指帐户余额或者近期付款清单。但“应用状态”和“资源状态”是不同的。
应用状态也被称为“会话状态”,该状态也是 REST 的“无状态”约束所指的状态,这种约束要求客户端独自维持状态。反之,如果你使用诸如 VNC 或者 Windows 远程桌面的远程会话技术,那么应用状态完全保存在服务器上。
Ted Nelson 在 1962 年创造了“超媒体”一词,是他发明的“超文本”泛化。鉴于超文本产生了内部互联的文本文档,那么超媒体将范围扩展到了任何形式的媒体。当然,两者的关键点都是我们可以在使用的内容嵌入链接。
约束实战
REST 在 2003/2004 年开始获得一些从事面向互联网服务开发者的关注——至少这些开发者确实将他们的服务冠以“REST”的绰号——最明显的是两个高调、自我标榜的“REST API”: Flickr 和 Amazon 。有趣的是两个服务也同时提供了基于 SOAP 的接口,但是与“REST API”相比,这两个 SOAP API都没有显出更多的使用。最终,REST 社区拥抱了这些服务,并且将它们作为一种进一步解释在Web 上使用REST 风格的价值和魅力的工具。不幸的是,这些API 存在一个问题:它们不完全是RESTful 的,因为它们无视(至少)一个REST 约束。事实上,Flickr API(以及Amazon、del.icio.us 等等)的问题比我们在此讨论的要多得多,此处我们只关注那些和超媒体有关系的问题。
幸运的是,我们不需要为寻找这些问题花太多时间。以“flickr.contacts.getList”操作返回的样本数据为例,用户可使用该操作获得他们自己的联系人清单:
<contacts page="1" pages="1" perpage="1000" total="3"><br></br> <contact nsid="12037949629@N01" username="Eric" iconserver="1"<br></br> realname="Eric Costello"<br></br> friend="1" family="0" ignored="1" /><br></br> <contact nsid="12037949631@N01" username="neb" iconserver="1"<br></br> realname="Ben Cerveny"<br></br> friend="0" family="0" ignored="0" /><br></br> <contact nsid="41578656547@N01" username="cal_abc" iconserver="1"<br></br> realname="Cal Henderson"<br></br> friend="1" family="1" ignored="0" /><br></br></contacts>
这里,“nsid”属性包含了一个表示单个联系人的唯一标识符,在这个例子里是其中的三个联系人。但是一旦客户端已经检索到了这个文档,接下来会怎样?如果他们想了解更多关于 Cal Henderson 的信息怎么办?通过快速检查 Flickr API 文档,可以发现有一个叫做"flickr.people.getInfo"的操作,它接收nsid 作为一个参数,返回那个nsid 字符串所标识联系人的更多信息。那么为了获得关于Cal 的更多信息,我们在HTTP GET 消息中需要使用的URI 将是:
http://api.flickr.com/services/rest/?method=flickr.people.getInfo?auth_key=xxxx&user_id=41578656547@N01
这不是超媒体。一个超媒体解决方案会使用标准化的标识符——对于 Web 来说就是 URI——而非私有的标识符,这避免了客户端在从联系人清单文档浏览到个人信息文档过程中需要特定于 Flickr 的知识。如果采用标准标识符,那么第一个文档应该是:
<contacts page="1" pages="1" perpage="1000" total="3"><br></br> <contact nsid="http://api.flickr.com/services/rest/?method=flickr.people.getInfo?auth_key=xxxx&user_id=12037949629@N01" username="Eric" iconserver="1"<br></br> realname="Eric Costello"<br></br> friend="1" family="0" ignored="1" /><br></br> <contact nsid="http://api.flickr.com/services/rest/?method=flickr.people.getInfo?auth_key=xxxx&user_id=12037949631@N01" username="neb" iconserver="1"<br></br> realname="Ben Cerveny"<br></br> friend="0" family="0" ignored="0" /><br></br> <contact nsid="http://api.flickr.com/services/rest/?method=flickr.people.getInfo?auth_key=xxxx&user_id=41578656547@N01" username="cal_abc" iconserver="1"<br></br> realname="Cal Henderson"<br></br> friend="1" family="1" ignored="0" /><br></br></contacts>
万事大吉,但是把这些变到超媒体对他们和他们的用户会有什么好处呢?
为了从一个应用状态前进到另一个应用状态,以 Flickr 现在的方式,要求客户端处理特定于 Flickr 的知识,这是另一种形式的私有应用。它不仅是私有的,而且即使在 Flickr API 本身内部它也不是一个一致的模型,因为从联系人清单文档浏览到联系人个人信息文档所需要的知识(如上面所述),不同于从联系人个人信息文档浏览到联系人个人照片清单所需要的知识(它是“flickr.photos.getContactsPublicPhotos”)。这给Flickr 提出了可发展性问题,因为即使对API 进行简单地扩展也轻易地要求传播新知识,需要依次修改客户端代码。一个诸如搜索引擎这样的普通客户端,也不能通过这些API 来给Flickr 内容建立索引,因为我确信搜索引擎的维护人员——或者其他使用这个应用程序模型的人——对于每次Ficlkr 扩展API 都需升级他们的软件不会有太多的兴趣。再次重申,这不仅仅限于超媒体:任何标准化的应用模型都会提供同样的好处。当然,超媒体模型已经证明它自己非常流行,即使那些使用它的人们没有意识到自己所做的事。
因此通过使用一个公共应用模型,它不仅仅是标准化的,而且总是稳定的,你可以通过准许消费者和生产者独立进化来降低两者间耦合。通过这种方法,新旧服务可以被组合在一起形成一个组合应用,新老客户端也可以合并成一个。我认为,一旦Web 可以让人们简单地在一个文档中包含一个指向几年前创作的页面和该内容消费者,当我们使用它时,完全可以无缝地浏览内容而无须下载一个新版本的浏览器。这都是特意设计的,决非偶然。
值得注意的是,Web 决没有垄断超媒体的使用。Email 是另一个已普及的应用,我们大家在互联网上每天都用到它,也是如此。每条Email 消息包含携带发信人和收信人的Email 地址的头,而拥有这些地址中的一个或者多个就足够发送另一条Email 消息了。
当我们在讨论这些鲜为人知的事实时,你可能也会有兴趣知道:Web 本身一个重要方面不使用超媒体: robots.txt ,亦称漫游器排除文档(robot exclusion)。它的工作方式:如果网站希望搜索引擎不要给它们的某些内容建立索引,那么就在他们的“/robots.txt” URI 中简单地放置一个文件,该文件描述了哪些内容不希望被建立索引。但是,就我所知的是“几乎没有人链接到robots.txt 文件”。为什么会这样?“/robots.txt”是一个固定且众所周知的位置,特别是对于搜索引擎来说:给出任何URI,它们都能从URI 构建出这个站点域相应的robots.txt URI。尽管这不是超媒体,因为链接不是动态地从另一个页面发现的,搜索引擎恰恰认为这是一个优点。这不能说它是一个坏的解决方案,因为超媒体方式需要两次网络往返(一次是发现链接robots.txt 的页面,另一次是抓取页面),这对于所有的当事方都是一种负担。所以这也是一个关于超媒体成本的好例子。但是要牢记这点:只有在极少情况下这(译注:即不采用超媒体)真的才是最佳方式。和robots.txt 原因相同,站点地图可能也算一个;但是其他的诸如“favicon.ico”和Apple 新的 iPhone WebClip 特性,将可能从使用超媒体上获益;例如,那些图标可以被一个图像搜索引擎搜索到,而不需要更新搜索引擎软件。
在考虑超媒体时,另一个值得关注的技术是 WADL ,Web 应用描述语言。尽管它自称“RESTful 描述语言”,但是对它有一个重要的警告。考虑从这个从 WADL 文件中摘录的例子:
<resources base="http://service.example.com/myservices/"><br></br> <resource path="search"><br></br> <method name="GET" id="search"><br></br> <request><br></br> <param name="query" type="xsd:string" style="query" required="true"/><br></br> </request><br></br> <response><br></br> <representation mediaType="application/xml" element="yn:ResultSet"/><br></br> <fault status="400" mediaType="application/xml" element="ya:Error"/><br></br> </response><br></br> </method><br></br> </resource><br></br></resources>
这个文件将一个“search”资源声明为一个“myservices”集合的一部分。通过声明使用 HTTP GET 和使用“query”参数,它描述了客户端从它选择的输入字符串构建一个 URI 的方式。
表面上看,它似乎是一个完美 RESTful 的、基于超媒体的解决方案,非常类似 HTML 表单(或者 URI 模板)的使用方式。那么警告是什么?问题在于 WADL 被消费的时机。某些对基于 Web 的解决方案感兴趣的 Web 服务支持者按照他们使用 WSDL 的方式(作为设计时部件)使用 WADL。但是,这样使用 WADL,类似于在编译浏览器的时候,开发一个具有内建知识(比方说 Google 主页表单)的 Web 浏览器:如果在部署浏览器后,Google 以不向后兼容的方式改变了这个表单(即,不仅仅增加一个新的可选参数),那么浏览器将无法使用那些资源 / 服务。使用具有 WADL 的超媒体约束意味着客户端应该在运行时消费 WADL。所以小心地选择你的 WADL 工具,因为那些试图帮助你的一些工具,可能帮倒忙。
结论
希望超媒体约束的价值现在看起来要明显多了。但是,不仅仅如此,我真正希望的是:当你决定使用它时,你能更好地了解哪些实践是你应该避免的。
记住:超媒体只是统一接口约束中的一员,因此对于后者更通用石蕊测试是:如果你正在开发客户端代码,而客户端代码假定它不能适用于所有资源(或者服务端“API”需要客户端的this,译注:服务端需要客户端的信息),那么你就没有使用统一接口。
关于作者
Mark Baker 在 SOA 和 Web 服务社区大名鼎鼎,源于他对推广 REST(表述性状态转移)架构风格的不断努力,以及他批评大多数标准和规范不了解什么成就并持续成就了 Web 的成功。
查看英文原文: Hypermedia in RESTful applications - - - - - -
译者简介:王志雄,长期从事软件开发工作,项目集中在 EAM 和设备点检管理领域。2004 年转入 JAVA 领域,曾经在项目中使用过 Hibernate、Struts、Spring 等。关心软件技术和相关工具的动态,将其中成熟的技术和工具应用到实际的项目之中。关心开源软件的发展动态以及软件过程和敏捷开发的实践探索。
评论