聊聊RESTful - 科普篇

REST

一休: Rest, have a rest.
Howard: Yeah, I have known a real REST.

(本文为“聊聊RESTful”系列文章第一篇,后续文章可通过篇尾链接查看)

前言

REST是一个非常值得学习的架构设计理论,因此我决定编写一系列的文章来跟大家聊一聊。

今年网上关于RESTful的声音少了很多,而前两年这是一个相当火的概念,就像现在的微服务,都快被写烂了。网上讨论微服务的文章水平参差不齐,而对于RESTful则更是如此。所以今天先跟大家做一个科普性的分享,目的是排除网上大量的干扰信息,给大家讲讲什么是真正的RESTful。

可能有些人还没听过RESTful,那么看了我这篇文章后,相信你一定会有动力去好好了解一下。我想大部分人跟我一样,都是看到了一篇文章,说现在流行RESTful风格的接口设计,要求接口命名要使用名词,然后数据的唯一标识要放在请求路径上,请求方法不能只用GET、POST,还要用PUT和DELETE,好一些的文章会提到要以资源为核心来设计接口。那么这些文章写得对吗?我这样一问好像他们说的不对似的,其实他们说的都对,基于REST理论的开发,确实会有这些要求,谁要说不对,那绝对是冤枉他们。但是,如果仅仅认为他们写的文章是对的,那确实会让我们对RESTful产生误解。因为大部分文章说的只是表象,而没有触及本质,如果你只知道表象而没有理解本质,那就亏大了。那REST的本质是什么呢,就让我们追根溯源,先来看看REST是如何被创造出来的。

REST介绍

REST全称Representational State Transfer,翻译成中文是表述性状态转移,它是Roy Fielding博士于2000年在他的博士论文中提出来的一种软件架构风格。我们所说的RESTful就是REST风格。这里我们注意,REST是一种软件架构风格,而不是简单的接口设计风格,这点后面还会再说。那么作者为什么要写这样一篇论文来讲REST呢,我们先来看下这位作者的头衔:

  • 美国计算机科学家
  • 加州大学欧文分校信息与计算机科学专业博士
  • HTTP、URI技术架构规范的主要设计者
  • Apache HTTPServer的主要开发者
  • Day Software、W3C技术架构组成员
  • Apache软件基金会的合作创始人和第一任主席
  • Adobe公司首席科学家

原来是位技术大牛,科学家级别的人物,那他的论文必然是言之有物的,我们再来看下他讲的REST的背景:

  • 第一版创作于1994年10月和1995年8月之间
  • 最早称为:HTTP对象模型(HTTP object model)
  • 2000年,Fielding博士在其发表的博士论文中正式提出了REST的概念

那么从1994年创作年到2000年这发表段时间,这位博士在做什么呢,他的论文中有写到:

Over the past six years, the REST architectural style has been used to guide the design and development of the architecture for the modern Web, as presented in Chapter 6. This work was done in conjunction with my authoring of the Internet standards for the Hypertext Transfer Protocol (HTTP) and Uniform Resource Identifiers (URI), the two specifications that define the generic interface used by all component interactions on the Web.

这样,我们大概能够想象到,REST就是作者在制定HTTP和URI两套规范时,归纳总结出的,其背后的方法论,而这个方法论又反过来指导了他们对两个规范的制定,以及对现代Web架构的开发设计。介绍这些信息,就是想更形象的告诉大家REST是一种软件架构风格,是一种方法论,而不是我们随便看几篇文章就以为它是的接口设计风格,这之间是有天壤之别的。

明确了REST的性质后,我们再来看看,为什么我说他值得学习,希望介绍给大家。我就从这篇论文讲起。论文标题是《Architectural Styles and
the Design of Network-based Software Architectures》
,中文是《架构风格与基于网络应用软件的架构设计》。我来根据论文目录一章一章的来给大家粗略地做下介绍:

第一章 软件架构
作者在这里给出了很多软件架构的术语的定义,为他后面的内容做了理论铺垫。

