过去的 20 年间,一个趋势主导了商业软件工具的开发:用复杂性对抗复杂性。这一趋势在任何地方 都没有比在分布式计算领域更明显。C 和 Java 社区已经看到一些惊人复杂的框架被构建出来支 持分布式通信。分布式计算环境(DCE)支持用 C 语言编写的应用程序之间的远程过程调用。公共对象请 求代理架构(CORBA)标准支持面向对象应用程序之间的通信。企业 JavaBean(EJB)规范提供安全性、 持久性、事务、消息和远程的服务。对各个框架的宣传甚嚣尘上,但是这些框架都没有满足预期,有些甚 至因为它们的复杂性而成为灾难。在这些框架中,只有 EJB 3.0 属于大力简化的结果,有潜力在分布式 应用程序上成功。市场可能给、也可能不给这个面临强敌的框架另一个空间,但 EJB 仍然需要交付使用 。
最新的大型分布式框架是 Web 服务。Web 服务技术让应用程序可以用平台独立或编程语言独立 的方式相互通信(请参阅 参考资料)。Web 服务标准也受到复杂性恶魔的威胁,但是称作 REST 的替代 策略承诺了更简单的方式。本文介绍了如何在 Ruby on Rails 中添加 REST 风格的 Web 服务,并从 Ruby 和 Java 代码调用服务。
Web 服务领域
就像 EJB、CORBA 和 DCE 一样,Web 服务的 核心抽象也是远程过程调用。Web 服务利用叫做 SOAP(最初,SOAP 代表简单对象存取协议,但是这个术 语现在降级了)的协议,用 XML 表示消息的结构。这里有一个技巧:如果协议用代表简单的 S 开始,那 它就不简单。Web 服务定义语言(WSDL)提供了服务的标准规范。像 SOAP 一样,WSDL 也是一个棘手而 复杂的 API,而 SOAP 和 WSDL 仅仅涉及到了构成 Web 服务这个大怪物的众多 API 的表面(请参阅 参 考资料)。Web 服务需要一次大修,感谢 Roy Fielding 的一份有影响的博士论文,Web 服务得到了大修 (请参阅 参考资料)。
Fielding 的论文描述了 REST 应用程序联网策略。REST 与全堆栈 Web 服务根本不同,主要原因有三 个:
REST 的核心抽象是远程资源而不是远程过程调用。
REST 没有发明一个详尽的标准列表,而是采用现有的 Internet 标准,包括 HTTP、XML 和 TCP/IP。
REST 没有覆盖每个可能场景,而是覆盖了最常见的问题。
请把 REST 想像成浏览。REST 客户使用与浏览器相同的 HTTP 命令访问资源。当 REST 客户访问到资 源的表示时,客户转换到一个状态。使用不同的 HTTP 命令,REST 客户可以创建、读取、更新或删除资 源的记录。
例如,以典型的博客为例。通过输入 URL,例如 blog.rapidred.com,得到贴子的列表。然后,如果 想编辑博客条目,可以在 URL 中输入 HTTP 参数(例如 blog.rapidred.com/edit?article=12345),然 后显示编辑表单。由于每个博客条目都有自己的 URL,所以点击链接或直接输入 URL,就可以用 HTTP 命 令读取、修改或删除内容。
简而言之,REST 可以:
用 TCP/IP 命名标准命名 Web 上的资源
用 HTTP 查询和操纵这些资源
使用基于文本的标准消息格式(例如 XML 或 HTML)来构造数据
Ruby on Rails 用 REST 对 Web 服务提供了优秀的支持。
Action Web Services 概述
Rails 用叫做 Action Web Services 的模块实现 Web 服务。许多开发框架鼓励视图和 Web 服务使用 独立的控制器。这个策略可以维护控制器之间的风格一致。问题是针对所服务的每种内容,都需要一个新 控制器。例如,Ajax 用户界面要求从控制器取得到 JavaScript 的远程 XML 调用。
不必为 Web 服务专门分配一个控制器,使用 Rails,可以通用地用同一个控制器向基于 HTML 的视图 、基于 XML 的 Web 服务和基于 XML 的 JavaScript 组件提供内容。理解 Action Web Services 的最好 方式就是在工作应用程序的环境下查看它的实际作用。
请用自己选择的数据库管理器创建一个叫做 service_development 的数据库。接下来,用以下命令创 建 Rails 项目和模型:
> rails service
> script/generate model Person
在生成模型之后,就有了一个叫做 db/migrate/001_create_people.rb 的迁移。请把这个迁移编辑成 像清单 1 一样:
清单 1. people 表的迁移
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :first_name, :string, :limit => 40
t.column :last_name, :string, :limit => 40
t.column :email, :string, :limit => 40
t.column :phone, :string, :limit => 15
end
end
def self.down
drop_table :people
end
end