理解本质的 REST

1,672 阅读24分钟
原文链接: ruby-china.org

REST本身是一个高度抽象化的架构风格,因而总是很难对它有一个比较深入且印象深刻的理解。写这篇文章的目的,是自己对学习REST的一个总结,也希望可以通过这篇文章,能够让读者真正的理解REST。

本文主要内容

  • 什么是REST
    • REST概念
    • REST的由来
    • REST的理解
  • REST的架构约束条件
    • 客户/服务器模型
    • 无状态
    • 缓存
    • 统一接口
    • 分层系统
    • 小结
  • 总结

什么是REST

REST的概念

先来看看百度对REST的定义:

REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

  • 我们更多的将REST称为表述性状态转移
  • 所谓的表述性状态转移,是对什么的表述?——资源
  • REST省略了主语Resource(资源),全称是 Resource Representational State Transfer,即资源表述性状态转移。通俗来讲就是:资源在网络中以某种表现形式进行状态转移。
  • 如果一个架构符合REST原则,就称它为RESTful架构。

在对REST更深一步的解释之前,我们先来看看REST的由来,而这对于REST的理解至关重要。

REST的由来

首先简单了解一下作者——Roy Thomas Fielding

  • HTTP/1.0协议专家组成员
  • HTTP/1.1协议专家组负责人
  • Apache HTTP服务器的核心开发者
  • Apache软件基金会合作创始人

Roy Thomas Fielding

一张图说明REST的由来:

REST的由来

好吧,这是一张很简陋的图,不过用来解释REST的由来足够了。 故事得从远古时期的HTTP/1.0协议说起,随着web技术的发展,沿用多年且面向静态文档的HTTP/1.0协议无法满足web应用的开发需求,作为HTTP/1.0协议专家组成员之一的Roy Fielding脱颖而出,成为了HTTP/1.1协议专家组的负责人,负责统筹制定新版本的协议。 Roy Fielding和他的同事们在制定HTTP/1.1协议的过程中,从技术架构层面对web之所以能获得巨大成功做了一番深入的研究和总结,之后将这些总结纳入到一套理论框架之中,并利用这套理论框架中的指导原则,指导HTTP/1.1协议的设计方向。经过三年的修订,HTTP/1.1协议于1999年6月正式成为规范。HTTP/1.1协议设计取得了极大的成功,在发布之后的十年里,都没有多少人认为有修订的必要。 Fielding在完成HTTP/1.1协议的设计工作之后,回到了加州大学欧文分校继续攻读自己的博士学位。第二年(2000年)在他的博士学位论文Architectural Styles and the Design of Network-based Software Architectures中(中文版名为《架构风格与基于网络的软件架构设计》),Fielding更为系统、严谨地阐述了这套理论框架,并且使用这套理论框架推导出了一种新的架构风格,并且为这种架构风格取了一个令人轻松愉快的名字“REST”——Representational State Transfer(表述性状态转移)的缩写。 这便是REST的由来,可以看出,REST架构风格,是通过推导web的技术架构因素层面而总结出来的,总结出来的理论框架被用来指导HTTP/1.1协议的设计方向。那么我们可以这样理解,REST是Web自身的架构风格,REST是HTTP/1.1协议等Web规范的设计指导原则,HTTP/1.1协议正是为实现REST风格的架构而设计的。

REST的理解

什么是web

从REST的来源中我们发现,要想深刻理解REST,首先得了解web。 先来看一些web的相关知识:

百度百科 web(World Wide Web)即全球广域网,也称为万维网,它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。是建立在Internet上的一种网络服务,为浏览者在Internet上查找和浏览信息提供了图形化的、易于访问的直观界面,其中的文档及超级链接将Internet上的信息节点组织成一个互为关联的网状结构。

维基百科 万维网(英语:World Wide Web),亦作“WWW”、“Web”,是一个由许多互相链接的超文本组成的系统,通过互联网访问。 万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。 互联网万维网用语经常被使用且没有太多区别。然而,两者是不一样的。互联网是电脑网络互相连接的全球系统。相较之下,万维网是全球收集的文件和其他资源,通过超链接和URIs连接。万维网资源通常使用HTTP访问,这是互联网通信协议的一种。 万维网的核心部分是由三个标准构成的:

  • 统一资源标识符(URI),这是一个统一的为资源定位的系统。
  • 超文本传送协议(HTTP),它负责规定客户端和服务器怎样互相交流。
  • 超文本标记语言(HTML),作用是定义超文本文档的结构和格式。