第二章 基于网络应用的架构
这里作者着重强调了什么是基于网络,他把基于网络和分布式进行了区别对比,把应用软件和网络软件进行了区分对比,然后讲解了基于网络的应用所关注的架构属性,这些架构属性就是本章的重点,它们分别是:

  1. 性能(Performance)
    1.1 网络性能(Network Performance)
    1.2 用户感知的性能(User-perceived Performance)
    1.3 网络效率(Network Efficiency)
  2. 可伸缩性(Scalability)
  3. 简单性(Simplicity)
  4. 可修改性(Modifiability)
    4.1 可进化性(Evolvability)
    4.2 可扩展性(Extensibility)
    4.3 可制定性(Customizability)
    4.4 可重用性(Reusability)
  5. 可见性(Visibility)
  6. 可移植性(Portability)
  7. 可靠性(Reliability)

当我们对基于网络的应用做架构设计时,我们的关注点,就是以上这些架构属性,当然,不同的应用可能关注的侧重点不同,但大都在这范围之内。
我们暂且记下这些架构属性,再来看下一章。

第三章 基于网络应用的架构风格
这一章作者阐述了几类有代表性的架构风格

  1. 数据流风格(Data-flow Styles)
    1.1 管道和过滤器(Pipe and Filter, PF)
    1.2 统一管道和过滤器(Uniform Pipe and Filter, UPF)
  2. 复制风格(Replication Styles)
    2.1 复制仓库(Replicated Repository, RR)
    2.2 缓存(Cache, $)
  3. 分层风格(Hierarchical Styles)
    3.1 客户-服务器(Client-Server, CS)
    3.2 分层系统(Layered System, LS)和分层-客户-服务器(Layered-Client-Server, LCS)
    3.3 客户-无状态-服务器(Client-Stateless-Server, CSS)
    3.4 客户-缓存-无状态-服务器(Client-Cache-Stateless-Server, C$SS)
    3.5 分层-客户-缓存-无状态-服务器(Layered-Client-Cache-Stateless-Server, LC$SS)
    3.6 远程会话(Remote Session, RS)
    3.7 远程数据访问(Remote Data Accessm, RDA)
  4. 移动代码风格(Mobile Code Styles)
    4.1 虚拟机(Virtual Machine, VM)
    4.2 远程求值(Remote Evaluation, REV)
    4.3 按需代码(Code on Demand, COD)
    4.4 分层-按需代码-客户-缓存-无状态-服务器(Layered-Code-on-Demand-Client-Cache-Stateless-Server, LCODC$SS)
    4.5 移动代理(Mobile Agent, MA)
  5. 点对点风格(Peer-to-Peer Styles)
    5.1 基于事件的集成(Event-based Integration, EBI)
    5.2 C2
    5.3 分布式对象(Distributed Objects, DO)
    5.4 被代理的分布式对象(Brokered Distributed Objects, BDO)

这一章的重点,就是每种架构风格对上一章中所说的架构属性的影响。举例来说,分层风格下的CS,它可以提高可伸缩性,提高简单性,提高可进化性。分类风格下LS,它也可以提高可伸缩性和可进化性,但是却会降低用户感知的性能。作者把这些具有代表性的架构风格对基于网络应用要关注的架构属性有什么样的影响做了一一说明。下面这张表就这些影响的总结:
架构风格对架构属性的影响

第四章 设计Web架构:问题与领悟
这一章作者总结了Web系统的需求以及我们要如何应对互联网的飞速发展与变化,为下一章推导REST风格做了铺垫。

第五章 表属性状态转移
前面做了这么多铺垫,到这一章才推导出了REST架构风格。上一章讲了Web系统的各种需求,那么我们就针对这些需求,看看什么样的架构风格可以解决他们。作者这里使用了叠加的方式,将客户-服务器无状态缓存统一接口分层系统按需代码这些架构风格逐个相加,而相加的结果就是我们的REST风格,论文里总结到:

REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components to reduce interaction latency, enforce security, and encapsulate legacy systems.

推导出REST后本章还介绍了REST的元素和视图,我们常说的资源,表述的概念,就是在这里面提到的,这里我暂不细说。

第六章 经验与评估
前面重点的都说完了,这里作者讲述了自己在制定HTTP、URI规范以及开发Apache HTTP服务器的过程中,遇到的经验与教训。

以上就是我对这篇论文的粗略介绍,不知大家看后是什么感觉。似乎仍旧不知道讲的什么吧,在我看来这非常正常,如果你没有读过这篇论文并且有较好的理解的话,光看这个粗略的介绍,确实无法理解其中的奥妙,他毕竟是篇博士论文,是一篇学术性很强的文章,没有一定的架构经验都很难读懂。但是,虽然看不懂,至少我们能看出一个问题,REST并不是简单的经验总结或者是最佳实践,而是通过一整套科学严谨的理论依据推导出来的一种方法论。我觉得这一点才是REST的精华之所在。我们做程序开发,做架构设计,很多时候都是凭着经验甚至凭着感觉。就像是归纳法,做A系统是这么做的,做B系统也是这么做的,那么做C系统,我也这么做。可实际上C和AB真的一样吗,没准哪天你就遇到黑天鹅了。而REST用的是演绎法,比如做系统A,那么需要根据A的需求来分析,发现系统A关注的是架构属性B,而论文中的结论是架构风格C对架构属性B有很大的积极作用,那么我们推导出,用架构风格C来开发系统A将满足需求上系统A对架构属性B的要求,这个结论就是确定无误的。所以我觉得这种用推导的方式来做架构设计的方法论,才是REST最有价值的部分,他不光是对架构设计,对我们做很多事情都有指导意义。

按上文所说,我好像把REST放在了一个学术性很强,又高高在上的位置,但其实他是很接地气的。前面说了很多次,作者是HTTP规范的主要设计者,HTTP是什么呢,毫不夸张的说是当今互联网的基石,你只要上网就离不开HTTP,而HTTP恰恰就是基于REST理论而设计出的规范,也就是说我们每天上网都在接触、应用和感受着REST给我们带来的便利。这一点也充分说明了REST的先进性。HTTP从上个世纪诞生至今,这20年间互联网经历了无数个翻天覆地的变化,而HTTP,尤其是HTTP1.1,却能以不变应万变,始终如一的为互联网提供良好的支持,虽然现在发布了HTTP2,但HTTP1.1仍能适应绝大多数的互联网需求。

说到这里,相信大家对REST的优点应该可以理解了。用REST设计制定的HTTP适应了互联网20年的变化,有了他你还怕产品经理的需求变更吗?这个比喻可能不太恰当,不过却说明一个道理,掌握了REST,你就能更好的应对网络软件的各种变化,设计出可用性可扩展性更好的软件。

这里我再说明一下,前面对这篇论文的介绍,对REST的介绍,其实是非常粗浅的,论文中有很多非常重要的理论和概念我都没有写到,为什么?因为写了你也看不懂,这些东西一定要静下心来仔细阅读研究,不是我一篇文章就能讲明白的,况且我自己的理解也是有限的,我也在不断的学习研究过程中。

REST成熟度模型

看到这里,有些读者会有一些顾虑,会觉得REST好像很复杂,我写了几千字还说不明白,必须要自己看论文来研究,学习成本很高,而他平时开发的软件并非是HTTP这种级别的项目,可能就是某个行业里已经很成熟的项目,那么是否值得我们花大量精力还学习呢。我还是一开始那句话,REST是非常值得学习的。为什么呢,因为REST不光是用来设计HTTP这种级别的软件的,任何基于网络的软件应用都可以从REST架构风格中获得有益的帮助。你不需要把REST研究非常透彻,才能应用它,你理解多少,就可以应用多少,在我们常见的企业级开发中,一些业务研发中,应用到的REST的相关知识相对论文里讨论的,还是相当浅显易懂的。

