REST本身是风格,不是规范
对于 REST 本身,以及它的四级的 Uniform interface 的介绍,遍地开花,本文不作冗余的介绍。 较为权威,但不一定公正的描述,可以通过前面的链接直达 Wikipedia。
REST只是一种实现风格,不是一个强制规范,更连连RFC规范都算不上。 ( RFC7231 作为参考文献,提到了一下 )
由此,REST的历史可以追述到2000年,也就是那篇很多人听过,但可能很多人没读过的 博士论文 ,它来自Roy Thomas Fielding。
2000年,离现在也有段时日了,如今REST怎么就突然走红了呢?几个正常人都能够想到的因素是:它借助了日益「完善的HTTP协议」、丰富的工具链,网络的日益发达。
对于HTTP协议,最权威的描述,莫过于RFC对其制定的规范了,有兴趣的可以去这里看下我对它的 简要概览 。
可以说,REST对于HTTP协议的理解是比一般人要深入的,也使得URL的设计产生了「设计感」。
REST的优势
丰富的工具与运行时环境
例如,cURL (最好的「全能」URL处理命令行工具) , POSTMAN (最好的GUI调试HTTP工具) , NGINX (最有希望成为市场第一的HTTP Server) , Apache HTTP Server (稳居HTTP Server排名第一的常青树)
由于HTTP协议,还能够非常好的通过部署SSL证书,实现HTTPS协议,REST的安全性也能够得到一定的提升,比如不安全链路下的内容防篡改。
无状态
这看上去是REST的一个优势,但使用REST的大多数服务,都是有状态的。无状态的设计,则是把对状态管理的责任,甩给了第三方平台。
也就是说,无状态,更多的是对RPC通信协议的「无状态化设计」。
服务对于无状态化的处理,大致有三种方式:
- 状态信息通过请求,也就是入参传递
- 状态信息通过外置的数据源存储,可能是缓存,也可能是数据库
- 托管给运行时环境的容器,例如J2EE应用服务器集群
Uniform interface
这一点,是REST最只管,最富设计感的特性,它可以使得一个URL表达一个客观对象,使得URL富有现实含义。
并且能够借助于,HTTP 协议的4个基本Method对这个对象的状态进行维护。
最常见、入门级的例子,是对于 Order , 或是 Book ,再或是 Article 这样的实体进行维护,比如以下四个HTTP Method:
- POST : https://yanjing.wang/articles/
- GET : https://yanjing.wang/articles/design-an-rest-like-rpc-over-http11
- DELETE : https://yanjing.wang/article/design-an-rest-like-rpc-over-http11
- PUT : https://yanjing.wang/article/design-an-rest-like-rpc-over-http11
URL一致性,使得这些URL看上去「几乎完美」,它及表达了目标对象的所述域,也表达了分类,还为目标对象分配了唯一性的标识。
REST的不足
不支持分布式事务
关于REST的绝大多数文章里,都没有设计「分布式事务」,这个复杂、难解的问题。
对于「分布式事务」,我的一个观点是——这是个伪命题。分布式事务并不存在,只存在本地事务。
在分布式环境下,保障多个点数据一致的算法,有Paxos以及Raft。
而要保障多个对一个事件所进行的操作都是「一致的」,则需要协调器的参与。 这里的「一致的」,所指的并不一定是保证两个被操作数据的一致性,而是「同目标」。
例如,对一篇 Article 进行 POST 动作时,常见的,还会更新这篇 Article 所标注的 Tags 和 Category 索引。 更新 Tags 与 Category 则是同目标,不同结果的两个子操作。
在复杂场景下,对 Article , Tags , Category 的维护,可能分属于三个独立的系统。 这需要有一个协调器进行分布式协调,才能保障一篇新的 Article 被正确发布,保障文章与两个索引的一致性。
使用场景的自述型
HTTP协议是被广泛使用的,它经常出现在普通人的视野中,普通人可能不了解HTTP所代表的全称,但在记忆网址,也就是域名的时候,不会忘记HTTP这个「符号」。
那么,一个HTTP地址,所代表的,是一个可被浏览器访问,并能够通过浏览器渲染出一个Web页面的地址,还是一个仅仅传输JSON格式报文的API地址呢?
很显然,类似 https://yanjing.wang/articles/design-an-rest-like-rpc-over-http11 这样的地址,难以被分辨。 一般都需要通过浏览器进行尝试,或者通过阅读配套的文档才能够了解其使用场景。
在传统的软件工程项目中,在一个域名下的Application,经常会既提供用于浏览器访问的Page URL,也提供用于浏览器请求后端数据的API URL,这使得URL的设计更为复杂。
另外常见的做法,是为Page以及数据提供不同的访问域名,例如用域名 https://api.yanjiong.wang/v1 专门提供API服务,用域名 https://yanjong.wang 专门提供Page服务。
这些应用实践方法,都会为软件工程带来更多的复杂性,更高的集成难度。
报文的承载能力
需要注意的是,一些HTTP方法,是不支持 Request Body 进行数据传输的。
虽然,作为客户端,可以在准备报文时,准备好 Request Body 进行发送,但RFC规范,则对这种情况作出规定——服务端可以忽略这些不应该出现的 Request Body。
为人熟知的HTTP Method,就是 GET , 它并不支持客户端发送 Request Body , 而仅仅能够从 Request URL 中传输请求数据。
不太为人熟知的 DELETE 也与 GET 类似,不支持 Request Body。
复杂场景下的RPC协议与报文的要求
URL要求
- 需要能够秉承 REST 风格的优势,也就是 Uniform interface
- 能够直观的区分两次不同的请求,
- 既能够表达对客观单个实体的操作,也能够表达对于一组实体的「事务性操作」
报文要求
- 以JSON为基础,在报文体较大时,不解析报文体保障性能。
- 能够提供相关扩展机制,对参数进行非JSON的序列化
- 能够快速的进行「回滚」,也就是撤销动作
追踪要求
- 能够在分布式条件下,对请求的调用链路进行跨系统无缝跟踪
- 能够区分首次与重试不同的请求
- 便于日志记录,还原故障现场的上下文环境
版本要求
- 能够区分不同的版本便于路由
- 能够由客户端指定兼容版本区间
查询要求
- 提供处理过程中的状态查询能力
- 提供处理结果的查询能力
- 能够实时展示调用进度
安全要求
- 提供报文传输安全性保护能力
- 提供双向身份认证能力
- 流量控制
- 应急隔离