总结来说,web是一个由许多相互链接的超文本组成的系统,它使用URI来定位系统中的每一个资源,并通过HTTP协议进行数据的交互。 更抽象的说,Web是一个分布式信息系统,为超文本文件和其他对象(资源)提供访问接口和访问机制。 理解了什么是web,我们便可以更好地理解什么是REST了。作为web自身的架构风格,我们直接给出结论:REST本质上是一种分布式超媒体系统的应用层解决方案,它为资源互通和资源管理的分离提出了一系列架构约束和原则,得到一个功能强、性能好、适宜通信的以网络为基础的应用软件架构。 这个结论依然很难理解,但我们需要对此有一个概念了解。接下来我们会对REST进行更为详细的介绍。

REST词组

要理解REST,首先需要理解(Resource)Representational State Transfer这个词组。

资源(Resource)

REST对于信息的核心抽象是资源。任何能被命名的信息都能作为一个资源:一份文档、一个与时间相关的服务(例如,“洛杉矶今日的天气”),一个其他资源的集合、一个非虚拟的对象(例如,人)等等。 换句话说,可以作为创作者的超文本引用的目标(the target of an author's hypertext reference)的任何概念都必须符合资源的定义。资源是到一组实体的概念性映射(a conceptual mapping),而不是在任何特定时刻与该映射相关联的实体本身。 更精确地说,资源R是一个随时间变化的成员函数

该函数根据时间t将资源映射到一个实体或值的集合,集合中的值可能是资源表述(resource representations)和/或资源标识符(resource identifiers)(两者是等价的)。 对于一个资源来说,唯一必须静态的是映射的语义,因为语义才是区别资源的关键。 正是资源的这个抽象定义,使得Web架构的核心功能得以实现。首先,它包含了很多信息的来源,并没有人为地通过类型或实现对它们加以区分,从而实现了通用性。其次,它允许引用到表述的延迟绑定,从而支持基于请求的性质来进行内容协商。最后,它允许创作者引用一个概念而不是引用此概念的某个单独的表述,从而使得当表述改变时无需修改所有的现有链接(假设创作者使用了正确地标识符)

如何来理解“对于一个资源来说,唯一必须静态的是映射的语义,因为语义才是区别资源的关键”这句话呢?举一个简单的例子来说明一下: “一个APP的当前版本”是一个资源,而“一个APP的最稳定版本”也是一个资源,尽管这两个资源在某个时刻上可能会映射到相同的值,但它们是是截然不同的,且两个资源能够被单独地标识和引用。

资源标识符

REST使用资源标识符来表示组件之间交互所涉及的特定资源。REST连接器提供了访问和操作资源的值集合的一个通用的接口,而无须关心其成员函数(membership function)是如何定义的,或者处理请求的软件是何种类型。由命名权威(naming authority)来为资源分配资源标识符,使得引用资源成为可能,映射的语义有效性也由同样的命名权威来负责维护(例如,确保成员函数不会改变)。 传统的超文本系统通常只在一个封闭的或局部的环境中运行,它们使用随信息的变化而改变的唯一节点或文档标识符,并依赖链接服务器(link server)以独立于内容的方式来维护引用。因为集中式的链接服务器完全无法满足Web的超大规模和跨越多个组织领域的需求,所以REST采用了其他的方式——以来资源的创作者来选择最符合被标识的概念本质的资源标识符。

资源标识符为访问和操作资源的值集合提供了一个通用的接口。换句话说,我们抽象出来的资源都应该是可标识的,都应该拥有一个明显的ID——在Web中,代表ID的统一概念是:URI(统一资源标识符)。URI构成了一个全局命名空间,使用URI标识关键资源意味着这些资源获得了一个唯一、全局的ID。 举个简单的例子:如果在一个类似于Amazon.com的在线商城中,没有用唯一的ID(一个URI)标识它的每一件商品,可想而知这将是多么可怕的业务决策。

表述(Representations)

资源的表述是一段对于资源在某个特定时刻的状态的描述。 资源在外界的具体呈现,可以有多种表述(或称为表现、表示)形式,在客户端和服务端之间传送的也是资源的表述,而不是资源本身。 例如文本资源可以采用html、xml、json等格式,图片可以使用PNG或JPG展现出来。 资源的表述包括数据和描述数据的元数据,例如,HTTP头“Content-Type” 就是这样一个元数据属性。 更确切的说:

REST组件使用表述来捕获某个资源的当前状态或预期状态,随后在组件之间移交该表述,同过这种方式在资源上执行各种动作(perform actions on a resource)。表述(representation)有一个字节序列和描述这些字节的表述元数据(representation metadata)构成。表述的其他常用但不精确的名称包括:文档、文件、HTTP消息实体、实例或变量。 表述由数据、描述数据的元数据、以及(有时候存在的)描述元数据的元数据组成(通常用来验证消息的完整性)。 表述的数据格式被称为媒体类型(media type)。

简单总结一下:

  • 资源总是以某种表述为载体显示的,即序列化的信息
  • 资源的表述是REST架构的表现层
  • 资源可以有多重表述

状态转移

状态转移:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。 互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的资源状态都保存在服务器端。因此,如果客户端想要操作服务器中的资源,必须通过某种手段,让服务器端的资源发生"状态转移"(State Transfer)。而这种转化是建立在表述之上的,所以就是"表述性状态转移"。 客户端使用的手段,在web中就是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:

  • GET——获取资源
  • POST——新建资源(也可以用于更新资源)
  • PUT——更新资源
  • DELETE——删除资源

小结

Resource Representational State Transfer,资源表述性状态转移,即就是:根据数据抽象出来的资源,以某种表现形式,通过某种手段,在网络中发生状态转移,以此来间接实现操作资源的目的。表述性状态转移(REST)架构风格是对分布式超媒体系统中的架构元素的一种抽象。 在web中,具体而言:

  • 每一个URI代表一种资源;
  • 客户端和服务器之间,传递这种资源的某种表现层;
  • 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

我们再来换一个角度,以搭建系统的角色来思考这个问题: 在web中,为了获取我们需要的分布在不同地域的超媒体资源,我们该如何设计这个系统?显然,web中有着大量的,分布在不同地方的各种类型的资源。我们需要提供的是一个大型分布式超媒体系统的应用层解决方案。 首先我们需要为所需的数据设定唯一标识,因此我们将数据进行抽象为资源,并使用统一资源标识符(URI)为每个资源设定ID,这样我们就有办法来操作每个资源。 那么该如何操作资源呢?换句话说,当我们看到一个URI并将它输入到浏览器中是,为何浏览器知道该怎样处理这个URI?事实上,浏览器知道如何去处理URI的原因在于:所有的资源都支持同样的接口(URI),支持一套同样的方法(HTTP动词)。这样,当我们自己按照这种方式来定义我们自己的资源时,web中的其他人便可以轻松的获取这些资源。 获取资源时,我们可能需要不同的呈现方式或是需求,因此我们需要对资源进行表述,使其表现为我们需要的形式。

从分布式系统的角度来看REST,我们发现以资源为核心的REST确实提供了一种解决大型分布式资源系统的解决方案,而web的成功也确实验证了这套理论的正确性。

REST的架构约束条件

REST作为一种组织web服务的架构风格,提出了一系列架构级约束。如果一个系统满足这些约束,那该系统就被称为是RESTful的。接下来,我们会逐条说明REST的五条必要约束。

客户/服务器模型

通信只能由客户端单方面发起,表现为请求-响应的形式。

客户-服务器约束背后的原则是分离关注点。通过分离用户界面和数据存储这两个关注点,我们改善了用户界面跨多个平台的可移植性;同时通过简化服务器组件,改善了系统的可伸缩性。然而,对于Web来说,最重要的是这种关注点的分离使得组件可独立地进化,从而支持多个组织领域的互联网规则的需求。

无状态

我们接下来在为客户-服务器交互添加一个架构约束:通信必须在本质上是无状态的,从客户到服务器的每个请求都必须包含理解该请求所必需的所有信息,不能利用任何存储在服务器端的上下文,会话状态因此要全部保存在客户端。

前面我们分析REST词组时,提到了资源的状态转移,而在这里,REST约束中又包含了无状态通信原则,看起来仿佛是矛盾了:既然“无状态”,又怎么能说“状态转移”呢?   其实,这里说的无状态通信原则,并不是说客户端应用不能有状态,而是指服务端不应该保存客户端状态。

应用状态与资源状态

状态应该区分应用状态和资源状态,客户端负责维护应用状态,而服务端维护资源状态。 客户端与服务端的交互必须是无状态的,并在每一次请求中包含处理该请求所需的一切信息。服务端不需要在请求间保留应用状态,只有在接受到实际请求的时候,服务端才会关注应用状态。 这种无状态通信原则,使得服务端和中介能够理解独立的请求和响应。 在多次请求中,同一客户端也不再需要依赖于同一服务器,方便实现高可扩展和高可用性的服务端。

优点

  • 可见性——监视系统不必为了确定一个请求的全部性质去查看该请求之外的多个请求
  • 可靠性——减轻了从局部故障中恢复的任务量
  • 可伸缩性——不必在多个请求之间保存状态,从而允许服务器组件迅速释放资源,并进一步简化其实现,因为服务器不必跨多个请求管理资源的使用情况

缺点

由于不能将状态数据保存在服务器的共享上下文中,因此增加了一系列请求中发送的重复数据(每次交互的开销),可能会降低网络性能。此外,将应用状态放在客户端还降低了服务器对于一致的应用行为的控制能力,因为这样一来,应用就得依赖多个客户端版本的语义的正确实现。

缓存

为了改善网络的效率,我们添加了缓存这个架构约束。缓存架构要求一个请求的响应中的数据被隐式地或显式地标记为可缓存的或不可缓存的。如果响应是缓存的,那么客户端缓存就可以为以后的相同请求重用这个响应的数据。

优点

添加缓存可能部分或全部消除一些交互,从而通过减少一系列交互的平均延迟时间,来提高效率、可伸缩性和用户感知的性能。

缺点

如果缓存中陈旧的数据与将请求直接发送到服务器得到的数据差别极大,那么缓存会降低可靠性。

统一接口

使REST架构风格区别于其他基于网络的架构风格的核心特征是,它强调组件之间要有一个统一的接口。通过在组件接口上应用通用性的软件工程原则,简化了正特的系统架构,也改善了交互的可见性。实现与它们所提供的服务是解耦的的,这促进了独立地可进化性。 然而,需要的付出的代价是,统一接口降低了效率,因为信息都使用标准化的形式来移交,而不能使用特定于应用的需求的形式。REST接口被设计为可以高效地移交大粒度的超媒体数据,并针对Web的常见情况做了优化,但是这也导致该接口对于其他形式的架构交互而言不是最优的。

为了获得统一的接口,需要有多个架构约束来指导组件的行为。REST由四个接口架构约束来定义:

  • 资源的识别(identification of resources)
  • 通过表述来操作资源(manipulation of resources through representations)
  • 自描述的信息(self-descriptive messages)
  • 超媒体作为应用状态引擎(hypermedia as the engine of application state,简称HATEOAS)

资源的识别

每个资源都拥有一个资源标识。每个资源的资源标识可以用来唯一地标明该资源。

通过表述来操作资源

这里说的是资源的自描述性。一个REST系统所返回的资源需要能够描述自身,并提供足够的用于操作该资源的信息,比如如何对资源进行添加,删除以及修改等操作。也就是说,一个典型的REST服务不需要额外的文档对如何操作资源进行说明。

自描述的信息

消息的自描述性。在REST系统中所传递的消息需要能够提供自身如何被处理的足够信息。例如该消息所使用的MIME类型,是否可以被缓存等。

超媒体作为应用状态引擎

即客户只可以通过服务端所返回各结果中所包含的信息来得到下一步操作所需要的信息,如到底是向哪个URL发送请求等。也就是说,一个典型的REST服务不需要额外的文档标示通过哪些URL访问特定类型的资源,而是通过服务端返回的响应来标示到底能在该资源上执行什么样的操作。一个REST服务的客户端也不需要知道任何有关哪里有什么样的资源这种信息。

这个描述的核心是超媒体概念,换句话说:是链接的思想。链接是我们在HTML中常见的概念,但是它的用处绝不局限于此(用于人们网络浏览)。考虑一下下面这个虚构的XML片段:

<order self="http://example.com/customers/1234"> 
   <amount>23</amount> 
   <product ref="http://example.com/products/4554"> 
   <customer ref="http://example.com/customers/1234"> 
</customer> </product></order>

如果你观察文档中product和customer的链接,就可以很容易地想象到,应用程序(已经检索过文档)如何“跟随”链接检索更多的信息。当然,如果使用一个遵守专用命名规范的简单“id”属性作为链接,也是可行的——但是仅限于应用环境之内。使用URI表示链接的优雅之处在于,链接可以指向由不同应用、不同服务器甚至位于另一个大陆上的不同公司提供的资源——因为URI命名规范是全球标准,构成Web的所有资源都可以互联互通。 超媒体原则还有一个更重要的方面——应用“状态”。简而言之,实际上服务器端(如果你愿意,也可以叫服务提供者)为客户端(服务消费者)提供一组链接,使客户端能通过链接将应用从一个状态改变为另一个状态。目前,只需要记住:链接是构成动态应用的非常有效的方式。 对此原则总结如下:任何可能的情况下,使用链接指引可以被标识的事物(资源)。也正是超链接造就了现在的Web。

分层系统

为了进一步改善与互联网规模这个需求相关的行为,我们添加了分层系统架构约束。分层系统风格通过限制组件的行为(即,每个组件只能“看到”与其相交互的相邻层),将架构分解为若干层级。通过将组件对系统的知识限制在单一层级内,为整个系统的复杂性设置了边界,并且提高了底层独立性。我们能够使用层级来封装遗留服务,使新的服务免受遗留客户端的影响,做法是将不常用功能转移到一个共享中间组件中,从而简化组件的实现。中间组件还能够通过支持跨多个网络和处理器的负载均衡,来改善系统的可伸缩性。

中间件: 中间件是一种独立的系统软件或服务程序,能够连接两个独立软件或系统。分布式应用软件借助于中间件能够在不同的技术之间共享资源。即:中间件使得若干个相互独立的系统,在各自都拥有着不同的接口的情况下,仍然能通过中间件来实现通信。执行中间件的一个关键途径是信息的传递。通过中间件,应用程序可以工作在多个平台及OS环境中。简而言之,中间件即桥梁。

分层系统的主要缺点:增加了数据处理的开销和延迟,因此降低了用户感知的性能。对于一个支持缓存架构约束的基于网络的系统来说,可以通过在中间层使用共享缓存所获得的好处来弥补这一缺点。

小结

REST架构风格由一组经过选择的架构约束组成,通过这些架构约束在候选架构上产生所期待的架构属性。尽管能够独立考虑其中每一个架构约束,但是根据它们在公共架构风格(common architectural styles)中的来源来对它们进行描述,使得我们理解选择它们背后的基础理论更加容易。

总结

本文试图从本质上来理解什么是REST。 我们首先从REST的起源说起,发现REST与Web之间的本质关系,并从Web的特性,得到REST本质上是一个分布式超媒体系统的应用层解决方案这一结论。接着我们对REST,即(Resource)Representational State Transfer(资源表述性状态转移)这个词组进行了详细分析,进一步得到了REST以资源为核心的架构风格。最后,我们对REST架构的五条必要约束条件进行进一步的阐述和说明,以便读者能够更为深刻地理解REST。 这篇文章到这里就算是结束了,笔者在写下这些内容的时候依然时时感到自己知识的匮乏,以致无法更为深刻地理解REST。笔者的这篇博客,既是希望能对自己所学做一个总结,也希望能给其他初学者带来一点帮助。文中若有理解不当的地方,欢迎批评指点。

参考资料