REST —— HTTP 背后的架构风格(上)
很多人以为 REST 是基于 HTTP 协议的一种规范。但实际上,HTTP 才是基于 REST 的架构风格来设计的

起源
故事要从下面这个接口文档说起,我相信有多开发人员看到它都会有一种亲切感,因为在我们的日常工作中,常常会见到这样的接口文档,甚至写成这样都算是很好的了。
接口地址:/library/addBook
参数:
| 字段 | 数据类型 | 说明 |
|---|---|---|
| name | string | 书名 |
| isbn | string | 书号 |
| category | string | 图书分类号 |
| author | string | 作者 |
响应:
{
"errorCode": 0;
"msg": "";
"data": {
"id": 478594,
"name": "REST实战",
"isbn": "9787564129651",
"category": "TP393.092",
"author": "Jim Webber,Savas Parastatidis,Ian Robinson"
}
}
记得当年看到了这样的接口文档,我的第一反应是,它是错的。正确的应该如下。
添加图书接口:
POST /library/book
Content-Type: application/json
请求消息体样例:
{
"name": "REST实战",
"isbn": "9787564129651",
"category": "TP393.092",
"author": "Jim Webber, Savas Parastatidis"
}
请求消息体字段说明:
| 字段 | 取值范围 | 说明 |
|---|---|---|
| name | 字符串(1-100) | 书名 |
| isbn | 字符串(13) | 书号 |
| category | 字符串(2-16) | 图书分类号 1 |
| author | 字符串(1-200) | 作者 |
响应状态码:201 表示成功,其他表示失败。
响应消息体样例:
{
"id": 478594,
"name": "REST实战",
"isbn": "9787564129651",
"category": "TP393.092",
"author": "Jim Webber,Savas Parastatidis,Ian Robinson"
}
字段说明:id 表示图书的唯一标识,其他字段同请求字段。
后面的写法肯定是更详细一些,但这里最关键的区别是对响应的描述。前者的响应状态码往往都是 200,然后再根据消息体中的 errrorCode 来判断请求是否成功。而后者仅通过状态码就可以判断请求是否成功,而消息体中直接就是业务数据。
我相信很多人也经历过这两种风格谁对谁错的讨论,但是往往不会有理想的结果。我根据自己的所学所知,确信我所期望的风格是正确的,但是我经历过的公司,几乎都在用那个我认为是错误的风格。究竟是哪里出了问题?这一次,我决定要一探究竟。
既然我想的跟我实际经历的不同,那我就把眼光再放远一些,看看别人家都是怎么用的。看谁家的呢,当然是看那些优秀公司的,我第一个想到的是微软。现在的年轻人可能对微软已经没有过于崇拜的情结了,那是因为在前些年,微软完美地错过了移动互联网时代,经历了一段衰落期。但是自从纳德拉掌管微软后,开始去 Windows 化,开始拥抱开源,开始在自己的服务器上部署 Linux,让微软起死回生,再次成为一家优秀的公司。
这是微软的接口2:



我一看,这不就是我主张的风格嘛,我很欣慰。
下一个看谁呢,微软收购了 GitHub,那我看看 GitHub 的3:



我一看,还是我主张的风格,我很欣慰。再看亚马逊 Aws4 的:


还是这个风格,再找家国内的,七牛云CDN接口5:


