re:Invent全球大会引领风向重塑未来 了解详情
写点什么

用 ROR 创建面向资源的服务

  • 2008 年 6 月 09 日
  • 本文字数:4170 字

    阅读完需:约 14 分钟

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<p>ActionController::Routing::Routes.draw do |map|</p><p> map.resources :weblogs do |weblog|</p><p> weblog.resources :entries</p><p> end</p><p>end</p>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|<p> format.html { render :template => 'weblogs/show' }</p><p> format.xml { render :xml => weblog.to_xml }</p><p> format.png { render :text => weblog.generate_image,</p><p> :content_type => "image/png" }</p><p>end</p><br></br>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 式服务有什么不同》

相关阅读:****

2008 年 6 月 09 日 20:321991
用户头像

发布了 63 篇内容, 共 23.2 次阅读, 收获喜欢 7 次。

关注

评论 1 条评论

发布
暂无评论
发现更多内容

优化JAVA代码总结

跳蚤

区块链架构下的智慧城市发展加速

CECBC

信息安全

秒杀系统的难点在哪?如何突破?

跳蚤

为何要构建团队契约

Bruce Talk

敏捷 Agile

博文推荐 | Apache Pulsar 延迟消息投递解析

Apache Pulsar

kafka 开源 RocketMQ pulsar Apache Pulsar

不写代码可以写爬虫程序吗?老师说可以,无编码学爬虫之一。

梦想橡皮擦

Python 28天写作 2月春节不断更

并发与并行

ES_her0

28天写作

译文 | 深度剖析 Pulsar Functions

Apache Pulsar

大数据 kafka 开源 pulsar Apache Pulsar

【答疑点评必看】如何从「数据范围」中找到解题「突破口」...

宫水三叶的刷题日记

面试 LeetCode 数据结构与算法

产品经理训练营知识汇总

SilentMacUser

产品经理 产品经理训练营 邱岳

架构师训练营第八周作业 - 命题作业

阿德儿

区块链一文知

秋呈

区块链+

区块链与数字货币的发展到底有什么意义

CECBC

数字货币

翻译:《实用的Python编程》03_01_Script

codists

Python

「架构师训练营 4 期」 第八周 - 001&2

凯迪

架构师训练营 4 期

MySQL连接超时关闭问题解决

flyer0126

MySQL MySQL优化

浅谈JVM 垃圾回收原理

跳蚤

递归的人生哲学

Nick

数据结构 算法 递归

微服务架构设计与最佳实践

万俊峰Kevin

微服务 go-zero Go 语言

(28DW-S8-Day9) 区块链如何对坏节点容错:拜占庭将军问题

mtfelix

28天写作 拜占庭容错 拜占庭将军

第十三周作业&总结

胡益

join为啥会阻塞主线程?

叫练

join

第8周课后练习-性能优化二

潘涛

架构师训练营 4 期

作业 - 第四章 业务流程与产品文档 (二)

hao hao

Elasticsearch Query Phase

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

数据库规范设计说明书 整理

edd

APP启动流程图

林亚超

【管理笔记16】行销的获利性分析

俊毅

28天写作

网络故障的排错思路指南

《函数式编程精粹》(1) 函数式思考

陈皓07

数字经济发展的时代特色

CECBC

数字经济

作为下一代云计算基础设施,虚拟化技术如何提高信息管理效率?

作为下一代云计算基础设施,虚拟化技术如何提高信息管理效率?

用ROR创建面向资源的服务-InfoQ