REST Richardson 成熟度模型
REST Richardson 成熟度模型
Richardson 服务成熟度模型描述 REST 服务的规范性和发展路径的模型,他把 REST 应用划分为 4 个层次:
level0
仅把 http 作为传输通道
level1
面向资源为中心
level2
利用 http 动作语义
level3
利用超媒体控制
这四个层次描述了规范的 REST 服务实现的步骤和过程。尽管整个模型分为 4 个层次,但是一般在实践中,能达到第 3 层次,部分到达第 4 层次,基本能让整个 api 结构能保持清晰和一致。一般来说除了 api 的部分入口会发生变化,其它大多数资源链接会随着 api 入口对应变化,level3 实现部分即可。
说一下对这 4 个层次的理解:
level0
level0 规定使用 http 作为传输方式,这统一了传输方式的选择,同时也是后续更高层次的基础。
level0 并没有规定如何利用 http ,因此在 level0 时服务调度的风格可以是多种方式的,表示方式也可以是多种多样的,如:
-
RPC 风格的 url
以 rpc 风格创建的 url 可如下
http://example.com/createUser?name=foo http://example.com/getOrder?id=bar
这种方式导致资源的链接多样,链接只能使用一次,使调用者每次都需要查找对应的文档
-
不限制 http 动作和状态
对 http 动作没有任务限制,对资源状态也没有明确限制
GET http://example.com/createUser?name=foo 404 GET http://example.com/getOrder?id=bar 200
对资源的处理方式与 http 的动作语义产生冲突,一直只用 GET 方法,没有充分利用 http 的语义。
状态码表示也会让人产生歧义,404 表示原来的 user 不存在,还是创建失败?
level0 是个很低级的成熟度,在 level0 不能给使用者带来好的体验。使用者知道以 http 方式来进行调用,但是调用每个接口,都需要查看对应的接口文档,接口的 url 风格和参数、返回值风格不一致。
level1
level1 有了个很大的改变:以资源为中心!
以资源的为中心的思想,是希望接口的提供者和使用者都是围绕资源的管理为入口来提供解决方案。提供者在设计接口时,首先考虑的要对外暴露什么资源;使用者使用时,先考虑要访问什么资源。
以资源为中心也反映到 url 的设计上:
/users/
/users/?name=foo
/users/1
/orders/
/orders/1
每个暴露出的的接口,首先从结构上表示的某种资源,如用户的集合、单个用户。(对于资源集合这里有争议,部分人认为应该用单数方式来表示类型,部分人认为使用复数表示集合,个人更倾向于使用集合的表示方式)。
在资源的基础上,可添加相应的查询过滤条件、上下页引用等等。
对资源的操作的返回结果一般也是表示资源的,也是对结果进行某种程度上的封装,来表达更多的语义。
level2
http 协议给以资源为中心是模型提供了一个很好框架,http 的动作、查询、超链接、响应码都能很好地映射到以资源为中心的模型去。
充分利用好 http 协议,可减少很多不一致的设计。
http 提供动作映射到资源操作:
GET 获取资源,无副作用,具备幂等性
POST 创建资源,有副作用,不具备幂等性
PUT 创建或更新资源,有副作用,具备幂等性
PATCH 部分更新,有副作用,具备幂等性
DELETE 删除资源,有副作用,具备幂等性
但操作会改变资源的状态,则此操作是有副作用的。当执行后结果保持不变,则称此操作是幂等的,幂等性对于实现分布式的环境有很大的意义。
这两个概念能帮助决定一个操作以使用什么样的 http 动词暴露出来。
需要注意的是部分客户端不支持完整的动作列表,就需要用些另类的方法来支持,如 spring 的 hidden method 方式,在表单中用一个隐藏的 _method
字段表示动作,加上服务器端的 HiddenHttpMethodFilter 共同支持:
<form method="POST" action="/users/1">
<input type="hidden" name="_method" value="PUT">
</form>
以天气资源为例子,系统提供天气预报查询的 api ,查询并不改变天气的状态,可使用以下的方式
GET /weather/
但这个 url 还不能满足幂等要求,每个地区、每天的天气都不一样,因此加上地区和时间
GET /weather/cn/guangzhou/20191201
这样 url 就能一定程度上满足幂等性的要求,每次查询都会得到相同的结果,也可以在 CDN 或浏览器上缓存起来。
在响应代码上,也应该与 http 的语义保持一致,常见的
200 成功
404 找不到资源
301/302 永久/临时转移
level3
有了前面几个级别为基础,整个 REST 结构就很清晰了,但还有一个问题还没有处理:资源的位置变化、外部资源的引用。
随着升级,可能一个资源的位置会转移到其它地方,可能被弃用。为了引用资源,需要在表示中使用某种链接方法,而不仅仅是数据的 json 或 xml。
在 REST 相关的书中,会提及到 HATEOAS (Hypertext As The Engine Of Application State)这种方式,它为资源加上对应的链接,用链接来在资源间进行导航。
利用这种方式,可实现一定程度上的智能客户端,能自动定位到对应的资源。
见 spring-hateoas 上的一个例子
{
"_links": {
"self": {
"href": "https://myhost/person/1"
},
"curies": {
"name": "ex",
"href": "https://example.com/rels/{rel}",
"templated": true
},
"ex:orders": {
"href": "https://myhost/person/1/orders"
}
},
"firstname": "Dave",
"lastname": "Matthews"
}
这里引用了外部的 orders ,这个资源并不在当前的服务节点,使用了 HAL 的方式表示此资源的链接,让客户端能顺利找到此资源。
实施 level3 相对来说会复杂一点,正如前面所描述,大部分的 api 只需要到 json 结果,但如果涉及到跨越系统调用,hateoas 是个避不开的坑。
并非银弹
这 4 个渐进的成熟层次有助引导我们思考 REST api 的设计,但现实的系统需求是多种多样的,并没有一种可以处处适用的无敌解决方案。
对于要求极端性能的系统,可能并不适合使用 http + json 的 rest 组合,可能要使用更加紧凑的 rpc + protobuff 的方案;与遗留系统的集成也需要考虑有的技术架构,不要无脑选 REST 。现实的系统总是需要我们在各种方案中进行取舍。