所以,我再向大家介绍另一个概念,REST成熟度模型。这是由Leonard Richardson给出的概念,我没有找到Leonard的太多资料,只知道他写过一本《Ruby Cookbook》,还开发过很多开源库,跟REST相关的,跟人合著了过《RESTful Web Services》和《RESTful Web APIs》两本书。作者在这个模型中,将REST分为从0到3,四个等级,来渐进式的告诉大家什么是真正的REST。下面我来逐级介绍。

第0级,所有请求都使用POST,并请求某一个特定的URI,我们很多基于HTTP的RPC架构就是这样的,他把HTTP当做了传输层协议来使用,而忽略了它作为应用层协议的特性。
这里我讲一下HTTP,HTTP全名HyperText Transfer Protocol,他是7层网络模型中,最上层的应用层协议,但是由于早年间HTTP被翻译成了超文本传输协议,以至于国内很多人都误以为他是传输层协议,再加上国内外也确实有很多技术架构把他当做传输层协议来用,比如早些年的Web service,因此HTTP作为应用层协议也就常常被忽略了,而如今,更多的开发者会把HTTP翻译成超文本转移协议或者超文本移交协议
REST成熟度模型的第0级中把HTTP当做传输层协议来用,让他只负责信息的传输,而不参与业务逻辑,这样一来HTTP的头部相当于失去了作用,也让各种逻辑与语义都混杂在了消息体中。

第1级,跟第0级相比,这一级不再只使用一个URI了,而是根据实际业务,抽象出不同的资源,以资源为核心设计多个URI来使用,比如查询商品信息,则URI为/commodity,查询购物车,则URI为/shoppingcart

第2级,与第1级相比,除了有资源的概念,使用了不同的URI,在这一级里还使用了不同的HTTP方法,通常来说是使用POST、DELETE、PUT、GET来表示增删改查的操作(这种对应方式并非的准确的,以后的文章会做介绍),同时对HTTP头部的信息也做了相应的处理,在处理响应时也会充分应用到HTTP的状态码来表示各种业务情况。
例如,添加一本图书的信息,请求是POST /book,如果响应状态码是201 Created,说明这个资源已经被创建,如果是400 Bad Request说明我提交的信息可能有错,如果是500 Internal Server Error说明服务端出现了程序异常。
在我们日常开发中,第2级是最重要的,现在行业内所谓的RESTful接口,做的好的也就是第2级的水平了。

第3级,使用了超媒体来作为应用状态引擎,或者说是超媒体驱动,这一级才是真正的REST。如何理解超媒体驱动呢,我们再来举个例子。还是以添加图书为例,对于第2级成熟度模型来说,我们一般会这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 以下仅为示例,因此图书信息也设计的极为简单
# 请求
POST /book HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"isbn": "9780321125217",
"name": "Domain-Driven Design",
"authorId": "A201701007"
}
# 响应
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "B20170711007",
"isbn": "9780321125217",
"name": "Howard",
"authorId": "A201701007"
}

当我们想查询这本书的详细信息时,我们会通过响应信息里的唯一标识id来拼出查询地址:/book/B20170711007,再发起GET请求查询

而对于第3级成熟度模型来说,效果则会是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 请求
POST /book HTTP/1.1
Content-Type: application/ld+json
{
"@context": "http://xxx/contexts/book.jsonld",
"isbn": "9780321125217",
"name": "Domain-Driven Design",
"authorId": "A201701007"
}
# 响应
HTTP/1.1 201 Created
Location: http://xxx/book/B20170711007

我们看到,在这个响应中没有消息体,但有一个响应头Location,他的值则表示我们刚刚添加的资源的链接地址,我们可以对这个地址发出GET请求来查询他的表述信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 请求
GET /book/B20170711007 HTTP/1.1
Accept: application/ld+json
# 响应
HTTP/1.1 200 Ok
Content-Type: application/ld+json
{
"@context": "http://xxx/contexts/book.jsonld",
"@id": "http://xxx/book/B20170711007",
"id": "B20170711007",
"isbn": "9780321125217",
"name": "Domain-Driven Design",
"author" {
"@id": "http://xxx/author/A201701007",
"id": "A201701007",
"name": "Eric Evans"
}
}

