介绍Web基础架构设计原则的经典论文《架构风格与基于网络的软件架构设计》导读

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中文站投稿或者参与内容翻译工作,请邮件至[email protected]。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

你可能感兴趣的:(webservice)