还是这个风格。
我一共找了 Microsoft、Google、阿里云、腾讯云等十多家知名企业的开放接口文档(见文章最后表格),绝大多数都是这种风格。欣喜若狂,这说明我是对的啊!理论上这个风格是好的,实际中,几乎所有优秀的公司也都是这么用的。那问题又回来了,为什么我经历的公司都不是这么用的呢?是不是很奇怪。
这是什么原因呢?我觉得这是跟 2000 年后中国互联网以及之后的移动互联网急速发展的大的经济环境相关的。我还记得那个时候有一项技术非常流行,就是 Web Service。那是一种 RPC 风格的接口规范,内容非常复杂,还没等我学会,就已经过时了。但是它留下的遗产却一直沿用至今,就是本文一开始说的那种接口风格。
在那个中国互联网起步的时代,我们自然是有什么就用什么,关键是把事做成,那个年代的佼佼者在未来许多年里,都成为了各大互联网公司的骨干力量。而对于这种学术味道有些浓的技术问题,关注的人并不会太多,于是 RPC 风格就一直流传了下来。我们当年甚至把 HTTP 这个应用层协议翻译为“超媒体传输协议”,导致很多人就真的把它当做传输层协议来用了,以至于对它底层的架构思想一无所知。
前些年 REST 这个概念又被人们翻了出来,大说特说。从某种意义上来看,也是国内技术体系完善的一种表现。我们在架构层面上遇到了越来越多的问题,从而发现有很多“老先生”们提出的理论还是非常有价值的,REST 就是其中之一。这也正是我今天要讲的主题,REST 究竟是什么?
什么是 REST
Restful 接口,这好像是最近七八年火起来的,我们现在谈 REST 必带接口,但其实 REST 本身,跟接口没什么关系。因为 REST 是一种架构风格,是一种高级别的抽象,是对架构设计的抽象,而接口更多的是实现层面的事。REST 全称 Representational State Transfer,中文翻译为表述性状态转移,它是 Roy Fielding 博士 2000 年时在他自己的博士论文中提出来的一种软件架构风格。
我们来看下 Fielding 博士是谁:

- 美国计算机科学家;
- 加州大学欧文分校信息与计算机科学专业博士;
- HTTP、URI 技术架构规范的主要设计者之一;
- Apache HTTP Server 的主要开发者;
- Day Software、W3C 技术架构组成员;
- Apache 软件基金会的合作创始人和第一任主席;
- Adobe 公司首席科学家。
一看就是大牛,头衔很多,但是这里着重关注其中一点,他是 HTTP、URI 技术架构规范的主要设计者之一。为什么单说这个呢?我们知道 REST 是最近几年才火起来的,又经常跟 HTTP 接口一起被提到,所以很多人自然而然地以为 REST 是基于 HTTP 协议的一种规范。
但实际上,你看论文就会知道,HTTP 其实是基于 REST 的架构风格来设计的,而 REST 同时也是在 HTTP 的设计过程中不断完善进而形成的。他俩可以说的同时诞生的,REST 是底层方法论,HTTP 是 REST 的一种实现。而且,是目前被大众广泛所知的唯一的一个实现,但是却非常成功。
在过去的 6 年间,我们使用 REST 架构风格来指导现代 Web 架构的设计和开发。这个工作是与我所创作的 HTTP(超文本移交协议)和 URI(统一资源标识符)两个互联网规范共同完成的,这两个规范定义了在 Web 上进行交互的所有组件所使用的通用接口。
《架构风格与基于网络应用软件的架构设计》
REST 的第一版在 1995 年就有了,当时叫 HTTP 对象模型,HTTP/1.0 的 RFC1945 是 1996 年发布的,至今广泛使用 HTTP/1.1 的 RFC2068 在 1996 年发布,RFC2616 在 1999 年发布,提出 REST 的论文是 2000 年发表的,这个时间基本就都对上了。
论文
我们来看看 REST 究竟是什么,凭什么根据它设计出的 HTTP 协议,竟然能够支撑互联网 20 多年的急速发展。这篇论文是由李琨翻译成中文的,题为《架构风格与基于网络应用软件的架构设计》。论文本身还是挺有深度的,也很抽象,我也不敢说都理解了。所以我今天讲的并不是这篇论文里晦涩难懂的理论知识,而是这篇论文的论述思路,不过相信我,这个思路才是最有价值的东西,你可以认为它就是 REST 的核心思想。
(以下对论文的介绍可能会略显枯燥,但请耐心读完,我们今天的重点不是论文本身,而是我后面要讲的论述思路)
论文一共六章:
- 第一章 软件架构
- 第二章 基于网络应用的架构
- 第三章 基于网络应用的架构风格
- 第四章 设计 Web 架构:问题与领域
- 第五章 表述性状态转移
- 第六章 经验与评估
第一章 软件架构
这里作者给出了很多术语的定义,这些术语都是后面论文用到的,一开篇作者就把它都交代清楚了。
第二章 基于网络应用的架构
作者在这章中首先强调了我们讨论的范围,是基于网络的应用软件架构。
说基于网络是为了区分于分布式,这里要注意,这篇论文是上个世纪写的,我们肯定不能用现有的概念去理解,要还原到当时的软件领域。这里说的基于网络,指的是互联网,实际上是为了强调,我们赖以通信的是物理网络,这其中存在着很多不确定性,比如延时,比如断网。
说应用软件,是说不是操作系统,不是网络软件,而是应用软件。我们关心用户的意图,关心应用状态,而不是仅仅是把数据从一端传输到另一端。
在这个特定范围下,我们开发软件的目的和标准是什么呢?两个方面,功能性的和非功能性的。
功能性就不用说了,我们开发软件就是要解决特定的问题,必须实现功能需求,这不是这篇论文的重点。(对于功能性需求,DDD 在架构层面上可以给出很好的方案)
而非功能性的,也可以说是软件质量,我们要达到什么指标呢?论文给出了如下七点,并称之为架构属性:
- 性能(Performance)
-
网络性能(Network Performance)
-
用户感知的性能(User-perceived Performance)
-
网络效率(Network Efficiency)
-
可伸缩性(Scalability)
-
简单性(Simplicity)
-
可修改性(Modifiability)
-
可进化性(Evolvability)
-
可扩展性(Extensibility)
-
可制定性(Customizability)
-
可重用性(Reusability)
-
可见性(Visibility)
-
可移植性(Portability)
-
可靠性(Reliability)
我们做架构设计时,除了功能性的要求外,在性能质量方面的要求,基本都在这里面了。
第三章 基于网络应用的架构风格
这一章里,作者介绍了五大类架构风格,所谓的架构风格,就是我们应该怎么设计软件,这里说的风格一词,不是指个性化的意思,它只是一种分类组合方式。
- 数据流风格(Data-flow Styles)
-
管道和过滤器(Pipe and Filter, PF)
-
统一管道和过滤器(Uniform Pipe and Filter, UPF)
-
复制风格(Replication Styles)
-
复制仓库(Replicated Repository, RR)
-
缓存(Cache, $)
-
分层风格(Hierarchical Styles)
-
客户-服务器(Client-Server, CS)
-
分层系统(Layered System, LS)和分层-客户-服务器(Layered-Client-Server, LCS)
-
客户-无状态-服务器(Client-Stateless-Server, CSS)
-
客户-缓存-无状态-服务器(Client-Cache-Stateless-Server, C$SS)
-
分层-客户-缓存-无状态-服务器(Layered-Client-Cache-Stateless-Server, LC$SS)
-
远程会话(Remote Session, RS)
-
远程数据访问(Remote Data Accessm, RDA)
-
移动代码风格(Mobile Code Styles)
-
虚拟机(Virtual Machine, VM)
-
远程求值(Remote Evaluation, REV)
-
按需代码(Code on Demand, COD)
-
分层-按需代码-客户-缓存-无状态-服务器(Layered-Code-on-Demand-Client-Cache-Stateless-Server, LCODC$SS)
-
移动代理(Mobile Agent, MA)
-
点对点风格(Peer-to-Peer Styles)
-
基于事件的集成(Event-based Integration, EBI)
-
C2
-
分布式对象(Distributed Objects, DO)
-
被代理的分布式对象(Brokered Distributed Objects, BDO)
然后,他分析了每一种风格对架构属性有怎么样的影响,这里有一张表,行表头是架构属性,列表头是架构风格,加号表示促进作用,减号表示削减作用:

以框住的这行——分层系统,来举个例子。这个是大家比较熟悉的一种架构风格,很好理解。有句话经常听到,也不知谁说的:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。那么对于分层系统来说,用户感知的性能就会被削减,因为增加了一层,必定会有时间的损耗。而可伸缩性、可进化性、可重用性和可移植性都会增强,因为分层后,我们可以对每一层做单独的升级、扩容等操作。
从论文开篇,到看到这张表,你隐约可以感受到,设计软件的方法,变得清晰了:
- 分析需求,包括功能性的和非功能性的;
- 确定关注的架构属性;
- 找到对应的有促进作用的架构风格;
- 依据架构风格进行设计。
第四章 设计 Web 架构 问题与领悟
这一章,作者分析了设计 Web 架构时所需要关注和解决的问题,也就是明确 Web 架构的需求。比如我的 Web 系统使用起来必须很方便,不能过分复杂,要具备可扩展性,才能应对未来的变化,要支持分布式超媒体,要能适应未来无法预计的规模增长。明确了这些关注点,我们才能选择适合的设计风格。
第五章 表述性状态转移
前面铺垫了这么多,在这一章终于要推导出 REST 架构风格了。整个推导过程,可以用下图来表示:

图最上方的黑点表示空风格,也就是什么都没有。然后依据之前对 Web 架构的需求,找到对应的架构属性,根据对不同属性的要求,一步步地添加相应的架构风格,最终形成的组合,就被称之为 REST。它包括了:
客户-服务器 + 无状态 + 缓存 + 统一接口 + 分层系统 + 按需代码。
要想完全看明白这张图,还是要通篇阅读论文,我可没办法两三句就把它说清。
第六章 经验与评估
最后第六章,作者讲了自己在制定 HTTP、URI 规范以及开发 Apache HTTP 服务器的过程中,遇到的经验与教训,这算是在实践 REST 架构风格过程中的积累了,我就不多介绍了。
论述思路
前面说了,本文着重要介绍的,是作者的论述思路。我们再向前回顾一下,作者是如何一步步推导出 REST 的。
1. 统一语言
首先定义统一的语言,先是对各种属性进行定义,让每个人的理解都能在同一个层面上。
2. 限定范围
明确论文的边界,交代清楚我们在讨论什么,我们不关心什么。
3. 明确需求
分析需求,明确我们所做的软件要解决什么问题,满足哪些需求。
4. 确定目标
根据需求,确定我们要达到的各项指标都是什么。
5. 适配方法
为了达到这些目的,满足我们的需求,需要采用什么方法。
6. 归纳组合
把这些方法有机地组合起来就是 REST。

仔细去思考这个过程,你会发现它不只是 REST 的推导过程,它也能够成为我们日常设计任何软件系统的方法和手段。这也就是我所说的,这篇论文的价值所在,他为我们设计软件系统提供了一套科学的方法论。
我们在日常的软件设计中,常常使用的是归纳法,就是说我之前是怎么做的,那么根据以前的经验,来决定我现在要怎么做。这么做的好处是简单、快,有点儿复制粘贴的感觉。但缺点是,我们现在做的东西毕竟和之前做过的是有差异的,如果因为追求简单和快,而低估了这种差异的影响,那么很可能会设计出不合格的架构。
而 REST 的推导过程是一种演绎法,他是从 0 开始,根据我们的诉求与目的,来选择架构风格,有什么样的架构属性要求,就添加对应的什么样的架构风格,那么最后一定能设计出满足我们要求的架构。相应的,它的缺点就是,在初始阶段肯定会产生更多的工作量。
不过在实际工作中,我们肯定不会是使用单一的方法,我在这里讨论这些方法的目的,就是为了避免我们使用单一的思维模式,一定注意到,要将归纳法和演绎法结合起来使用,寻求最好的平衡。
前面对 REST 做了概念性的介绍,也分析了 REST 论文的论述思路。大家如果想对 REST 有更深入的理解,一定要去阅读论文,甚至要阅读很多遍,才能有所收获。
下面我们结合本文一开始提出的两种接口风格,来看看 REST 是如何指导具体实践的。
(未完待续)
参考
Footnotes
-
图书分类号: https://www.clcindex.com/ ↩
-
这是微软的接口: https://docs.microsoft.com/en-us/rest/api/apimanagement/2019-12-01/producttag/assigntoproduct ↩
-
GitHub 的: https://developer.github.com/v3/issues/ ↩
-
亚马逊 Aws: https://docs.aws.amazon.com/apigateway/api-reference/link-relation/integration-update/ ↩
-
七牛云CDN接口: https://developer.qiniu.com/fusion/api/4246/the-domain-name ↩