架构风格:你真的懂REST吗?

本文探讨如下几个问题:

  • 什么是REST
  • REST包含哪些约束
  • 什么是RESTful
  • 纯RESTful API的难点在哪里

如果你去搜索「什么是REST」的话,大部分情况下,你看到的基本都是RESTful!

这类内容主要说的是:

  • 资源URL应该怎么写
  • 要用GET来获取资源
  • 要用POST来新建资源
  • 要用PUT来更新资源
  • 要用DELETE来删除资源

而实际上REST并不是这些,或者说并不完全是这些!

什么是REST

REST全称Representational State Transfer,出自Roy Thomas Fielding博士的博士论文《Architectural Styles and
the Design of Network-based Software Architectures》第五章(Fielding博士的这篇论文会在后面单独讨论),一般翻译为「表述性状态转移」。

在论文的第6章第一节提到了为什么会取REST这么一个名字:

The name 「Representational State Transfer」 is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through the application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use.
「表述性状态转移」这个名称是为了唤起人们对于一个良好设计的 Web 应用如何运转的印象:一个由网页组成的网络(一个虚拟状态机),用户通过选择链接在应用中前进(状态迁移),导致下一个页面(应用的下一个状态的表述)被转移给用户,并且呈现给他们,以便他们来使用。

在「什么是架构模式和架构风格」一文中,提到过:

  • 架构风格是一组架构约束
  • REST是一种架构风格

所以,REST是一组架构约束

REST约束

REST是一个复合架构风格,即它包含了其它的架构风格!

REST约束包括:CS,无状态,分层,缓存,统一接口以及按需代码。其中「统一接口」是REST与其它架构风格的主要区别所在!「统一接口」包括了四个子约束:资源的识别,通过表述操作资源,自描述的消息,超媒体作为应用状态引擎!

《Architectural Styles and
the Design of Network-based Software Architectures》第五章给出了REST的完整推导过程,这里简单列出!

从一个没有约束的架构(NullStyle)开始,不断的添加约束,使此架构进化为需要的架构

Null Style:组件之间没有显著边界的系统,一个没有约束的架构

Client-Server

  • 约束:分离关注点(客户端接口和服务端数据存储)
  • 优势:客户端和服务端可以独立的进化。客户端可以与多个服务端通信,服务端可以方便的伸缩
  • 劣势:降低了性能

Stateless

  • 约束:通信必须在本质上是无状态的。从客户到服务器的每个请求都必须包含理解该请求所必需的所有信息,不能利用任何存储在服务器上的上下文,会话状态因此要全部保存在客户端
  • 优势:改善可见性,监视系统不必为了确定一个请求的全部性质而去查看该请求之外的多个请求。改善可靠性,减轻了从局部故障中恢复的任务量。改善可伸缩性,不必在多个请求之间保存状态,从而允许服务器组件迅速释放资源,并进一步简化其实现,因为服务器不必跨多个请求管理资源的使用。
  • 劣势:降低网络性能,由于不能将状态数据保存在服务器上的共享上下文中,因此增加了在一系列请求中发送的重复数据(每次交互的开销)。将应用状态放在客户端,降低了服务器对于一致的应用行为的控制

Cache

  • 约束:一个请求的响应中的数据被隐式地或显式地标记为可缓存的或不可缓存的
  • 优势:改善网络性能。如果响应是可缓存的,那么客户端缓存就可以为以后的相同请求重用这个响应的数据。可部分或全部消除一些交互,从而通过减少一系列交互的平均延迟时间,来提高效率、可伸缩性和用户可觉察的性能。
  • 劣势:可能降低可靠性,缓存中陈旧的数据与将请求直接发送到服务器得到的数据可能差别很大。

Uniform InterfaceREST核心特征):

  • 约束:组件之间要有一个统一的接口,包括四个子约束:
    • 资源的识别(identification of resources):每个资源都有各自的标识符。客户端在请求时需要指定该标识符。客户端所获取的是资源的表述,如HTML,XML 或 JSON 格式等。
    • 通过表述操作资源(manipulation of resources through representations):客户端操作的是资源的表述,而不是资源本身。
    • 自描述的消息(self-descriptive messages):每条消息都包含足够的信息来描述如何处理该消息。
    • 超媒体作为应用状态引擎(HATEOAS)(hypermedia as the engine of application state):客户端通过服务器提供的超媒体内容来了解如何操作表述,通过将对表述的操作提交到服务端,服务端来操作资源,继而改变服务端的状态。
  • 优势:简化整体架构,改善可见性,促进独立的可进化性
  • 劣势:降低了效率。信息都使用标准化的形式来转移,而不能使用特定于应用的需求的形式。

** Layered System**:

  • 约束:通过限制组件的行为,将架构分解为若干等级的层。
  • 优势:通过将组件对系统的知识限制在单一层内,为整个系统的复杂性设置了边界,并且提高了底层独立性。使用层来封装遗留的服务,使新的服务免受遗留客户端的影响;通过将不常用的功能转移到一个共享的中间组件中,从而简化组件的实现。中间组件还能够通过支持跨多个网络和处理器的负载均衡,来改善系统的可伸缩性
  • 劣势:增加了数据处理的开销和延迟,因此降低了用户可觉察的性能。可以通过在中间层使用共享缓存来弥补这一缺点。

