BT

用ROR创建面向资源的服务

作者 徐涵 发布于 2008年6月10日 | 注意:GTLC全球技术领导力峰会 推动杰出的技术领导者学习和成长!

Ruby on Rails(ROR)的成功主要在于它的简单化假设(simplifying assumptions)。Rails并不是向你提供一大堆用以解决各种问题的工具,而是为你提供了一种用以解决各类常见问题的方式。可以非常讯速地创建 Rails应用,只要:尝试把关系数据库中的数据暴露出来,数据库表有确定的名称与结构,你愿意采用一种模型-视图-控制器(Model-View- Controller)架构,等等。因为在Web应用领域中,很多问题均符合这些假设,所以问题一般都能顺利解决,很少会遇到麻烦。

Rails过去的版本暴露的是经典的REST-RPC混合服务,不过自Rails 1.2版起,它开始更加注重REST式设计了。或许这是必然的:HTTP统一接口(uniform interface)是另一个简单化假设(simplifying assumptions)。我在第7章已经向你展示过Rails框架是如何做到用少量代码实现复杂的REST式服务了,在这一节,我将对此做一个回顾,并 概括性地讲述Rails的REST式架构。

路由

当Rails收到一个HTTP请求时,它会根据该请求的目标URI,把请求路由给适当的控制器类(controller class)处理。如示例12-1所示,config/routes.rb文件定义了Rails应如何对给定请求进行处理。

示例12-1:一个简单的routes.rb文件

# routes.rb

ActionController::Routing::Routes.draw do |map|

map.resources :weblogs do |weblog|

weblog.resources :entries

end

end

config/routes.rb文件可以相当复杂。第7章中展示的那个(见示例7-3)相对来说算复杂的:我有很多资源,必须克服简单化假设,以得到想要的URI结构。示例12-1是一个较简单的、接纳了简单化假设的routes.rb文件。

通过该文件可知有两个控制器类(WeblogsController和EntriesController),该文件告诉Rails如何把收到的请 求路由给这两个类。WeblogsController类处理发给 /weblogs 及 /weblogs/{id} 的请求。这里的路径变量{id}是放在params[:id]里的。

EntriesController类处理发给/weblogs/{weblog_id}/entries及/weblogs /{weblog_id}/entries/{id}的请求。这里的路径变量{weblog_id}是放在params[:weblog_id]里的, {id}是放在params[:id]里的。

{id}、{weblog_id}等变量常用于进行资源(resource)与系统里特定对象(object)的关联。它们常常跟数据库里的记录 ID相对应,并经常被代入ActiveRecord的find方法。在我的社会性书签服务(见第7章)里,试过给它们像{username}这样具备描述 性的名称,并用它们来标识名称而不是ID。

资源、控制器和视图

正如我在第7章中所讲述的那样,每个Rails控制器可以暴露两种资源。你可以有一个“列表”或“工厂”资源(响应GET和/或POST请求)和许 多“对象”资源(响应GET、PUT和DELETE请求)。一般来说,列表资源跟数据库表相对应,而对象资源跟数据库表里的记录相对应。

每个控制器都是一个Ruby类;于是,向一个类“发送”HTTP请求,就意味着调用某个特定的方法。Rails为每个控制器定义了五个标准方法,并 通过HTTP GET暴露了两个特殊的视图模板。在示例12-1中调用的map.resources :weblogs 使得下面这七种HTTP请求成为可能。

  • GET/weblogs:一个博客列表。 Rails调用WeblogsController#indexmethod。
  • GET/weblogs/new:用于新建一个博客的表单。Rails用app/view/weblogs/new.rhtml来呈现该视图。该视图是一个超媒体文件,它描述了客户端应发送什么样的HTTP请求来创建一个博客。
    换句话说,这是一个HTML表单(其实也可以是一个简短的WADL文档)。这个表单指出,客户端应向/weblogs(见下)发送POST请求来新建博 客。它还指出客户端应为这个新博客采用什么样的表示格式(representation format),以便服务器能理解它。
  • POST/weblogs:创建一个新博客。Rails调用WeblogsController#create方法。
  • GET/weblogs/{id}:一个博客。Rails调用WeblogsController#show方法。
  • GET/weblogs/{id};edit:修改博客状态的表单。Rails用app/view/weblogs/edit.rhtml来呈现该视图。该视图是一个超媒体文件,它描述了客户端应发送什么样的HTTP请求来修改博客的状态。
    该视图可以是一个HTML表单或一个简短的WADL文档。它告诉客户端应如何向 /weblogs/{id} 发送PUT请求。
  • PUT/weblogs/{id}:修改博客的状态。Rails调用WeblogsController#update方法。在这里的“状态”指的是与博客资源关联的状态(state),如博客的名称及作者联系信息等。各篇博客文章是作为单独资源暴露出来的。
  • DELETE /weblogs/{id}:删除一个博客。Rails调用WeblogsController#delete方法。

你也许不会为创建的所有控制器都暴露这七个接口。特别是,多半不会使用那些特殊视图,除非打算把Web服务作为一个Web网站来运营——没问题,你只要别实现那些不打算暴露的方法或视图就行了。

返回的表示

Rails使我们能够更容易实现“根据客户端的请求,返回一个资源的不同表示”。示例12-2所示的Ruby代码可以返回一个博客的三种不同表示。 它将根据客户端请求的目标URI或Accept报头决定返回哪个表示。若客户端向/weblogs/1.html发出请求,就会得到HTML版表示;若客 户端向/weblogs/1.png发出请求,就会得到PNG版表示。respond_to函数负责解释客户端的能力与需求。你所需要做的,就是按优先级 实现被支持的选项。

