1. 概述
Roy Fielding 博士(见个人主页)是IETF 发布的HTTP 和URI 协议的主要设计者。HTTP 和URI 是两个最为重要的Web 基础技术架构协议,因此Fielding 博士可谓是Web 架构的奠基者之一。
除了学术上的卓越成就之外,Fielding 博士还参与过很多开源软件的设计和开发工作。他是libwww-perl(世界上最早的HTTP 开发库之一)的开发者,曾经负责Apache HTTP 服务器中与HTTP、URI 协议相关部分代码的开发。Fielding 博士还指导过很多其他团队在HTTP 客户端和服务器端软件方面的开发工作。
HTTP/1.1 协议(RFC 2616)于 1999 年发布,加上于 1998 年发布的 URI 协议(RFC 2396),至此 Web 的基础技术架构已经完全确立。为了向世人详细说明 Web 基础技术架构背后的设计原则,Fielding 在 2000 年撰写了自己的著名博士学位论文《Architectural Styles and the Design of Network-based Software Architectures》。这篇论文的中文版名为《架构风格与基于网络的软件架构设计》,可以从 InfoQ 中文站上下载:
这篇论文很不容易读懂,作为论文中文版的译者,笔者试图在这篇导读中为读者梳理出一个阅读的脉络。不过笔者还是希望读者能克服困难,亲自去读一下这篇论文,因为这篇论文实在是太精彩了。《论语》有很多评注版本,但是读者最好还是自己亲自读一下《论语》原作,免得上了朱熹之流歪嘴和尚的当。下面我们进入正题。
2. 论文导读
Fielding 博士论文一共包括了绪论和 6 章正文的内容。绪论的内容就是对 6 章正文的总结,不需要多说,以下分别对 6 章正文的每一章进行导读。
2.1 第 1 章——软件架构
在第 1 章中,Fielding 重新定义了一套研究软件架构的术语,讨论了每个术语定义的由来,并且将这些术语与相关研究进行比较。Fielding 还对其他软件架构研究者的一些相关研究进行了点评。
这些软件架构术语包括:软件架构、架构元素、组件、连接器、数据、配置、架构属性、架构风格。以下是 Fielding 重新给出的术语定义:
- 软件架构是一个软件系统在其操作的某个阶段的运行时(run-time)元素的抽象。一个系统可能由很多层抽象和很多个操作阶段组成,每层抽象和操作阶段都有自己的软件架构。
- 软件架构由一些架构元素(组件、连接器和数据)的配置来定义,这些元素之间的关系受到约束,以获得想要得到的一组架构属性。
- 组件是软件指令和内部状态的一个抽象单元,通过其接口提供对数据的转换能力。
- 连接器是对组件之间的通讯、协调或合作进行仲裁的一种抽象机制。
- 数据是组件通过连接器接收或发送的信息元素。
- 配置是在系统的运行期间组件、连接器和数据之间的架构关系的结构。
- 软件架构的架构属性集合包括了对组件、连接器和数据的选择和排列所导致的所有属性。架构属性是由架构中的一组约束所导致的。
- 架构风格是一组相互协作的架构约束,这些约束限制了架构元素的角色和功能,以及在任何一个遵循该风格的架构中允许存在的元素之间的关系。
Fielding 在将自己的术语定义与相关研究进行比较的过程中,对于一些相关研究提出了批评。例如:
“一些相关的研究完全不关注软件在运行时的特性,而只关注软件静态的源代码中的结构特性。”
Fielding 将这些研究者的研究内容称作“软件结构”,他不认为这是严格意义上的“软件架构”。Fielding 明确指出软件架构是软件在运行时的特性,他说:
“我们将软件架构和源代码结构分离开来,是为了更好地关注软件的运行时特性,这些特性不依赖于一个特定的组件实现。因此,尽管架构的设计和源代码结构的设计关系密切,它们其实是分离的设计活动。”
关注软件在运行时的特性,是 Fielding 的软件架构研究方法与其他研究者明显的不同之处。在对架构元素定义的讨论中,Fielding 进一步解释了这个差别。按照他的说法,软件架构就好像是大楼的架构,而软件结构则好像是大楼的设计图纸。假如大楼的设计图纸丢失了,大楼并不会立即倒塌,因此不能将大楼的设计图纸看作是大楼的架构本身。同样地,不应该将画在纸面上的方框直线图(例如常见的 ER 图)看作是软件架构本身,那样会导致严重的纸上谈兵,即仅仅根据绘制在纸面上的方框直线图来研究软件架构,其实这些图形只代表了存在于软件源代码中的静态软件结构。
Fielding 批评说:
“在这个过程中,软件架构被简化为通常在大多数非形式化的架构图表中能够看到的东西:方框(组件)和直线(连接器)。数据元素和其他很多真实软件架构的动态方面都被忽略了。这样的一个模型是不足以描述基于网络的软件架构的,因为对于基于网络的应用而言,数据元素在系统中的位置和移动常常是系统行为唯一至关重要的决定因素。”
(译者注:在 UML 中,除了静态的类图之外,还有时序图、状态图、活动图等等来表现各种动态的行为。但是在 Fielding 的博士论文研究期间,UML 规范尚未完全稳定下来。)
在所有的软件架构研究者中,Fielding 首次提出了软件的架构风格这样一个非常重要的概念,并且将软件架构风格当作是“一种用来对架构进行分类和定义它们的公共特征的机制。”
对于国内的软件架构研究者来说,架构风格是一个全新的概念。国内的软件架构研究者很少有人从架构风格这样高的抽象层次来思考软件的架构设计,几乎全部都是针对某种特定的架构来讨论。那么架构风格与特定架构是一种什么关系呢?
简单来说,架构风格与特定架构相比是更高层次的抽象。做一个不是很恰当的类比:假如将架构风格看作面向对象设计中的接口,那么特定的架构就是接口的实现类。例如:“分布式对象”是一种架构风格,而 CORBA、DCOM、EJB、.NET Remoting 都是分布式对象这种架构风格的架构实例。虽然它们四者之间存在着很多差别,但是它们其实属于同一种架构风格。
一种架构风格是由一组架构约束组成的,当将这组架构约束应用于某种特定的架构时,会产生出一些架构属性。这些架构约束和架构属性正是判断某种架构风格是否适合于一种特定运行环境的关键。
在讨论了上述这些软件架构术语之后,Fielding 接下来讨论了模式和模式语言、架构视图,这两部分也很有趣。
在对模式和模式语言的讨论中,Fielding 指出:
“如同软件的架构风格一样,软件模式的研究也偏离了其在建筑架构中的起源。”
他追根溯源地剖析了建筑学中模式语言的创造者 Alexander 大师 (Jeffrey Charles Alexander) 发明模式语言的本意。Fielding 说:
“其实,Alexander 的模式概念的核心并非是对于重复出现的架构元素的排列,而是发生在一个空间内重复出现的事件(人类的活动和情绪)的模式。Alexander 还理解到:事件的模式不能脱离于发生这些事件的空间。”
架构视图代表的是对于特定架构的不同观察角度,Fielding 说:
“观察一种架构,除了可从系统中的多个架构及组成这些架构的多种架构风格的角度之外,还有可能从很多其他的角度来观察。Perry 和 Wolf 描述了三种重要的软件架构视图:处理、数据、连接。处理视图侧重于流过组件的数据流,以及组件之间连接的那些与数据相关的方面。数据视图侧重于处理的流程,而不是连接器。连接视图侧重于组件之间的关系和通信的状态。”
Fielding 在后面论文的第 5 章中,正是使用了这三种视图来描述 REST 架构风格。不过在第 5 章中,三种视图的名称略有变化,处理(processing)视图变成了过程(process)视图、连接(connection)视图变成了连接器(connector)视图。
在第 1 章剩余的内容中,Fielding 回顾了其他软件架构研究者的一些相关研究工作,并且对这些研究工作进行了点评。笔者注意到,这一部分并没有提到 UML 相关的研究工作。在笔者看来,尽管 UML 对于软件架构研究确实非常重要,但是 UML 其实只是一个沟通工具,UML 的图形本身并不能教会设计者如何设计软件的架构(会画 UML 图 != 会设计复杂的软件架构)。另外 UML 的研究工作和 Fielding 的研究工作是并行的,Fielding 可能并不了解 UML 同时取得的进展。
2.2 第 2 章——基于网络应用的架构
在第 2 章中,Fielding 提出了自己的对于不同软件架构进行评估和比较的方法,并且精确定义了与基于网络应用(network-based application)的架构相关的各种架构属性。
架构可以存在于软件系统的多个层次,电脑中的 BIOS、显示卡驱动程序、操作系统、应用软件都有自己的架构。一篇关于 Web 基础技术架构的论文不可能无所不包,有必要限定论文所讨论架构的范围。基于网络应用的架构就是这篇论文中所讨论架构的范围。在这一类应用软件中,组件之间的交互能够通过网络通信来实现。这类应用通常包括客户端和服务器端两部分,它们合在一起才构成了一个完整的应用。基于网络应用并不包括那些构成网络协议栈本身(例如 TCP/IP 协议栈)的软件,仅包括使用这些协议栈软件的应用软件。
Fielding 特别指出了“基于网络应用”与我们常见的“分布式应用”之间的区别:
“Tanenbaum 和 van Renesse 是这样来区分分布式系统和基于网络的系统的:分布式系统在用户看来像是普通的集中式系统,但是运行在多个独立的 CPU 之上。相反,基于网络的系统有能力跨越网络运转,但是这一点无需表达为对用户透明的方式。在某些情况下,还希望用户知道一个需要网络请求的动作和一个在本地系统就能满足的动作之间的差别,尤其是当使用网络意味着额外的处理成本的时候。本论文涵盖了基于网络的系统,并不仅限于那些对用户透明的系统。”
也就是说,可以将“分布式应用”看作是“基于网络应用”的子集。狭义的“分布式应用”需要确保对于用户的透明,而广义的“基于网络应用”则无须维持这种虚假的透明(让用户忽略本地调用和远程调用之间的巨大差别)。尽管如此,在笔者看来,除非做学术研究,普通 Web 开发者并不需要严格区分“基于网络应用”和“分布式应用”。Fielding 在博士论文中之所以使用了“基于网络应用”这个广义的新术语,是为了让评审团的成员不至于落入狭义的“分布式应用”的很多传统思维之中。
Fielding 提出了自己的对于不同软件架构进行评估和比较的方法:首先识别出架构中存在的架构约束,然后判断每个约束将会导致哪些架构属性,并且将累积的架构属性与那些应用所要求的架构属性进行比较。如果这些累积的架构属性符合应用的要求,则说明这种架构对于该应用来说是适合的。
我们在第 1 章中已经看到,按照 Fielding 的定义,架构风格正是由一组相互协作的架构约束组成的。在笔者看来,因为对于同一架构风格的架构实例而言,它们所具有的架构约束和由这些架构约束产生的架构属性往往是相同的。所以在很多情况下,若某种特定的架构不适合某个特定的应用,那么此种架构所属的架构风格中的其他架构也不适合该特定的应用。例如:如果 EJB 不适合用来连接 Web 应用的客户端和服务器端,那么同属分布式对象架构风格的.NET Remoting 同样也不适合。
Fielding 指出:可以使用架构属性作为对于不同软件架构进行评估和比较的依据。架构属性虽然还没有达到定量的层次,但是已经足以在定性的层次对于各种架构进行客观的评估和比较。
那么对于基于网络应用,我们应该关注哪些架构属性呢?Fielding 精确定义了以下这些重要的架构属性:
- 性能(Performance)
- 可伸缩性(Scalability)
- 简单性(Simplicity)
- 可修改性(Modifiability)
- 可见性(Visibility)
- 可移植性(Portability)
- 可靠性(Reliability)
其中,性能可以细分为网络性能(Network Performance)、用户感知的性能(User-perceived Performance)、网络效率(Network Efficiency)。可修改性可以细分为可进化性(Evolvability)、可扩展性(Extensibility)、可定制性(Customizability)、可配置性(Configurability)、可重用性(Reusability)。
对于基于网络应用,上述这些架构属性构成了评估和比较不同架构的度量标准。根据某种架构中识别出的架构约束对于这些架构属性的影响(正面或负面)来对这些架构加以评估,就可以得到客观和精确的结论,判断出某种架构是否适合某个特定的应用。
这是一种非常好的评估和比较软件架构优劣的方法。当然,每一种架构都有其适用场合,并不存在绝对的优劣之分。但是当针对某个特定的应用时,在充分考虑了应用运行环境的情况下,完全可以区分出哪些架构是适合的、哪些架构是不适合的。
作为一名软件架构师,长期以来笔者感到苦恼的是缺乏一种客观评估和比较各种架构的方法。很多不称职的架构师往往喜欢偷懒,不去做具体分析,结果是 design by buzzword 的盛行。大约 10 年前在 J2EE 开发领域最典型的例子就是言必称分布式,似乎分布式就是企业应用皇冠上的明珠,随便什么应用都要使用 EJB 来开发。结果导致了很高的项目失败率,最终交付应用的性能和健壮性都很糟糕。在可预见的未来,这种情况还会以其他形式一再重复下去。出现这种情况的主要原因就是:甚至是有多年开发经验的架构师也没有能力来对各种架构进行细致的比较,判断的依据往往是主观的经验(例如:因为我对 Web Service 很熟悉,所以这个 API 应该使用 Web Service 开发)。
Fielding 所提出的方法,是笔者迄今为止看到过的最清晰、可操作性最强的软件架构研究方法(没有之一)。这种研究方法不仅对于研究 Web 应用或者分布式应用的架构来说是重要的,对于研究任何类型的软件架构都很重要。Fielding 不仅是 Web 基础技术架构的奠基者之一,也可以称的上是研究软件架构的大师。我们在论文后面的章节还会看到更多的精彩内容。
2.3 第 3 章——基于网络的架构风格
在第 3 章中,Fielding 使用第 2 章中定义的架构属性作为度量标准,对很多种基于网络应用的架构风格进行了全面的评估和比较。
这一章中所讨论的架构风格种类很多,尽管如此,仍然无法包括所有可能的基于网络应用的架构风格,而仅仅是评估了一组有代表性的架构风格的样本,并且建造一个分类的框架,以便其他架构风格一旦被开发出来就能够被添加到这个分类中。
Fielding 在这一章中评估的架构风格分为以下几个大类:
- 数据流风格(Data-flow Styles)
- 复制风格(Replication Styles)
- 分层风格(Hierarchical Styles)
- 移动代码风格(Mobile Code Styles)
- 点对点风格(Peer-to-Peer Styles)
每一大类的架构风格又可以分为几个小类,
数据流风格(Data-flow Styles)包括了管道和过滤器(Pipe and Filter)、统一管道和过滤器(Uniform Pipe and Filter)。
复制风格(Replication Styles)包括了复制仓库(Replicated Repository)、缓存(Cache)。
分层风格(Hierarchical Styles)包括了客户 - 服务器(Client-Server)、分层系统(Layered System)和分层 - 客户 - 服务器(Layered-Client-Server)、客户 - 无状态 - 服务器(Client-Stateless-Server)、客户 - 缓存 - 无状态 - 服务器(Client-Cache-Stateless-Server)、分层 - 客户 - 缓存 - 无状态 - 服务器(Layered-Client-Cache-Stateless-Server)、远程会话(Remote Session)、远程数据访问(Remote Data Access)。
移动代码风格(Mobile Code Styles)包括了虚拟机(Virtual Machine)、远程求值(Remote Evaluation)、按需代码(Code on Demand)、分层 - 按需代码 - 客户 - 缓存 - 无状态 - 服务器(Layered-Code-on-Demand-Client-Cache-Stateless-Server)、移动代理(Mobile Agent)
点对点风格(Peer-to-Peer Styles)包括了基于事件的集成(Event-based Integration)、C2、分布式对象(Distributed Objects)、被代理的分布式对象(Brokered Distributed Objects)
Fielding 通过二维表格列举了这些架构风格对于各种架构属性的影响,并且对于每种架构风格的优缺点进行了点评。Fielding 在这里所采用的方法其实是一种倒推法,根据对基于网络应用所需要的架构属性的影响(正面或负面),来评估哪些架构风格更适合于基于网络应用。
详细的表格读者可以阅读论文原文,笔者在这里不再赘述。在付出了很大努力,做了大量比较之后,Fielding 非常坦诚地指出,他所做的这些比较仍然存在两个局限。第一个局限是这里的评估是特别为分布式超媒体的需求而量身定制的。这一点很容易理解,因为 Fielding 做这些研究的主要目的,是为 Web 系统找到一种最适合、最实用的架构风格。第二个局限是对于架构属性的分组。这种架构属性的分组方式(见第 2 章的定义)可能并不适合于软件架构的一些细微之处。例如简单性这个架构属性,有时候可以再细分为可理解性和可验证性,而且两者之间可能存在冲突。
Fielding 说:
“尽管如此,这些最初的调查和分类,对于任何可能解决这些局限的更进一步的分类来说,是一个必需的先决条件。”
在笔者看来,Fielding 所做的这些开创性工作是非常有价值的。前人种树,后人乘凉。笔者期待有软件架构的研究者,能够站在 Fielding 博士的肩上,在这个领域作出更多的贡献。
在第 3 章剩余的内容中,Fielding 回顾了其他软件架构研究者的相关研究工作,并且对这些研究工作进行了点评。
第 3 章是 Fielding 博士论文中非常出彩的一章,集中体现出了 Fielding 在软件架构研究领域的深厚功力。以前笔者曾经读过的一些软件架构方面的著作,但是感觉只是看到了一个个树木,论文的第 3 章让笔者看到了整片的森林,一下子豁然开朗。笔者在读这一章的时候感觉是大开眼界。以前读过的任何一本软件架构方面的著作中都没有这样清晰地比较过各种软件架构的影响。甚至有些作者还有意模糊某种架构的不利方面,而且从来不肯讲清楚某种架构所适用的运行环境,给读者造成的感觉就是:这是一种普遍适用的“银弹”架构。除了技术图书的作者,一些开发中间件产品(例如:应用服务器)的公司更是变本加厉。这样的例子不必一一列举,实在太多了。如果有更多的人能像 Fielding 博士这么诚实,世界是不是会变得好很多?
在以后的工作中,每当笔者感觉到某个应用似乎可以使用某种架构时,都会重新阅读 Fielding 博士论文的第 3 章,看看这种架构有没有已经被讨论过,有哪些优缺点。如果这种架构是一种尚未被讨论过的新架构,我就会按照 Fielding 所提出的研究方法和分类框架,画出二维表格,仔细分析这种架构对于各种架构属性将会造成的影响,从而判断这种架构是否真的适合于我所要建造的应用。这是笔者目前所知道可以遵循的最为可靠的研究方法,可以让我们尽可能远离 design by buzzword。
2.4 第 4 章——设计 Web 架构:问题与领悟
在第 4 章中,Fielding 首先总结出了 Web 系统的需求;然后讨论了随着 Web 的飞速发展,Web 系统面临的问题和挑战;最后提出了解决问题和应对挑战的方法。
Web 系统的需求是在设计适合于 Web 系统的架构风格之前要搞清楚的。Web 系统需要满足以下需求:
- 低门槛
- 可扩展性
- 分布式超媒体
- 互联网规模
低门槛:构成 Web 基础技术架构的技术必须简单易用,任何创作者都可以很容易地使用这些技术向 Web 加入自己创作的内容。
可扩展性:Web 基础技术架构应该足够灵活,足以应对各种可能的变化,有很强的进化能力,而不应该陷入已部署系统的局限中无法自拔。
分布式超媒体:因为分布式超媒体允许在远程某个地点存储表述和控制信息,Web 基础技术架构要支持使用分布式超媒体的大粒度交互。同时因为 Web 系统的信息源是跨越整个互联网分布的,这种架构必须使网络交互最小化(减少交互的请求 - 响应数量、缩短一个会话中所有交互数据传输的总时间)。
互联网规模实际上包括了两个需求:无法控制的可伸缩性和独立部署。
无法控制的可伸缩性:当面对整个互联网时,一个 Web 应用无法控制到来的并发访问量和请求的格式。很有可能遇到超出自己处理能力的并发访问量,因而造成拥塞。还有可能遇到恶意构造的请求,试图突破系统的安全防护。因此 Web 基础技术架构必须要对可伸缩性和安全性两方面提供更好的支持。
独立部署:整个 Web 系统中已经部署的旧的组件(即某种 Web 基础技术架构协议的实现)不应该妨碍新部署的组件使用自己的扩展功能。Web 基础技术架构作为一个整体,必须被设计为易于以一种增量的、迭代的方式来部署,因为强制以一种整齐划一的方式来部署是不可能的。
Fielding 接下来讨论了随着 Web 的飞速发展,Web 系统面临的问题和挑战。
Fielding 说:
“尽管为 Web 的成功而欢欣鼓舞,但 Internet 开发者社区开始担心 Web 使用的快速增长率,伴随早期 HTTP 的一些糟糕的网络特性,将会很快超越 Internet 基础设施的容量,并且导致全面的崩溃。Web 应用的交互性质的变化更加恶化了这种情况。尽管最初的协议是为单个的请求响应对(request-response pairs)而设计的,新的站点使用了越来越多的内嵌图片(in-line images)作为网页内容的一部分,这导致了不同的浏览交互模式(interaction profile)。已部署的架构在对可扩展性、共享缓存、中间组件的支持等方面存在着严重的局限,这使得开发解决增长率问题的特别解决方案非常困难。同时,软件市场中的商业竞争导致了新的提议和一些有时候与 Web 协议相矛盾的提议层出不穷。”
Fielding 指出,Web 基础技术架构的三个工作组(URI、HTTP、HTML)所面临的巨大挑战是:
“如何将一组新的功能引入到一个已经被广泛部署的架构中,以及如何确保新功能的引入不会对那些使 Web 成功的架构属性带来不利的甚至是毁灭性的影响。”
在第 4 章的剩余部分,Fielding 提出了解决问题和应对挑战的方法。这种方法基于以下的三个假设:
- 假设一:在 WWW 架构背后的设计基本原理能够通过一种由应用于 Web 架构中的元素之上的约束组成的架构风格来描述。
- 假设二:能够为 WWW 架构风格添加约束,从而获得更好地反映一个现代 Web 架构想要得到的属性的新的混合风格。
- 假设三:修改 Web 架构的提议能够与更新后的 WWW 架构风格进行比较和分析,以便在部署之前识别出存在的冲突。
Fielding 的解决方法的步骤为:
- 第一步:识别出那些负责产生想要得到的属性的一组存在于早期 Web 架构中的约束。
- 第二步:识别出在一个 Internet 规模的分布式超媒体系统中想要得到的属性,然后选择额外的会产生那些属性的架构风格,将它们与早期的 Web 约束相结合,形成一种新的、混合的现代 Web 架构的架构风格。
- 第三步:使用新的架构风格作为指导,我们能够对被提议的扩展与针对风格中的约束对 Web 架构所做的修改进行比较。存在冲突表明这个提议会违反一个或多个在 Web 背后的设计原则。
- 第四步:解决发现的严重冲突。使用更加有益于 Web 风格的设计来替代相同的功能,或者告知提议人将此功能实现为与 Web 并行运行的单独的架构。
Fielding 说:
“修订后的协议标准是根据新的架构风格的指导来编写的。最后,如同修订后的协议标准中定义的那样,更新后的 Web 架构通过参与到基础设施(infrastructure)和中间件软件(middleware software)的开发过程中来进行部署,它们组成了大多数的 Web 应用。”
在这里,Fielding 所说的修订后的协议标准,可以理解为他直接领导的 HTTP 和 URI 协议的修订版本,也可以理解为所有融入 Web 的新的协议标准。
从第 4 章中我们可以得知,负责设计 Web 基础技术架构的专家们也并非永远先知先觉,他们其实也是在 Web 系统部署了很多年,遇到了很多问题之后,才去重新识别 Web 系统所要求的这些需求,并且针对这些需求来改造 Web 的基础技术架构。幸运的是,他们所做的这些亡羊补牢的工作在新千年到来之前就已经完成了,否则恶化的趋势发展下去,真的很有可能导致 Web 系统的全面崩溃。
Fielding 博士论文中的前面 4 章,是读者阅读和理解这篇论文的难点。如果您能坚持按照顺序读完前面 4 章,那么理解后面 2 章内容的难度就小多了。前面 4 章的内容是在播种,后面 2 章的内容是在收获。这篇经典论文的结构很像是长江大河,层层推进,每一章与前面一章都有很强的衔接关系。因此笔者建议读者还是按照顺序读下来,不要因为只对 REST 感兴趣,跳过难度较大的前面 4 章,直接阅读第 5 章。那样的话,读者不仅会难以理解,得到的理解也会是很片面的。只知道果而不知道因,知其然而不知其所以然。想要完全理解 REST,就必须完全理解 Fielding 所创建的这一套研究软件架构的方法论。在笔者看来,这一套方法论才是这篇论文的最大贡献。西瓜和芝麻,哪个该取哪个该舍,一目了然。
2.5 第 5 章——表述性状态转移(REST)
在第 5 章中,Fielding 根据第 4 章所识别出的 Web 系统的需求,推导出了一种完全满足 Web 系统需求的架构风格——REST。
通常来说,推导出一种架构风格有两种方法:做加法和做减法。做加法就是先从一个最简单的架构风格开始,逐个添加识别出的架构约束;做减法就是先从一个非常复杂的架构风格开始,逐个去除不需要的架构约束。第一种方法强调创造性和想象力,而第二种方法则强调限制和对系统环境的理解。这两种方法都可以得到满足应用需求的架构风格,区别是实践的难度。我们都知道,为一个简单的设计添加新的元素,通常都要比为一个复杂的设计减少元素更容易。Fielding 推导 REST 架构风格的过程,采用的就是做加法的方式。
架构风格的推导从一个“空”风格开始。“空”风格就是没有任何架构约束的风格。笔者非常欣赏这种推导方法,因为它体现出了道家的“无中生有”、“一生二、二生三、三生万物”的思想。REST 架构风格推导的过程,让我们清晰地看到了 Fielding 博士这些 Web 基础技术架构奠基者的设计思路,看到了 Web 的基础技术架构如何在保持相对最简化设计的同时完美地满足了 Web 系统的需求。
为“空风格”添加的第一个架构约束是客户 - 服务器。添加这个约束是为了分离关注点。因为 Web 系统交互所使用的分布式超媒体的存储地和使用地大多数时候都是不同的,它们的分工和角色有明显的不同,有必要将它们区分为客户和服务器。服务器主要负责数据的存储,而客户端主要负责提供用户界面。对于 Web 系统来说,最重要的是这种关注点的分离允许组件独立地进化,从而支持跨多个域的互联网规模的需求。
添加的第二个架构约束是无状态。状态通常可以分成两类,特定于单个客户端的会话状态、由多个客户端共享的资源状态。在这里所说的无状态指的仅仅是会话状态。服务器只负责保存资源状态,保存会话状态是客户端的责任。Fielding 说:
“从客户到服务器的每个请求都必须包含理解该请求所必需的所有信息,不能利用任何存储在服务器上的上下文,会话状态因此要全部保存在客户端。”
这个架构约束对于设计具有高度可伸缩性的服务器来说是非常重要的,我们几乎可以将此看作一个铁律。只要读者希望服务器具有最大的可伸缩性,都应该将服务器设计为无状态的。无状态的服务器设计做负载均衡会非常容易,很容易通过添加新的服务器来支持更大的负载。
添加的第三个架构约束是缓存。缓存通过将数据搬移到距离其使用地更近的位置,提高了网络效率和性能,同时还减少了对于服务器不必要的访问,因此缓存提高了服务器的可伸缩性。添加这个架构约束也是为了满足互联网规模的高度可伸缩性需求。以 HTTP 这种 REST 风格的架构为例,HTTP 作为 Web 基础架构协议内建有很多对于缓存的支持。缓存可以出现在 HTTP 通信链中的很多地方:用户代理(例如浏览器)、代理服务器、网关(又叫反向代理服务器)、来源服务器等等。
添加的第四个架构约束是统一接口。客户与服务器之间的所有交互都应该使用统一的接口来完成。统一接口使得客户和服务器的实现代码完全解耦,因此它们可以独立进化。只要接口保持不变,就不会影响到对方和整个 Web 应用的功能。
按照 Fielding 的描述,REST 的统一接口由 4 个部分组成:资源的标识、通过表述对资源执行的操作、自描述的消息、以及作为应用状态引擎的超媒体(现在通常缩写为 HATEOAS)。以 HTTP 为例,资源的标识就是资源的 URI;资源的表述是资源在特定时刻状态的描述,可以通过在客户 - 服务器之间传递资源的表述,对资源执行某种操作;自描述的消息由一些标准的 HTTP 方法、可定制的 HTTP 头信息、可定制的 HTTP 响应代码组成;超媒体就是 HTML,可以使用 HTML 作为引擎,驱动应用状态的迁移。
添加的第五个架构约束是分层系统。REST 风格的网络交互不仅仅包括客户和服务器两个参与者,REST 风格的网络交互可以分成很多层,它们合在一起构成一个完整的通信链。以 HTTP 为例,HTTP 协议允许插入很多中间组件,位于两端的用户代理、来源服务器与这些中间组件合在一起构成了整个 HTTP 通信链。交互的参与者都是相对独立的组件,组件之间的交互通过内建在组件内部的连接器来完成,所有连接器之间的交互都使用 HTTP 协议定义的统一接口。用户代理和来源服务器的角色比较单纯,但是对于中间组件来说,它对于通信链中位于来源服务器方向的组件是客户端,而对于通信链中位于用户代理方向的组件是服务器端。所有的组件都只会与直接相邻的组件进行交互,就好像其他不相邻的组件不存在一样。一个组件完全不需要知道整个交互的拓扑结构,除了在通信活跃期间,甚至也不需要知道相邻组件的存在。HTTP 通信链中最常见的中间组件包括代理服务器、网关、防火墙等等。
Fielding 说:
“分层系统约束和统一接口约束相结合,导致了与统一管道和过滤器风格类似的架构属性。”
对于管道和过滤器的支持,是 REST 架构风格与其他架构风格相区别的一个重要特征。
为 REST 添加的第六个,也是最后一个架构约束是按需代码。按需代码说的是客户端可以从服务器端下载可执行代码,在客户端运行。浏览器从 Web 服务器上下载 JavaScript 脚本就是一个最典型的例子,另外还可以下载 Java Applet、Flash、Silverlight 等等可执行的代码。
在架构层面支持按需代码,简化了客户端应用的开发。允许在部署之后下载可执行代码也改善了系统的可扩展性。然而,这也降低了可见性,因为这些可执行代码所代表的语义对于中间组件来说是不可见的,因此中间组件难以对其做有效的缓存和安全审计。所以 Fielding 将按需代码设计为只是 REST 的一个可选的架构约束。
我们看到,构成 REST 架构风格的架构约束其实并不多。5 个必需的架构约束加上一个可选的架构约束,不多也不少,完美地满足了 Web 系统的需求。这很符合笔者所欣赏的设计哲学:好的设计并不是无法再添加新的部分,而是任何部分都无法再减少。得到满足应用需求的最简化设计应该是一个优秀软件架构师所追求的目标。
识别出满足 Web 系统需求的架构风格所应该具有的架构约束之后,Fielding 接下来定义了 REST 风格的架构有哪些组成元素。一个 REST 风格的架构,其组成元素包括数据、连接器、组件三大类。每一类架构的组成元素都包括了多个成员。
- 数据元素包括:资源、资源标识符、表述、表述元数据、资源元数据、控制数据
- 连接器元素包括:客户端、服务器端、缓存、解析器、隧道
- 组件元素包括:来源服务器、网关、代理服务器、用户代理
读者如果很熟悉 HTTP 协议的话,可以很容易地在 HTTP 协议中找到上述所有 REST 架构的组成元素。
Fielding 还定义了 REST 架构风格的三种视图,每一种视图从不同的角度来观察 REST。这三种视图是:
- 过程视图
- 连接器视图
- 数据视图
架构的组成元素定义了 REST 的组成部分,三种架构视图则描述了这些组成元素之间如何协作以形成一个完整的架构。
在对过程视图的描述中,Fielding 以一个复杂的 HTTP 交互网络中存在的 3 种交互展了 REST 风格的架构。
REST 风格的架构——HTTP 协议的过程视图
图中展示了 3 种并行的数据流处理过程。在 a 过程中用户代理通过一个本地代理服务器(有翻墙经验的读者对这个再熟悉不过了)和一个网关(例如常见的 Nginx 或者 Squid)来间接访问来源服务器。在 b 过程中用户代理直接访问来源服务器,没有任何中间组件。在 c 过程中,用户代理通过一个共享的代理服务器访问一个 WAIS 服务,共享代理服务器负责完成协议的转换工作。
这张过程视图非常好地反映出 REST 所具有的各种架构约束:客户 - 服务器、无状态、缓存、统一接口、分层系统、按需代码。读者还可以将这张过程视图与论文前面的 REST 架构风格的示意图相对照。
REST 架构风格
这两张架构图,一个抽象(在架构风格层次)、一个具体(在架构实例层次),但是却高度一致。聪明的读者看懂了这两张架构图,对于 HTTP/1.1 协议为何如此设计,想必已经了然于胸。
Fielding 说:
“REST 通过强制消息具有自描述性(请求之间的交互是无状态的、使用标准的方法和媒体类型来表达语义和交换信息、响应可以明确地表明其可缓存性)来支持中间组件的处理。”
这段话是说,HTTP 消息的语义应该保持对于中间组件的可见性。作为 Web 开发者而言,可以将消息语义理解为通过 GET/POST/PUT/DELETE 等标准 HTTP 方法对资源执行的 CRUD 操作。表达消息语义的信息不应该被埋藏在 HTTP 消息体之中。中间组件通常不会去尝试解析 HTTP 消息体以理解消息的语义,因为那样做是非常低效的。
连接器视图侧重于组件之间的通信机制。统一接口这个 REST 的主要的架构约束就是由连接器来实现的,连接器之间的交互必需通过统一的接口来完成。这个统一接口通常使用 HTTP 协议来实现,但是并不局限于 HTTP 协议。在 REST 的三大类架构组成元素中,连接器是直接与各种协议(HTTP 或其他通信协议,例如流程视图中 c 分支中的 WAIS 协议)打交道的部分。
正是因为 REST 架构风格支持统一接口,因此才有可能实现可重用的连接器。无状态、缓存、统一接口、分层系统 4 个架构约束彼此协作,使得基于 HTTP 协议能够实现大规模的分布式缓存,将负载分摊到跨网络的各个中间组件之上,从而极大提高了来源服务器的可伸缩性。
Fielding 说:
“为了获得连接器语义的单一的、通用的接口的好处,这一约束牺牲了其他架构的一些好处,例如像 WAIS 这样的相关性反馈协议(relevance feedback protocol)的有状态交互的好处。作为回报,通用的接口使得通过单个代理访问多个服务成为了可能。”
这段话是说,尽管 REST 风格的架构能够将 HTTP 协议之外的其他协议也纳入进来,但是对于这些其他协议的支持是有限的。举个较为常见的例子,很多开源项目的 FTP 服务器同时也支持通过 HTTP 协议来访问,但是仅限于支持可匿名访问的文件,无法支持需要登录才能访问的文件。因为 FTP 协议对于用户登录的支持是有状态的,而 REST 风格的架构必须是无状态的。
数据视图侧重于通信中所使用的数据格式的定义。Fielding 说:
“因为基于 REST 的架构主要通过转移资源的表述来进行通信,所以延迟会同时受到通信协议的设计和表述数据格式的设计两方面的影响。”
数据格式的定义会严重影响用户感知的性能,如果一种数据格式的定义允许增量地呈现,那么用户感知的性能要好的多。同时,如果对数据进行有效地缓存,也能够非常有效地改善应用的性能。
对于通信协议的设计,Fielding 举了一个微妙的“先响应后思考”的例子。尽管服务器端无法确定客户端究竟需要什么格式的内容,它还是在收到第一次请求之后就将最有可能的内容发送给客户端。这样做要比先进行一轮内容协商后再发送客户端所需要格式的内容性能更好。
Fielding 在对于数据视图的总结描述中这样说:
“因此,REST的模型应用是一个引擎,它通过检查和选择当前的表述集合中的状态迁移选项,从一个状态迁移到下一个状态。毫不奇怪,这与一个超媒体浏览器的用户接口完全匹配。然而,REST风格并不假设所有应用都是浏览器。事实上,通用的连接器接口对服务器隐藏了应用的细节,因此各种形式的用户代理都是等价的,无论是为一个索引服务执行信息获取任务的自动化机器人,还是查找匹配特定查询标准的数据的私人代理,或者是忙于巡视破损的引用或被修改的内容的维护爬虫。”
这一段话对于理解 REST 架构的本质特征之一的 HATEOAS(Hypermedia as the Engine of Application State),以及 REST 为何称作“表述性状态转移”是至关重要的。
最后,Fielding 回顾了对于 Web 架构的一些相关的研究工作,指出了这些研究工作中的局限,以及 REST 与它们的区别。
其中比较有趣的一段是解释了为何 HTTP 被设计为一种无状态的拉模型,而不是像 EBI(基于事件的集成)风格那样的推模型。这是因为推模型需要服务器端保存每个客户端的状态信息,但是互联网规模使得我们不可能实现一种无节制的推模型,那样可伸缩性就太差了。
到了这一章,REST 架构风格的面貌已经完全确定了,接下来的是将 REST 的设计原则付诸实践。Fielding 和由他所领导的协议设计团队正是在 REST 设计原则的指导下,设计出了 HTTP/1.1 协议。HTTP/1.1 协议自 1999 年 8 月正式发布之后,取得了辉煌的成功,促进了 Web 以几何级数的速度飞速发展,影响到了地球上的每一个人类社区。
2.6 第 6 章——经验与评估
在第 6 章中,Fielding 回顾了使用 REST 架构风格的设计原则指导 HTTP 和 URI 协议的设计,以及指导 libwww-perl、Apache httpd 等 HTTP 协议的实现的过程中所积累的经验和教训。
这一章是整篇博士论文的总结,其中的内容非常丰富。与前面 5 章的内容不同的是,这一章的内容最为具体,而且大多数内容都是与普通 Web 开发者密切相关的。读者在阅读论文过程中遗留的大多数疑问,都可以在本章中找到答案。为了协助读者更好地理解本章的内容,笔者在本文中花费了更多的篇幅。
REST 架构风格其实并不是什么新的东西,从 Web 的历史来说,甚至可以说相当古老。Fielding 和他的协议团队自从 1994 年以来就在内部使用 REST 来指导 Web 基础技术架构协议的设计。不过 Fielding 直到 2000 年才通过这篇博士论文向世人揭示出 REST 的全貌。而 REST 真正流行开来,还是要等到 Ajax 流行之后,特别是在出现了一些成熟的服务器端 REST 开发框架之后,使得 REST 这种抽象的架构风格变成了我们能够日常实践的开发架构。
Fielding 说:
“这个名称“表述性状态转移”是有意唤起人们对于一个良好设计的 Web 应用如何运转的印象:一个由网页组成的网络(一个虚拟状态机),用户通过选择链接(状态迁移)在应用中前进,导致下一个页面(代表应用的下一个状态)被转移给用户,并且呈现给他们,以便他们来使用。”
这一段话与上一章的“因此,REST 的模型应用是一个引擎”那段话相呼应,对于读者理解 REST 架构的本质非常重要。
Fielding 说:
“重要的是 REST 确实能够完全捕获一个分布式超媒体系统的那些被认为是 Web 的行为和性能需求的核心的方面,这样在这个模型中对行为进行优化,将能够导致在已部署的 Web 架构中得到最适宜的行为。”
对于 Web 基础架构协议的设计来说,REST 最大的价值是可以用来判断收到的提议中哪些是对于某协议的扩展,哪些是与 Web 的架构风格相一致的,哪些是与 Web 的架构风格相背离的。尽量避免引入与 Web 的架构风格相背离的协议扩展,否则这一类的扩展最终将导致 Web 的崩溃。
在设计 URI 协议的过程中,Fielding 重新定义了资源的含义,将资源与其最初缺乏灵活的简单文档映射脱钩。资源实际上可以代表服务器端任意可命名的抽象概念,而不仅仅代表一个文档。
Fielding 说:
“在 REST 中,对于“资源”的定义基于一个简单的前提:标识符的改变应该尽可能很少发生。因为 Web 使用内嵌的标识符,而不是链接服务器,创作者需要一个标识符,这个标识符能够紧密地匹配他们想要通过一个超媒体引用来表达的语义,允许这个引用保持静态,甚至是在访问该引用所获得的结果可能会随时间而变化的情况下。REST 达到了这个目标,通过将一个资源定义为创作者想要标识的语义,而不是对应于创建这个引用时的那些语义的值。然后留给创作者来保证所选择的这个标识符确实真正标识出了他所想要表达的语义。”
对于资源的操作是通过在客户端和服务器端之间转移(传递)资源的表述来进行的,Fielding 将这种间接的操作方式称作“操作影子”。
Fielding 说:
“将“资源”定义为一个 URI 标识了一个概念,而不是标识了一个文档,这给我们带来了另一个问题:一个用户如何访问、操作或转移一个概念,使得他们在选择了一个超文本链接后能够得到一些有用的东西。REST 通过定义在被标识的资源的“表述”之上执行的操作,而不是在资源本身之上执行的操作回答了这个问题。一个来源服务器维护着从资源的标识符到每个资源相对应的表述集合的映射,因此可以通过由资源标识符定义的通用接口转移资源的表述来操作一个资源。”
Fielding 还说:
“REST 对于资源的定义来源于 Web 的核心需求:独立创作跨多个可信任域的互相连接的超文本。强制接口的定义与接口的需求相匹配会使得协议似乎含糊不清,但这仅仅是因为被操作的接口仅仅是一个接口,而不是一个实现。这些协议是与一个应用动作的意图密切相关的,但是接口背后的机制必须要确定该意图如何来影响底层实现中资源到表述的映射。
这里所隐藏的信息是关键的软件工程原则之一,也就是 REST 使用统一接口的动机。因为客户端被限制为只能对资源的表述执行操作,而不是直接访问资源的实现,因此资源的实现可以以任何命名权威所希望的形式来建造,而不会影响到使用资源的表述的客户端。此外,如果当资源被访问时,存在着资源的多个表述,可以使用一个内容选择算法来动态地选择一个最适合客户端能力的表述。当然,其缺点就是对资源进行远程创作不像对文件进行远程创作那么直接。
这里所隐藏的信息是关键的软件工程原则之一,也就是 REST 使用统一接口的动机。因为客户端被限制为只能对资源的表述执行操作,而不是直接访问资源的实现,因此资源的实现可以以任何命名权威所希望的形式来建造,而不会影响到使用资源的表述的客户端。”
如果读者对面向对象设计(OOD)相当熟悉,上面这段话看起来似曾相识。资源建模的过程,与面向对象建模很相似。资源可以看作是服务器所暴露出来的接口,与 OOD 中的接口类似,资源也是一个用来做抽象的工具。与类或接口不同的是,因为存在统一接口的约束,所以在每个资源之上只能定义有限的几个标准操作。
将资源从最初很具体的文档映射转变为一种用来做抽象的工具之后,带来的一个问题是远程创作不再像以前那么直接了。原先直接映射到文档时,修改一个资源是很直接的,现在则需要通过操作资源的表述来简介进行。资源的表述携带了资源在某个特定时刻的状态信息,客户端可以通过改变获取到的资源表述,加以修改,然后将修改过的资源表述提交到服务器端,来修改资源的状态。
Fielding 说:
“资源是一种概念上的映射——服务器接收到标识符(标识这个映射),将它应用于当前的映射实现(mapping implementation,通常是与特定集合相关的树的深度遍历和 / 或哈希表的组合)上,以发现当前负责处理该资源的处理器实现,然后处理器实现基于请求的内容选择适当的动作 + 响应。所有这些特定于实现的问题都隐藏在 Web 接口之后,它们的性质无法由仅能够通过 Web 接口访问资源的客户端来作出假设。”
按照 Fielding 的设想,资源所代表的语义以及它的 URI 应该是保持长期稳定的,但是除此以外,资源的实现是可以变化的。将资源的实现封装在它的 URI 所代表的抽象概念之后,这与面向对象设计中通过接口来封装变化的思想是一致的。
这是一个关于 Web 架构设计的极端重要的思想,但是,可惜的是,10 多年过去了,深刻理解这个思想的 Web 开发者仍然很少。如果 Web 开发者都理解了这个思想,那么 Web 上面缺乏维护的超链接和“404 Not Found”错误就会少的多。
Fielding 继续说:
“对于设置资源标识符和用表述组装那些资源的动作而言,语义是一个副产品。服务器或客户端软件绝对不需要知道或理解 URI 的含义——它们仅仅扮演一个管道,通过这个管道,资源的创建者(一个作为命名权威的人)能够将表述与通过 URI 标识的语义关联起来。换句话说,在服务器端没有资源,仅仅是通过由资源定义的抽象接口提供答案的机制。这看起来似乎很奇怪,但是这正是使得 Web 跨越如此众多的不同实现的关键所在。”
正是因为存在这样的设计,才极大简化了浏览器和 Web 服务器(例如 Apache httpd 或 Nginx)中与协议相关部分的开发。
Fielding 说:
“按照用来组成已完成产品的组件来定义事物,是每一个工程师的天性。Web 却并非是以这种方式运作的。基于在一个应用动作期间每个组件的角色,Web 架构由在组件之间的通信模型之上的约束组成。这防止了组件对于每件事物作出超越资源抽象的假设,因此隐藏了抽象接口任何一端的真实的机制。”
对于这段话读者需要细心体会,有助于理解 Web 与其他分布式系统之间的区别。对于资源抽象和统一接口的支持,是 Web 与其他分布式系统的本质区别。笔者在读到这段话时感到,Web 系统的基础技术架构实在是非常精妙的设计!
接下来 Fielding 指出了在使用 URI 的 Web 应用中经常出现的与 REST 不匹配(不相容)的一些地方。
一个例子是 URI 中包括标识当前用户的信息,如果服务器通过 URI 重写而不是 cookie 来跟踪会话信息,就会发生这种情况。Fielding 说:
“通过记录用户的动作来跟踪他们的行为……由于违反了 REST 的约束,这些系统会导致共享缓存变得效率低下,这降低了服务器的可伸缩性,并且在一个用户与其他用户共享那些引用时会导致不希望的结果。”
另一个例子是客户端将 Web 服务器简单地看作一个分布式文件系统,即类似于 NFS 这样的东西,然后对 Web 服务器做镜象。完全静态的 Web 服务器,这样做镜象没问题,但是对于一个动态的 Web 服务器,是不能这样做镜象的。因为每个 URI 所代表的资源是抽象的,做镜像得到的只是资源在某个特定时刻的影子,而不是资源本身。
这些问题的根源还是在于 Web 开发者并没有深入理解 REST,不理解 HTTP 和 URI 的设计意图。REST 的设计原则虽然融入了 HTTP 和 URI 协议的设计之中,但是很难强制这些协议的使用者必须按照 REST 的设计原则来设计开发 Web 应用。当然,随着 REST 的日渐普及和深入人心,这些问题会有所改善。
Fielding 总结了在创作 HTTP 协议的过程中应用 REST 所获得的经验。
最初的 HTTP/1.0 协议的一些方面不是很适合 REST 的要求,在设计 HTTP/1.1 的过程中,有必要对这些方面加以隔离,使得它们不至于影响新协议的部署。HTTP1.1 为了这个目的,引入了协议版本控制。此外协议版本控制还可以用来增大未来与 REST 的要求不兼容的协议扩展部署的难度。
Fielding 说:
“这些规则(指协议版本控制)的存在协助了多个协议修订版的部署,并且防止了 HTTP 架构师遗忘掉协议的部署是其设计的一个重要方面。这些规则是通过使得对于协议兼容的改变和不兼容的改变容易区别来做到这一点的。兼容的改变很容易部署,对于协议接受能力的差异能够在协议流(protocol stream)中进行沟通。不兼容的改变难以部署,因为它们在协议流能够开展通信之前,必须做一些工作来确定协议的接受能力。”
设想一下,假如未来的 HTTP/2.0 协议与 REST 的要求不兼容,那么它不可能平滑地部署,已部署的应用也无法平滑地迁移到 HTTP/2.0 上面。
HTTP/1.1 还扩大了响应状态代码的范围,定义了一个通用的规则来解释新的响应状态代码,这样就能部署新的响应状态代码而不会严重损害老的客户端应用。
HTTP/1.1 还增加了很多自描述的消息,即 HTTP 头信息字段。前面我们已经知道,自描述的消息是 HTTP 统一接口的一部分。其中与 Web 开发者关系最密切的是缓存控制和内容协商两部分的头信息。
缓存控制方面,HTTP/1.1 添加了 Cache-Control、Age、Etag 和 Vary 几个头信息字段。
内容协商分为抢先式协商、反作用式协商和透明式协商三种。HTTP/1.1 同时支持这三种内容协商方式。对于 Web 开发者来说,反作用式协商用的最多,因为它能得到更好的性能。反作用式协商就是 Fielding 在第 5 章中解释数据视图时,举例提到的那种“先响应后思考”的工作方式。
HTTP/1.1 还通过支持持久连接和直写式缓存,改善了协议的性能。我们现在使用浏览器访问 HTTP 服务器,默认情况下使用的都是 HTTP/1.1 的持久连接。
接下来 Fielding 指出了在 HTTP 协议早期版本中识别出的与 REST 不匹配的一些地方。包括区分非权威的响应码、Cookie 的问题等等。
Fielding 说:
“Cookie 也违反了 REST,因为它们允许数据在没有充分表明其语义的情况下进行传递,这样就成为了一个安全和隐私方面的关注点。结合使用 Cookie 和 Referer[sic] 头信息字段,有可能当用户在多个站点之间浏览时,对他进行跟踪。”
Cookie 这个东西可谓非常古老,在 HTTP/1.0 协议中就已经支持 Cookie 了。Fielding 在 13 年前的博士论文中特别指出了滥用 Cookie 的危害,可惜很少有 Web 开发者考虑这个方面。今年央视报道过的一些 Cookie 跟踪公司为何会出现引起公众大范围的恐慌,现在读者理解了吧?当然这些报导是夸大其辞了,Cookie 跟踪没那么万能,不过危害确实也不小。
Fielding 同时给出了替代 Cookie 更好的实现方式,读者可以仔细看看。
此外,如果想要扩展 HTTP 头信息字段的含义,需要注意:
“HTTP 头信息字段名称仅当它们所包含的信息对于正确理解消息并非是必需的时候,才能够被任意扩展。”
通常情况下,不应该改变那些标准 HTTP/1.1 头信息字段的语义,企图用这些头信息字段实现某种非标准的功能。Web 开发者可以使用自定义的 HTTP 头信息字段,而且这些自定义的 HTTP 头信息字段不应该影响 HTTP 通信链的参与方对于 HTTP 消息的正确理解。
除了创作这些 Web 架构基础协议,Fielding 还亲自参与了这些协议的一些软件实现的开发工作。Fielding 简单描述了他在 libwww-perl、Apache httpd 等项目中的工作,这些项目的巨大成功,充分验证了 REST 的有效性。
最后,Fielding 总结了在所有这些实践活动中所获得的教训。
在“基于网络的 API 的优势”中,Fielding 指出了这种新的设计方法相对于传统的“基于库的 API”的优势:
Fielding 说:
“一个基于网络的 API 对于应用的交互而言,是一种包含有已定义语义的在线(on-the-wire)的语法。一个基于网络的 API 没有在应用的代码上强加任何除了读或写网络的需求之外的限制,但是确实在能够有效地跨接口进行通信的一组语义上添加了限制。其有利的方面就是,性能仅仅受限于协议的设计,而不是受限于该设计的特殊实现。
一个基于库的 API 为程序员做的工作要多得多,但是通过做这些工作,也带来了大量更多的复杂性,并且其负担超出了任何单个系统必须承受的限度,这种方法在一个不同种类的网络中的可移植性较差,而且总是会导致首先选择通用性,而不是性能。作为一个副作用,它也导致了在开发过程中产生惰性(为任何事情都去责备 API 代码),而不去努力解决其他通信参与方不合作的行为。”
Fielding 在详细对比了 Web 的架构与 CORBA 这样的中间件系统之间的差别之后说:
“为何这些差别是很重要的?因为它将一个网络中间组件能够成为有效的代理(effective agent)的系统,和一个网络中间组件最多只能成为路由器的系统区分了开来。”
理解这一点很重要,在 HTTP 通信链中很容易透明地加入一些新的中间组件,而在某种分布式对象风格的架构(例如 CORBA、DCOM、EJB、.NET Remoting)的通信链中很难做到。分布式对象风格的架构都是“基于库的 API”,并不是说它们使用了网络,就自动成为了“基于网络的 API”。
Fielding 还说:
“这种形式的区分也可以在将消息解释为一个单元(a unit)或者解释为一个流(a stream)中看到。HTTP 允许接收者或发送者来自行决定。CORBA 的 IDL 甚至(仍然)不允许使用流,即使当它确实得到了扩展以支持流之后,通信的双方仍然被绑定在相同的 API 上(译者注:即 CORBA 的基于库的 API),而不是能够自由地使用最适合于它们的应用类型的东西。”
在电信行业曾经很流行的 CORBA 为什么逐渐遭到废弃,变成了一种遗留的架构?很多人会说那是因为它的复杂性。那么它为什么一定要设计的这么复杂呢?几乎没有人能够回答。很多年以来,国内的架构师看到软件大厂所鼓吹的各种神奇架构你方唱罢我登场,疲于跟随。Fielding 为我们清晰地指出了这些遗留架构中存在的本质问题。
在“REST 不是一种 RPC”中,Fielding 明确指出了 REST 架构风格与 RPC 架构风格之间的区别。Fielding 说:
“将 HTTP 和 RPC 区分开的并不是语法,甚至也不是使用一个流作为参数所获得的不同的特性,尽管它帮助解释了为何现有的 RPC 机制对于 Web 来说是不可用的。使得 HTTP 与 RPC 存在重大不同的是:请求是使用具有标准语义的通用的接口定向到资源的,这些语义能够被中间组件和提供服务的来源机器进行解释。结果是使得一个应用支持分层的转换(layers of transformation)和间接层(indirection),并且独立于消息的来源,这对于一个互联网规模、多个组织、无法控制的可伸缩性的信息系统来说,是非常有用的。与之相比较,RPC 的机制是根据语言的 API(language API)来定义的,而不是根据基于网络的应用来定义的。”
也就是说,REST 与 RPC 的根本区别在于是否支持统一接口。RPC 没有使用统一接口来完成交互,因此 RPC 风格的 API 在可伸缩性方面要比 REST 风格的 API 差的多。
同时,Fielding 还指出了“HTTP 不是一种传输协议”。很多人把 HTTP 看作一种能够穿越防火墙、简单易用的传输协议,仅仅是因为 HTTP 的消息体可以包含任意的内容。
“REST 不是一种 RPC”和“HTTP 不是一种传输协议”是关于 HTTP 和 REST 的最大的两个误解,而且这两个误解通常会同时出现,其影响范围非常广泛。
虽然有很多人基于以 REST 架构风格设计的 HTTP/URI 协议来实现一种 RPC 风格的 API,仅仅将 HTTP 当作一种传输协议,但是必须明确指出,以这样的方式使用 HTTP 协议是错误的和低效的,在可伸缩性方面会付出很大的代价。SOAP 就是这两个误解的一个典型的例子,关于 SOAP 的问题,笔者将会在后续的文章中详细探讨。
最后,Fielding 还讨论了 REST 对于媒体类型设计的影响。
在这一部分中,我们需要注意的是 REST 并非对于所有的媒体类型一视同仁,事实上,REST 会淘汰掉那些不适合其设计原则要求的媒体类型。目前 HTML 是最适合 REST 要求的媒体类型,REST 也是特别针对 HTML 优化过的。使用其他的媒体类型,特别是一些自定义的二进制格式,与 HTTP 这种 REST 风格的架构配合工作,不会得到理想的结果。
Fielding 举了一个 JavaScritp 最终战胜 Java Applet 的例子。Fielding 说:
“JavaScript 更好地适合于 Web 技术的开发模型。它具有低得多的门槛,既是因为它作为一种语言的总体复杂性比较小,也是因为一个新手程序员将他最初的工作代码整合起来需要花费的努力比较小。JavaScript 对于交互的可见性所产生的影响也比较少。独立的组织能够按照与复制 HTML 相同的方式来阅读、验证和复制 JavaScript 的源代码。与之相反,Java 是作为二进制包下载的——用户因此必需信任 Java 执行环境中的安全限制。同样,Java 拥有很多更多的功能,允许这些功能存在于一个安全环境中被认为是很可疑的,包括将 RMI 请求发送到来源服务器的能力。RMI 并不支持中间组件的可见性。
也许两者最重要的区别是,JavaScript 仅仅导致了很少的用户可觉察的延迟。JavaScript 通常作为主要表述的一部分来下载,然而 Java applet 要求一个独立的请求。Java 代码一旦被转换为字节代码的格式,要比通常的 JavaScript 代码大得多。最后一点是,当 HTML 页面的其余部分正在被下载时 JavaScript 就能够执行,Java 则要求包含类文件的完整的包被下载并且安装之后,应用才能够开始执行,因此 Java 并不支持增量的呈现。”
简而言之,JavaScript 战胜 Java Applet 是因为它更符合 REST 的要求,在 REST 的严格挑选之下,它成为了最终的胜利者。Flash 比 Java Applet 好很多,但是 Fielding 说它的问题也存在,所以笔者对于 Flash 的未来并不是很看好。随着移动互联网的普及,Flash 的下滑趋势目前已经很明显了。
到了这里,笔者关于 Fielding 博士论文的导读就结束了,这实在是一次艰难的长途旅行。:) 希望通过这篇导读的协助,使得读者能够更容易读懂 Fielding 博士这篇 Web 历史上的经典论文,深刻理解 Web 基础技术架构的设计原则,提升自己的架构设计功力。在本文的最后,让我们一起对 Tim Berners-Lee、Roy Fielding 等 Web 基础技术架构的奠基者们表示衷心的感谢和崇高的敬意。没有他们开创性的伟大工作,就没有 Web 的今天,Web 的世界也不会像现在这样既和谐而又繁荣。
感谢马国耀对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论 1 条评论