Code-On-Demand(可选):

  • 约束:一个客户端组件知道如何访问一组资源,但不知道如何处理它们。它向一个远程服务器发送对于如何处理资源的代码的请求,接收这些代码,然后在本地执行这些代码
  • 优势:能够为一个已部署的客户端添加功能,改善了可扩展性和可配置性;当代码能够使它的动作适应于客户端的环境,并在本地与用户交互而不是通过远程交互时,能够得到更好的用户可觉察性能和效率。由于服务器将工作交给了客户端(否则将消耗服务器的资源),从而改善了服务器的可伸缩性
  • 劣势:由于需要管理求值环境,降低了简单性,在一些情况下可以通过简化静态的客户端功能得到补偿。最大的限制是由于服务器发送代码而不是简单的数据,因此缺乏可见性。如果客户端无法信任服务器,缺乏可见性会导致明显的部署问题。

总结

可能看完了推导,你还是不知道REST是什么!下面我通过一个列子来解释什么是「REST」!

举个例子

我们先看看Fielding博士为什么要设计REST?Fielding博士在论文里提到了,他设计REST是为了指导现代Web架构的设计与开发!基于REST,Fielding博士设计了HTTP1.1!也就是说,HTTP1.1是符合REST的!所以要搞懂REST,只要理解HTTP1.1就可以了!

如果你做过Web应用,那么CS,分层,无状态,缓存应该都很好理解,这里就不赘述了!按需代码就是类似Flash,Applet这类Web端应用,用以扩展Web功能的!

这里只说一下「统一接口」这个约束!

我们就以一个简单的HTTP请求来解释REST!

比如你输入www.abc.com时:

  • 你通过标示符来定位到www.abc.com网站的首页,abc站点将首页资源组装成Response信息返回到你的浏览器(资源的识别)
  • 返回的内容头里(HTTP header),告诉了浏览器该如何处理返回的信息(自描述的消息)
  • 返回的信息体(HTTP body),一般是HTML格式,它是你访问的首页资源的表述,里面包含了你能对这个表述进行的操作,比如能访问哪些链接,能提交哪些数据(超媒体作为应用状态引擎)
  • 你点击链接后,这是对表述的操作,你根本没有接触到真实的资源(通过表述操作资源)
  • 服务端接收到你的请求后,将对应链接的资源组装成Response信息返回(此时应用的状态就改变了)。
  • 浏览器接到返回后,将页面渲染出来,你可以进行下一步的操作。

什么是RESTful

上面解释了什么是「REST」!现在来解释一下什么是RESTful!

前面说了,REST是一组架构约束!那么,如果一个应用满足了REST约束,那么我们就可以称这个应用是RESTful的

虽然,很多系统自称是RESTful的,但是,实际上,绝大部分系统都不是RESTful的,或者不是完全RESTful的!Fielding博士对这个问题,发表了一篇博文,明确什么系统才能称为是符合我REST的!文中明确说明,系统必须满足HATEOAS约束才能称为是符合REST的!而HATEOAS很难实现!因为有人的参与!

为了缓解这个尴尬,Richardson 提出了「REST成熟度模型」。该模型把 REST 服务按照成熟度划分成 4 个层次:

  • 第一个层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP 和 XML-RPC 都属于此类。
  • 第二个层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表述。
  • 第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源。
  • 第四个层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表述中包含了链接信息。客户端可以根据链接来发现可以执行的动作。

从上述 REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服务是成熟度最高的,也是推荐的做法。

  • 对于不使用 HATEOAS 的 REST 服务,客户端和服务器的实现之间是紧密耦合的。客户端需要根据服务器提供的相关文档来了解所暴露的资源和对应的操作。当服务器发生了变化时,如修改了资源的 URI,客户端也需要进行相应的修改。
  • 而使用 HATEOAS 的 REST 服务中,客户端可以通过服务器提供的资源的表达来智能地发现可以执行的操作。当服务器发生了变化时,客户端并不需要做出修改,因为资源的 URI 和其他信息都是动态发现的。

现在大部分的自称是RESTful的系统,一般只能达到第三个层次!

HATEOAS的难点

HATEOAS为什么难以实现?是因为客户端无法决策!HTTP能实现RESTful,是因为浏览器只是将表述以及对资源的操作选项展示了出来,至于具体该如何操作,是由使用浏览器的人来决定的!也就是说,虽然服务端告诉了客户端操作的可选项,但是客户端没办法知道该选择什么!
网页浏览是有人的参与的,但是RESTful API是没有人参与的,这就导致RESTful API的客户端难以做出决定,该做什么!

可行的解决办法是:

  • 语义分析:客户端具有语义分析能力,能够自动的分析出后面需要执行哪个操作,目前这个很难实现
  • 客户端领域设计:客户端引入领域设计,在一个领域内,客户端和服务端达成共识,在特定领域内,目前有哪些操作。不过这个还是做不到只修改服务端就可以实现系统的进化。服务端进化后,客户端需要做对应的调整才可以完成整个系统的进化。

参考资料