示例12-2:返回多个表示中的一个

respond_to do |format|

format.html { render :template => 'weblogs/show' }

format.xml { render :xml => weblog.to_xml }

format.png { render :text => weblog.generate_image,

:content_type => "image/png" }

end

HTML和ActiveResource XML序列化格式(serialization format)是两种特别常见的表示格式(representation formats)。HTML表示是用Rails视图来展现的(就像在面向人类用户的Web应用程序中一样)。要把一个ActiveRecord对象暴露为 一个XML文档,你只要调用该对象(或对象列表)的to_xml方法即可。

通过Rails插件,我们可以轻易地把具有其他表示格式的数据暴露出来。在第7章,我安装了atom-tools Ruby gem,用以为书签列表生成Atom提要(feed)。示例7-8里有一个respond_to代码块,它用于根据请求决定返回Atom还是普通XML表 示。

收到的表示

Rails的工作就是根据收到的表示(incoming representation)生成一组关键字-值对(key-value pairs),并以params hash的形式来提供这些关键字-值对。默认情况下,它知道如何解析Web浏览器发送的表单编码的(form-encoded)文档,以及to_xml生 成的XML文档。

如果希望它能够解析自己的表示格式,你可以在ActionController::Base.param_ parsers hash里添加一个新的Proc对象。该Proc对象是一段代码,它的作用是处理服务器收到的具有给定媒体类型的表示。关于param_parsers hash的详细情况,请参阅Rails文档。

将Web应用作为Web服务

Rails 1.2在融合human web与programmable web方面做得相当好。正如我在第3章中展示的,Rails自带一个叫做scaffold_resource的代码生成器,它可以把数据库表暴露为一组资 源。你可以用Web浏览器来访问这些资源,也可以用Web服务客户端(如ActiveResource)来访问这些资源。

如果你用Web浏览器来访问scaffold_resource服务的话,你会得到数据库对象的HTML表示,以及用于操作它们的HTML表单(由 前面提到的new.rhtml和edit.rhtml生成)。你可以通过发送表单编码格式(form-encoded format)的表示来创建、修改或删除资源。PUT和DELETE请求是通过重载的POST(overloaded POST)来模拟的。

如果你用一个Web服务客户端来访问scaffold_resource服务的话,你会得到数据库对象的XML表示。你可以修改该XML文档,并通过PUT请求把它发回去。非重载的(non-overloaded)POST与DELETE请求的工作方式与你预期的一样。

关于programmable web与human web的基本相似性,没有比这更能令人信服的例子了。第7章因为篇幅原因所以没有讲述Rails的这方面内容,不过它非常有说服力地证明了Rails适合 于“设计具有相同功能的网站和Web服务”的场合。Rails可以用同一套底层代码来暴露网站和Web服务。

Rails/ROA设计步骤

下面是一个根据第6章的通用设计步骤修改后得到的版本。我在第7章设计社会性书签服务时已经非正式地采用这些设计步骤了。这里的设计步骤跟第6章的 区别在于:你不是直接把数据集划分为一个个资源,而是把数据集划分为一个个控制器,再把控制器划分为资源。这样,就不会出现“你最后得到的资源不适应 Rails控制器”的问题了。

  1. 规划数据集。
  2. 把数据集分配给一个个控制器。
    对于每一个控制器:
    • 该控制器暴露的是一个列表或工厂资源吗?
    • 该控制器暴露的是一组对象资源吗?
    • 该控制器暴露的是一个用于创建或修改资源的表单资源吗?
      对于列表和对象资源:
      • 设计来自客户端的表示(假如与Rails标准不同的话)。
      • 设计返回给客户端的表示。
      • 把该资源与已有资源联系起来。
      • 考虑有哪些典型的事件经过?第9章描述的“基于数据库的应用的控制流”在此会有帮助。
      • 考虑可能出现哪些错误情况?同样,这里常常可以采用基于数据库的应用的控制流。

本文节选自博文视点出版公司即将推出的经典著作《RESTful Web Services中文版》中的第12章《REST式服务框架》。

《RESTful Web Services中文版》向 读者介绍了什么是REST、什么是面向资源的架构(Resource-Oriented Architecture,ROA)、REST式设计的优点、REST式Web服务的真实案例分析、如何用各种流行的编程语言编写Web服务客户端、如何 用三种流行的框架(Ruby on Rails、Restlet和Django)实现REST式服务等。不仅讲解REST与面向资源的架构(ROA)的概念与原理,还向读者介绍如何编写符合 REST风格的Web 2.0应用。本书详实、易懂,实战性强,提供了大量RESTful Web服务开发的最佳实践和指导,适合广大的Web开发人员、Web架构师及对Web开发或Web架构感兴趣的广大技术人员与学生阅读。

与此同时,博文视点还授权InfoQ中文站独家为大家提供额外的样章进行试读:欢迎下载第3章《REST式服务有什么不同》

相关阅读

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论
提供反馈
错误报告
商务合作
内容合作
Marketing
InfoQ.com及所有内容,版权所有 © 2006-2016 C4Media Inc. InfoQ.com 服务器由 Contegix提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司 京ICP备09022563号-7 隐私政策
BT