火眼金睛的你可能发现了这里的json好像有些不同,@id是什么鬼,请大家注意头部信息的中的媒体类型application/ld+json,这种媒体类型称为JSON for Linking Data,简称JSON-LD,是一种支持超媒体的媒体类型,具体语法我这里不做介绍,感兴趣的同学可以查阅相关资料学习。但是就算不懂语法,我们也能从上面的例子中看出所谓的超媒体。我们向图书接口发起POST请求,添加一本书,然后响应头中给出这本书的URL,我们再对这个URL发起GET请求来查询这本书的表述信息,表述信息中包括了作者信息,而如果我们想查看详细的作者信息便可以根据 author > @id 中的URL再发起GET请求,整个过程我们只知道最开始的/book,甚至这个地址也是上一次请求的响应中给出的。这就是所谓的超媒体驱动。而具有超媒体驱动的第3级,才是真正的REST。

前面说到行业内很多REST接口其实都只达到了第2级,那么有没有第3级的呢,也许是我孤陋寡闻,不过成熟可用的API我还没见过第3级的,但是有一个我们几乎每天都能接触到的技术却是第3级,那就是HTML。举个极端的例子,我们用浏览器上网只要知道百度的地址就可以了,其他的所有操作都是基于搜索结果也就是响应信息,一步一步点击而完成的。大家可以想象一下这个场景,这个就是超媒体驱动。

我刚才说第3级才是真正的REST,而我们所谓的RESTful接口,做得好的也就是第2级,那么第2级是不是就不算REST了呢?REST的作者Fielding博士写了一篇文章,题目是“REST APIs must be hypertext-driven”,也就是说REST接口必须是超文本驱动的,作者以此来反驳了很多自称的RESTful接口。但是,尽管如此,大家依旧把没有使用超文本驱动的接口称为RESTful接口,作者也没办法。所以,只要我们心里清楚真正的REST是什么,叫不叫RESTful接口也就不那么重要了。

综上,REST成熟度模型对我们有两点意义:

  1. 他通过渐进的方式告诉我们如何才能达到真正的REST水平。
  2. 他可以指导我们在实际工作中渐进式的应用REST理论,就像刚才说的,第2级,已经不错了。

资料推荐

看到这里,相信大家对REST已经有了学习热情,那要如何学呢,我来给大家提点儿建议,推荐一些资料。

前面介绍了Fielding的论文,所以说这篇论文是必须要学的,但是,不建议大家一上来就去看他,因为不太容易理解,我们可以先从简单的看起,推荐大家一本书《REST实战》。看了书之后,你对REST有了自己的理解了,然后再结合前面说的REST成熟度模型,你就能指导自己做一些REST风格的架构设计了,有了一定的经验,再来看Fielding的博士论文,就会轻松很多了。如果你英语不是很好可以看翻译版的。《REST实战》和博士论文的翻译者是同一个人,叫李琨,在书和论文的译者序的部分他会提到一个QQ群,叫REST实战讨论组,这个群对我理解REST有很大的帮助,大家有兴趣的也可以加进来。然后李琨老师还为论文写了一篇导读,大家在看论文前也可以去看下,对我们理解论文会很有帮助。

今天就跟大家聊到这里,后面我会继续写一些RESTful相关的文章,从实践的角度继续跟大家聊聊RESTful,欢迎大家扫描下方二维码,关注我的公众号“就浩这口”,收听这篇文章的语音版,并持续关注后续内容,我们下期再见。

传送门

陈浩 wechat
欢迎扫描上面二维码,关注我的公众号“就浩这口”
感谢您的支持,我会创作更多更好的内容