BT

用ROR创建面向资源的服务

作者 徐涵 发布于 2008年6月10日 | 被首富的“一个亿”刷屏?不如定个小目标,先把握住QCon上海的优惠吧!

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

We notice you’re using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.