加入收藏 | 设为首页 | 关于我们尊敬的先生/女士,您好,欢迎光临论文期刊网!

 推荐期刊

 联系我们

点击这里给我发消息 尹编辑 123456 点击这里给我发消息 尹编辑 123456 投稿邮箱:[email protected]
联系电话:123456
免费电话:123456
 论文欣赏
基础架构设计原则的经典论文《架构风格与基于网络的软件设计》导
发布时间:2019-03-14 点击: 发布:中国论文期刊网

  Roy Fielding 博士(见个人主页)是 IETF 发布的 HTTP 和 URI 协议的主要设计者。HTTP 和 URI 是两个最为重要的 Web 基础技术架构协议,因此 Fielding 博士可谓是 Web 架构的奠基者之一。

  除了学术上的卓越成就之外,Fielding 博士还参与过很多开源软件的设计和开发工作。他是 lib(世界上最早的 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 中文站上下载:

  这篇论文很不容易读懂,作为论文中文版的译者,笔者试图在这篇导读中为读者梳理出一个阅读的脉络。不过笔者还是希望读者能克服困难,亲自去读一下这篇论文,因为这篇论文实在是太精彩了。《论语》有很多评注版本,但是读者最好还是自己亲自读一下《论语》原作,免得上了朱熹之流歪嘴和尚的当。下面我们进入正题。

  连接器、数据、配置、架构属性、架构风格。以下是 Fielding 重新给出的术语定义:软件架构是一个软件系统在其操作的某个阶段的运行时(run-time)元素的抽象。一个系统可能由很多层抽象和很多个操作阶段组成,每层抽象和操作阶段都有自己的软件架构。软件架构由一些

  (组件、连接器和数据)的配置来定义,这些元素之间的关系受到约束,以获得想要得到的一组架构属性。

  集合包括了对组件、连接器和数据的选择和排列所导致的所有属性。架构属性是由架构中的一组约束所导致的。

  是一组相互协作的架构约束,这些约束限制了架构元素的角色和功能,以及在任何一个遵循该风格的架构中允许存在的元素之间的关系。

  Fielding 在将自己的术语定义与相关研究进行比较的过程中,对于一些相关研究提出了批评。例如:

  “一些相关的研究完全不关注软件在运行时的特性,而只关注软件静态的源代码中的结构特性。”

  Fielding 将这些研究者的研究内容称作“软件结构”,他不认为这是严格意义上的“软件架构”。Fielding 明确指出软件架构是软件在

  关注软件在运行时的特性,是 Fielding 的软件架构研究方法与其他研究者明显的不同之处。在对架构元素定义的讨论中,Fielding 进一步解释了这个差别。按照他的说法,软件架构就好像是大楼的架构,而软件结构则好像是大楼的设计图纸。假如大楼的设计图纸丢失了,大楼并不会立即倒塌,因此不能将大楼的设计图纸看作是大楼的架构本身。同样地,不应该将画在纸面上的方框直线图(例如常见的 ER 图)看作是软件架构本身,那样会导致严重的纸上谈兵,即仅仅根据绘制在纸面上的方框直线图来研究软件架构,其实这些图形只代表了存在于软件源代码中的静态软件结构。

  “在这个过程中,软件架构被简化为通常在大多数非形式化的架构图表中能够看到的东西:方框(组件)和直线(连接器)。数据元素和其他很多真实软件架构的动态方面都被忽略了。这样的一个模型是不足以描述基于网络的软件架构的,因为对于基于网络的应用而言,数据元素在系统中的位置和移动常常是系统行为唯一至关重要的决定因素。”

  这样一个非常重要的概念,并且将软件架构风格当作是“一种用来对架构进行分类和定义它们的公共特征的机制。”

  简单来说,架构风格与特定架构相比是更高层次的抽象。做一个不是很恰当的类比:假如将架构风格看作面向对象设计中的接口,那么特定的架构就是接口的实现类。例如:“分布式对象”是一种架构风格,而 CORBA、DCOM、EJB、Remoting 都是分布式对象这种架构风格的架构实例。虽然它们四者之间存在着很多差别,但是它们其实属于同一种架构风格。

  “如同软件的架构风格一样,软件模式的研究也偏离了其在建筑架构中的起源。”

  “观察一种架构,除了可从系统中的多个架构及组成这些架构的多种架构风格的角度之外,还有可能从很多其他的角度来观察。Perry 和 Wolf 描述了三种重要的软件架构视图:处理、数据、连接。处理视图侧重于流过组件的数据流,以及组件之间连接的那些与数据相关的方面。数据视图侧重于处理的流程,而不是连接器。连接视图侧重于组件之间的关系和通信的状态。”

  在第 1 章剩余的内容中,Fielding 回顾了其他软件架构研究者的一些相关研究工作,并且对这些研究工作进行了点评。笔者注意到,这一部分并没有提到 UML 相关的研究工作。在笔者看来,尽管 UML 对于软件架构研究确实非常重要,但是 UML 其实只是一个沟通工具,UML 的图形本身并不能教会设计者如何设计软件的架构(会画 UML 图 != 会设计复杂的软件架构)。另外 UML 的研究工作和 Fielding 的研究工作是并行的,Fielding 可能并不了解 UML 同时取得的进展。

  架构可以存在于软件系统的多个层次,电脑中的 BIOS、显示卡驱动程序、操作系统、应用软件都有自己的架构。一篇关于 Web 基础技术架构的论文不可能无所不包,有必要限定论文所讨论架构的范围。基于网络应用的架构就是这篇论文中所讨论架构的范围。在这一类应用软件中,组件之间的交互能够通过网络通信来实现。这类应用通常包括客户端和服务器端两部分,它们合在一起才构成了一个完整的应用。基于网络应用并不包括那些构成网络协议栈本身(例如 TCP/IP 协议栈)的软件,仅包括使用这些协议栈软件的应用软件。

  Fielding 特别指出了“基于网络应用”与我们常见的“分布式应用”之间的区别:

  “Tanenbaum 和 van Renesse 是这样来区分分布式系统和基于网络的系统的:分布式系统在用户看来像是普通的集中式系统,但是运行在多个独立的 CPU 之上。相反,基于网络的系统有能力跨越网络运转,但是这一点无需表达为对用户透明的方式。在某些情况下,还希望用户知道一个需要网络请求的动作和一个在本地系统就能满足的动作之间的差别,尤其是当使用网络意味着额外的处理成本的时候。本论文涵盖了基于网络的系统,并不仅限于那些对用户透明的系统。”

  也就是说,可以将“分布式应用”看作是“基于网络应用”的子集。狭义的“分布式应用”需要确保对于用户的透明,而广义的“基于网络应用”则无须维持这种虚假的透明(让用户忽略本地调用和远程调用之间的巨大差别)。尽管如此,在笔者看来,除非做学术研究,普通 Web 开发者并不需要严格区分“基于网络应用”和“分布式应用”。Fielding 在博士论文中之所以使用了“基于网络应用”这个广义的新术语,是为了让评审团的成员不至于落入狭义的“分布式应用”的很多传统思维之中。

  我们在第 1 章中已经看到,按照 Fielding 的定义,架构风格正是由一组相互协作的架构约束组成的。在笔者看来,因为对于同一架构风格的架构实例而言,它们所具有的架构约束和由这些架构约束产生的架构属性往往是相同的。所以在很多情况下,若某种特定的架构不适合某个特定的应用,那么此种架构所属的架构风格中的其他架构也不适合该特定的应用。例如:如果 EJB 不适合用来连接 Web 应用的客户端和服务器端,那么同属分布式对象架构风格的.NET Remoting 同样也不适合。

  其中,性能可以细分为网络性能(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 基础技术架构的奠基者之一,也可以称的上是研究软件架构的大师。我们在论文后面的章节还会看到更多的精彩内容。

  详细的表格读者可以阅读论文原文,笔者在这里不再赘述。在付出了很大努力,做了大量比较之后,Fielding 非常坦诚地指出,他所做的这些比较仍然存在两个局限。第一个局限是这里的评估是特别为分布式超媒体的需求而量身定制的。这一点很容易理解,因为 Fielding 做这些研究的主要目的,是为 Web 系统找到一种最适合、最实用的架构风格。第二个局限是对于架构属性的分组。这种架构属性的分组方式(见第 2 章的定义)可能并不适合于软件架构的一些细微之处。例如简单性这个架构属性,有时候可以再细分为可理解性和可验证性,而且两者之间可能存在冲突。

  “尽管如此,这些最初的调查和分类,对于任何可能解决这些局限的更进一步的分类来说,是一个必需的先决条件。”

  第 3 章是 Fielding 博士论文中非常出彩的一章,集中体现出了 Fielding 在软件架构研究领域的深厚功力。以前笔者曾经读过的一些软件架构方面的著作,但是感觉只是看到了一个个树木,论文的第 3 章让笔者看到了整片的森林,一下子豁然开朗。笔者在读这一章的时候感觉是大开眼界。以前读过的任何一本软件架构方面的著作中都没有这样清晰地比较过各种软件架构的影响。甚至有些作者还有意模糊某种架构的不利方面,而且从来不肯讲清楚某种架构所适用的运行环境,给读者造成的感觉就是:这是一种普遍适用的“银弹”架构。除了技术图书的作者,一些开发中间件产品(例如:应用服务器)的公司更是变本加厉。这样的例子不必一一列举,实在太多了。如果有更多的人能像 Fielding 博士这么诚实,世界是不是会变得好很多?

  在以后的工作中,每当笔者感觉到某个应用似乎可以使用某种架构时,都会重新阅读 Fielding 博士论文的第 3 章,看看这种架构有没有已经被讨论过,有哪些优缺点。如果这种架构是一种尚未被讨论过的新架构,我就会按照 Fielding 所提出的研究方法和分类框架,画出二维表格,仔细分析这种架构对于各种架构属性将会造成的影响,从而判断这种架构是否真的适合于我所要建造的应用。这是笔者目前所知道可以遵循的最为可靠的研究方法,可以让我们尽可能远离 design by buzzword。

  低门槛:构成 Web 基础技术架构的技术必须简单易用,任何创作者都可以很容易地使用这些技术向 Web 加入自己创作的内容。

  可扩展性:Web 基础技术架构应该足够灵活,足以应对各种可能的变化,有很强的进化能力,而不应该陷入已部署系统的局限中无法自拔。

  分布式超媒体:因为分布式超媒体允许在远程某个地点存储表述和控制信息,Web 基础技术架构要支持使用分布式超媒体的大粒度交互。同时因为 Web 系统的信息源是跨越整个互联网分布的,这种架构必须使网络交互最小化(减少交互的请求 - 响应数量、缩短一个会话中所有交互数据传输的总时间)。

  独立部署:整个 Web 系统中已经部署的旧的组件(即某种 Web 基础技术架构协议的实现)不应该妨碍新部署的组件使用自己的扩展功能。Web 基础技术架构作为一个整体,必须被设计为易于以一种增量的、迭代的方式来部署,因为强制以一种整齐划一的方式来部署是不可能的。Fielding 接下来讨论了随着 Web 的飞速发展,Web 系统面临的问题和挑战。

  “尽管为 Web 的成功而欢欣鼓舞,但 Internet 开发者社区开始担心 Web 使用的快速增长率,伴随早期 HTTP 的一些糟糕的网络特性,将会很快超越 Internet 基础设施的容量,并且导致全面的崩溃。Web 应用的交互性质的变化更加恶化了这种情况。尽管最初的协议是为单个的请求响应对(request-response pairs)而设计的,新的站点使用了越来越多的内嵌图片(in-line images)作为网页内容的一部分,这导致了不同的浏览交互模式(interaction profile)。已部署的架构在对可扩展性、共享缓存、中间组件的支持等方面存在着严重的局限,这使得开发解决增长率问题的特别解决方案非常困难。同时,软件市场中的商业竞争导致了新的提议和一些有时候与 Web 协议相矛盾的提议层出不穷。”

  假设三:修改 Web 架构的提议能够与更新后的 WWW 架构风格进行比较和分析,以便在部署之前识别出存在的冲突。

  第一步:识别出那些负责产生想要得到的属性的一组存在于早期 Web 架构中的约束。

  第二步:识别出在一个 Internet 规模的分布式超媒体系统中想要得到的属性,然后选择额外的会产生那些属性的架构风格,将它们与早期的 Web 约束相结合,形成一种新的、混合的现代 Web 架构的架构风格。

  第三步:使用新的架构风格作为指导,我们能够对被提议的扩展与针对风格中的约束对 Web 架构所做的修改进行比较。存在冲突表明这个提议会违反一个或多个在 Web 背后的设计原则。

  第四步:解决发现的严重冲突。使用更加有益于 Web 风格的设计来替代相同的功能,或者告知提议人将此功能实现为与 Web 并行运行的单独的架构。

  “修订后的协议标准是根据新的架构风格的指导来编写的。最后,如同修订后的协议标准中定义的那样,更新后的 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 所创建的这一套研究软件架构的方法论。在笔者看来,这一套方法论才是这篇论文的最大贡献。西瓜和芝麻,哪个该取哪个该舍,一目了然。

  通常来说,推导出一种架构风格有两种方法:做加法和做减法。做加法就是先从一个最简单的架构风格开始,逐个添加识别出的架构约束;做减法就是先从一个非常复杂的架构风格开始,逐个去除不需要的架构约束。第一种方法强调创造性和想象力,而第二种方法则强调限制和对系统环境的理解。这两种方法都可以得到满足应用需求的架构风格,区别是实践的难度。我们都知道,为一个简单的设计添加新的元素,通常都要比为一个复杂的设计减少元素更容易。Fielding 推导 REST 架构风格的过程,采用的就是做加法的方式。

  架构风格的推导从一个“空”风格开始。“空”风格就是没有任何架构约束的风格。笔者非常欣赏这种推导方法,因为它体现出了道家的“无中生有”、“一生二、二生三、三生万物”的思想。REST 架构风格推导的过程,让我们清晰地看到了 Fielding 博士这些 Web 基础技术架构奠基者的设计思路,看到了 Web 的基础技术架构如何在保持相对最简化设计的同时完美地满足了 Web 系统的需求。

  。添加这个约束是为了分离关注点。因为 Web 系统交互所使用的分布式超媒体的存储地和使用地大多数时候都是不同的,它们的分工和角色有明显的不同,有必要将它们区分为客户和服务器。服务器主要负责数据的存储,而客户端主要负责提供用户界面。对于 Web 系统来说,最重要的是这种关注点的分离允许组件独立地进化,从而支持跨多个域的互联网规模的需求。

  “从客户到服务器的每个请求都必须包含理解该请求所必需的所有信息,不能利用任何存储在服务器上的上下文,会话状态因此要全部保存在客户端。”

  这个架构约束对于设计具有高度可伸缩性的服务器来说是非常重要的,我们几乎可以将此看作一个铁律。只要读者希望服务器具有最大的可伸缩性,都应该将服务器设计为无状态的。无状态的服务器设计做负载均衡会非常容易,很容易通过添加新的服务器来支持更大的负载。

  。缓存通过将数据搬移到距离其使用地更近的位置,提高了网络效率和性能,同时还减少了对于服务器不必要的访问,因此缓存提高了服务器的可伸缩性。添加这个架构约束也是为了满足互联网规模的高度可伸缩性需求。以 HTTP 这种 REST 风格的架构为例,HTTP 作为 Web 基础架构协议内建有很多对于缓存的支持。缓存可以出现在 HTTP 通信链中的很多地方:用户代理(例如浏览器)、代理服务器、网关(又叫反向代理服务器)、来源服务器等等。

  按照 Fielding 的描述,REST 的统一接口由 4 个部分组成:资源的标识、通过表述对资源执行的操作、自描述的消息、以及作为应用状态引擎的超媒体(现在通常缩写为 HATEOAS)。以 HTTP 为例,资源的标识就是资源的 URI;资源的表述是资源在特定时刻状态的描述,可以通过在客户 - 服务器之间传递资源的表述,对资源执行某种操作;自描述的消息由一些标准的 HTTP 方法、可定制的 HTTP 头信息、可定制的 HTTP 响应代码组成;超媒体就是 HTML,可以使用 HTML 作为引擎,驱动应用状态的迁移。

  分层系统。REST 风格的网络交互不仅仅包括客户和服务器两个参与者,REST 风格的网络交互可以分成很多层,它们合在一起构成一个完整的通信链。以 HTTP 为例,HTTP 协议允许插入很多中间组件,位于两端的用户代理、来源服务器与这些中间组件合在一起构成了整个 HTTP 通信链。交互的参与者都是相对独立的组件,组件之间的交互通过内建在组件内部的连接器来完成,所有连接器之间的交互都使用 HTTP 协议定义的统一接口。用户代理和来源服务器的角色比较单纯,但是对于中间组件来说,它对于通信链中位于来源服务器方向的组件是客户端,而对于通信链中位于用户代理方向的组件是服务器端。所有的组件都只会与直接相邻的组件进行交互,就好像其他不相邻的组件不存在一样。一个组件完全不需要知道整个交互的拓扑结构,除了在通信活跃期间,甚至也不需要知道相邻组件的存在。HTTP 通信链中最常见的中间组件包括代理服务器、网关、防火墙等等。

  “分层系统约束和统一接口约束相结合,导致了与统一管道和过滤器风格类似的架构属性。”

  。按需代码说的是客户端可以从服务器端下载可执行代码,在客户端运行。浏览器从 Web 服务器上下载 JavaScript 脚本就是一个最典型的例子,另外还可以下载 Java Applet、Flash、Silverlight 等等可执行的代码。

  在架构层面支持按需代码,简化了客户端应用的开发。允许在部署之后下载可执行代码也改善了系统的可扩展性。然而,这也降低了可见性,因为这些可执行代码所代表的语义对于中间组件来说是不可见的,因此中间组件难以对其做有效的缓存和安全审计。所以 Fielding 将按需代码设计为只是 REST 的一个可选的架构约束。

  我们看到,构成 REST 架构风格的架构约束其实并不多。5 个必需的架构约束加上一个可选的架构约束,不多也不少,完美地满足了 Web 系统的需求。这很符合笔者所欣赏的设计哲学:好的设计并不是无法再添加新的部分,而是任何部分都无法再减少。得到满足应用需求的最简化设计应该是一个优秀软件架构师所追求的目标。

  识别出满足 Web 系统需求的架构风格所应该具有的架构约束之后,Fielding 接下来定义了 REST 风格的架构有哪些组成元素。一个 REST 风格的架构,其组成元素包括数据

  数据元素包括:资源、资源标识符、表述、表述元数据、资源元数据、控制数据连接器元素包括:

  (有翻墙经验的读者对这个再熟悉不过了)和一个网关(例如常见的 Nginx 或者 Squid)来间接访问来源服务器。在 b 过程中用户代理直接访问来源服务器,没有任何中间组件。在 c 过程中,用户代理通过一个共享的代理服务器访问一个 WAIS 服务,

  这两张架构图,一个抽象(在架构风格层次)、一个具体(在架构实例层次),但是却高度一致。聪明的读者看懂了这两张架构图,对于 HTTP/1.1 协议为何如此设计,想必已经了然于胸。

  “REST 通过强制消息具有自描述性(请求之间的交互是无状态的、使用标准的方法和媒体类型来表达语义和交换信息、响应可以明确地表明其可缓存性)来支持中间组件的处理。”

  这段话是说,HTTP 消息的语义应该保持对于中间组件的可见性。作为 Web 开发者而言,可以将消息语义理解为通过 GET/POST/PUT/DELETE 等标准 HTTP 方法对资源执行的 CRUD 操作。表达消息语义的信息不应该被埋藏在 HTTP 消息体之中。中间组件通常不会去尝试解析 HTTP 消息体以理解消息的语义,因为那样做是非常低效的。

  连接器视图侧重于组件之间的通信机制。统一接口这个 REST 的主要的架构约束就是由连接器来实现的,连接器之间的交互必需通过统一的接口来完成。这个统一接口通常使用 HTTP 协议来实现,但是并不局限于 HTTP 协议。在 REST 的三大类架构组成元素中,连接器是直接与各种协议(HTTP 或其他通信协议,例如流程视图中 c 分支中的 WAIS 协议)打交道的部分。

  正是因为 REST 架构风格支持统一接口,因此才有可能实现可重用的连接器。无状态、缓存、统一接口、分层系统 4 个架构约束彼此协作,使得基于 HTTP 协议能够实现大规模的分布式缓存,将负载分摊到跨网络的各个中间组件之上,从而极大提高了来源服务器的可伸缩性。

  “为了获得连接器语义的单一的、通用的接口的好处,这一约束牺牲了其他架构的一些好处,例如像 WAIS 这样的相关性反馈协议(relevance feedback protocol)的有状态交互的好处。作为回报,通用的接口使得通过单个代理访问多个服务成为了可能。”

  这段话是说,尽管 REST 风格的架构能够将 HTTP 协议之外的其他协议也纳入进来,但是对于这些其他协议的支持是有限的。举个较为常见的例子,很多开源项目的 FTP 服务器同时也支持通过 HTTP 协议来访问,但是仅限于支持可匿名访问的文件,无法支持需要登录才能访问的文件。因为 FTP 协议对于用户登录的支持是有状态的,而 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 以几何级数的速度飞速发展,影响到了地球上的每一个人类社区。

  REST 架构风格其实并不是什么新的东西,从 Web 的历史来说,甚至可以说相当古老。Fielding 和他的协议团队自从 1994 年以来就在内部使用 REST 来指导 Web 基础技术架构协议的设计。不过 Fielding 直到 2000 年才通过这篇博士论文向世人揭示出 REST 的全貌。而 REST 真正流行开来,还是要等到 Ajax 流行之后,特别是在出现了一些成熟的服务器端 REST 开发框架之后,使得 REST 这种抽象的架构风格变成了我们能够日常实践的开发架构。

  “这个名称“表述性状态转移”是有意唤起人们对于一个良好设计的 Web 应用如何运转的印象:一个由网页组成的网络(一个虚拟状态机),用户通过选择链接(状态迁移)在应用中前进,导致下一个页面(代表应用的下一个状态)被转移给用户,并且呈现给他们,以便他们来使用。”

  “在 REST 中,对于“资源”的定义基于一个简单的前提:标识符的改变应该尽可能很少发生。因为 Web 使用内嵌的标识符,而不是链接服务器,创作者需要一个标识符,这个标识符能够紧密地匹配他们想要通过一个超媒体引用来表达的语义,允许这个引用保持静态,甚至是在访问该引用所获得的结果可能会随时间而变化的情况下。REST 达到了这个目标,通过将一个资源定义为创作者想要标识的语义,而不是对应于创建这个引用时的那些语义的值。然后留给创作者来保证所选择的这个标识符确实真正标识出了他所想要表达的语义。”

  “将“资源”定义为一个 URI 标识了一个概念,而不是标识了一个文档,这给我们带来了另一个问题:一个用户如何访问、操作或转移一个概念,使得他们在选择了一个超文本链接后能够得到一些有用的东西。REST 通过定义在被标识的资源的“表述”之上执行的操作,而不是在资源本身之上执行的操作回答了这个问题。一个来源服务器维护着从资源的标识符到每个资源相对应的表述集合的映射,因此可以通过由资源标识符定义的通用接口转移资源的表述来操作一个资源。”

  “REST 对于资源的定义来源于 Web 的核心需求:独立创作跨多个可信任域的互相连接的超文本。强制接口的定义与接口的需求相匹配会使得协议似乎含糊不清,但这仅仅是因为被操作的接口仅仅是一个接口,而不是一个实现。这些协议是与一个应用动作的意图密切相关的,但是接口背后的机制必须要确定该意图如何来影响底层实现中资源到表述的映射。

  这里所隐藏的信息是关键的软件工程原则之一,也就是 REST 使用统一接口的动机。因为客户端被限制为只能对资源的表述执行操作,而不是直接访问资源的实现,因此资源的实现可以以任何命名权威所希望的形式来建造,而不会影响到使用资源的表述的客户端。此外,如果当资源被访问时,存在着资源的多个表述,可以使用一个内容选择算法来动态地选择一个最适合客户端能力的表述。当然,其缺点就是对资源进行远程创作不像对文件进行远程创作那么直接。

  这里所隐藏的信息是关键的软件工程原则之一,也就是 REST 使用统一接口的动机。因为客户端被限制为只能对资源的表述执行操作,而不是直接访问资源的实现,因此资源的实现可以以任何命名权威所希望的形式来建造,而不会影响到使用资源的表述的客户端。”

  如果读者对面向对象设计(OOD)相当熟悉,上面这段话看起来似曾相识。资源建模的过程,与面向对象建模很相似。资源可以看作是服务器所暴露出来的接口,与 OOD 中的接口类似,资源也是一个用来做抽象的工具。与类或接口不同的是,因为存在统一接口的约束,所以在每个资源之上只能定义有限的几个标准操作。

  将资源从最初很具体的文档映射转变为一种用来做抽象的工具之后,带来的一个问题是远程创作不再像以前那么直接了。原先直接映射到文档时,修改一个资源是很直接的,现在则需要通过操作资源的表述来简介进行。资源的表述携带了资源在某个特定时刻的状态信息,客户端可以通过改变获取到的资源表述,加以修改,然后将修改过的资源表述提交到服务器端,来修改资源的状态。

  “资源是一种概念上的映射——服务器接收到标识符(标识这个映射),将它应用于当前的映射实现(mapping implementation,通常是与特定集合相关的树的深度遍历和 / 或哈希表的组合)上,以发现当前负责处理该资源的处理器实现,然后处理器实现基于请求的内容选择适当的动作 + 响应。所有这些特定于实现的问题都隐藏在 Web 接口之后,它们的性质无法由仅能够通过 Web 接口访问资源的客户端来作出假设。”

  “对于设置资源标识符和用表述组装那些资源的动作而言,语义是一个副产品。服务器或客户端软件绝对不需要知道或理解 URI 的含义——它们仅仅扮演一个管道,通过这个管道,资源的创建者(一个作为命名权威的人)能够将表述与通过 URI 标识的语义关联起来。换句话说,在服务器端没有资源,仅仅是通过由资源定义的抽象接口提供答案的机制。这看起来似乎很奇怪,但是这正是使得 Web 跨越如此众多的不同实现的关键所在。”

  一个例子是 URI 中包括标识当前用户的信息,如果服务器通过 URI 重写而不是 cookie 来跟踪会话信息,就会发生这种情况。Fielding 说:

  “通过记录用户的动作来跟踪他们的行为……由于违反了 REST 的约束,这些系统会导致共享缓存变得效率低下,这降低了服务器的可伸缩性,并且在一个用户与其他用户共享那些引用时会导致不希望的结果。”

  另一个例子是客户端将 Web 服务器简单地看作一个分布式文件系统,即类似于 NFS 这样的东西,然后对 Web 服务器做镜象。完全静态的 Web 服务器,这样做镜象没问题,但是对于一个动态的 Web 服务器,是不能这样做镜象的。因为每个 URI 所代表的资源是抽象的,做镜像得到的只是资源在某个特定时刻的影子,而不是资源本身。

  这些问题的根源还是在于 Web 开发者并没有深入理解 REST,不理解 HTTP 和 URI 的设计意图。REST 的设计原则虽然融入了 HTTP 和 URI 协议的设计之中,但是很难强制这些协议的使用者必须按照 REST 的设计原则来设计开发 Web 应用。当然,随着 REST 的日渐普及和深入人心,这些问题会有所改善。

  最初的 HTTP/1.0 协议的一些方面不是很适合 REST 的要求,在设计 HTTP/1.1 的过程中,有必要对这些方面加以隔离,使得它们不至于影响新协议的部署。HTTP1.1 为了这个目的,引入了协议版本控制。此外协议版本控制还可以用来增大未来与 REST 的要求不兼容的协议扩展部署的难度。

  “这些规则(指协议版本控制)的存在协助了多个协议修订版的部署,并且防止了 HTTP 架构师遗忘掉协议的部署是其设计的一个重要方面。这些规则是通过使得对于协议兼容的改变和不兼容的改变容易区别来做到这一点的。兼容的改变很容易部署,对于协议接受能力的差异能够在协议流(protocol stream)中进行沟通。不兼容的改变难以部署,因为它们在协议流能够开展通信之前,必须做一些工作来确定协议的接受能力。”

  HTTP/1.1 还增加了很多自描述的消息,即 HTTP 头信息字段。前面我们已经知道,自描述的消息是 HTTP 统一接口的一部分。其中与 Web 开发者关系最密切的是缓存控制和内容协商两部分的头信息。

  “Cookie 也违反了 REST,因为它们允许数据在没有充分表明其语义的情况下进行传递,这样就成为了一个安全和隐私方面的关注点。结合使用 Cookie 和 Referer[sic] 头信息字段,有可能当用户在多个站点之间浏览时,对他进行跟踪。”

  Cookie 这个东西可谓非常古老,在 HTTP/1.0 协议中就已经支持 Cookie 了。Fielding 在 13 年前的博士论文中特别指出了滥用 Cookie 的危害,可惜很少有 Web 开发者考虑这个方面。今年央视报道过的一些 Cookie 跟踪公司为何会出现引起公众大范围的恐慌,现在读者理解了吧?当然这些报导是夸大其辞了,Cookie 跟踪没那么万能,不过危害确实也不小。

  “HTTP 头信息字段名称仅当它们所包含的信息对于正确理解消息并非是必需的时候,才能够被任意扩展。”

  “一个基于网络的 API 对于应用的交互而言,是一种包含有已定义语义的在线(on-the-wire)的语法。一个基于网络的 API 没有在应用的代码上强加任何除了读或写网络的需求之外的限制,但是确实在能够有效地跨接口进行通信的一组语义上添加了限制。其有利的方面就是,性能仅仅受限于协议的设计,而不是受限于该设计的特殊实现。

  一个基于库的 API 为程序员做的工作要多得多,但是通过做这些工作,也带来了大量更多的复杂性,并且其负担超出了任何单个系统必须承受的限度,这种方法在一个不同种类的网络中的可移植性较差,而且总是会导致首先选择通用性,而不是性能。作为一个副作用,它也导致了在开发过程中产生惰性(为任何事情都去责备 API 代码),而不去努力解决其他通信参与方不合作的行为。”

  “这种形式的区分也可以在将消息解释为一个单元(a unit)或者解释为一个流(a stream)中看到。HTTP 允许接收者或发送者来自行决定。CORBA 的 IDL 甚至(仍然)不允许使用流,即使当它确实得到了扩展以支持流之后,通信的双方仍然被绑定在相同的 API 上(译者注:即 CORBA 的基于库的 API),而不是能够自由地使用最适合于它们的应用类型的东西。”

  在电信行业曾经很流行的 CORBA 为什么逐渐遭到废弃,变成了一种遗留的架构?很多人会说那是因为它的复杂性。那么它为什么一定要设计的这么复杂呢?几乎没有人能够回答。很多年以来,国内的架构师看到软件大厂所鼓吹的各种神奇架构你方唱罢我登场,疲于跟随。Fielding 为我们清晰地指出了这些遗留架构中存在的本质问题。

  “将 HTTP 和 RPC 区分开的并不是语法,甚至也不是使用一个流作为参数所获得的不同的特性,尽管它帮助解释了为何现有的 RPC 机制对于 Web 来说是不可用的。使得 HTTP 与 RPC 存在重大不同的是:请求是使用具有标准语义的通用的接口定向到资源的,这些语义能够被中间组件和提供服务的来源机器进行解释。结果是使得一个应用支持分层的转换(layers of transformation)和间接层(indirection),并且独立于消息的来源,这对于一个互联网规模、多个组织、无法控制的可伸缩性的信息系统来说,是非常有用的。与之相比较,RPC 的机制是根据语言的 API(language API)来定义的,而不是根据基于网络的应用来定义的。”

  同时,Fielding 还指出了“HTTP 不是一种传输协议”。很多人把 HTTP 看作一种能够穿越防火墙、简单易用的传输协议,仅仅是因为 HTTP 的消息体可以包含任意的内容。

  虽然有很多人基于以 REST 架构风格设计的 HTTP/URI 协议来实现一种 RPC 风格的 API,仅仅将 HTTP 当作一种传输协议,但是必须明确指出,以这样的方式使用 HTTP 协议是错误的和低效的,在可伸缩性方面会付出很大的代价。SOAP 就是这两个误解的一个典型的例子,关于 SOAP 的问题,笔者将会在后续的文章中详细探讨。

  在这一部分中,我们需要注意的是 REST 并非对于所有的媒体类型一视同仁,事实上,REST 会淘汰掉那些不适合其设计原则要求的媒体类型。目前 HTML 是最适合 REST 要求的媒体类型,REST 也是特别针对 HTML 优化过的。使用其他的媒体类型,特别是一些自定义的二进制格式,与 HTTP 这种 REST 风格的架构配合工作,不会得到理想的结果。

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

QQ在线编辑

服务热线

展开