上个月,我在Skillsmatter参加了一个关于RESTful微服务快速进阶的培训课程。课程着重探讨了REST API在web应用和微服务交互方面起到的作用。对我来说,这个课程给我最大的收获是让我更好地理解REST,以及它的优点和不足。
过去我大部分工作是在移动技术领域,也就是web API的调用端。我过去所调用的大部分API被认为是RESTful的,不过现在我对RESTful有了更深的理解,我敢说那些API里有99%都算不上RESTful。
定义
REST是“呈现状态转移(REpresentational State Transfer)”的缩写。或许可以这样来定义它:一种API的架构风格,在客户端和服务端之间通过呈现状态的转移来驱动应用状态的演进。
约束
要让应用RESTful化,需要遵循以下约束。遵循了这些约束的分布式系统,就会拥有如下非功能属性:性能,伸缩性,易用性,扩展性,可见性,可移植性和可靠性。
CS模式
CS模式通过分离客户端和服务器端的关注点,让客户端不再关注数据的存储问题,从而提高客户端代码的可移植性。另一方面,服务器端不再关注用户界面和用户状态,从而变得更简单,提高了伸缩性。服务器端跟客户端可以独立开发,只要它们都遵守契约。
无状态
客户端上下文在多个请求之间是绝不会保存在服务器上的。每个请求必须包含必要的信息。无状态的服务器通过快速释放资源和简化实现提高了可伸缩性。可靠性使得从局部失败中恢复变得容易。很明显,监控系统不必通过考虑单个请求来判断请求的性质。
无状态服务器的一个缺点是降低了网络性能,因为所有需要的数据必须在每次请求中发送。
可缓存
REST应用程序是web系统,因此客户端和中间层可以缓存响应。响应必须被定义为可缓存或不可缓存的,以防客户端重复使用旧数据导致降低可靠性。如果缓存中的陈旧数据与已生成的请求的数据显著不同,则由服务器处理请求。缓存可以消除一些客户端和服务器之间的交互,这就提升了可伸缩性、效率和通过减少平均延迟达到的用户可感知的性能。
统一的接口
使用统一的接口降低了系统复杂度和耦合度,让系统的不同部分可以独立演化。稍后会解释URI,资源和超媒体是如何通过生成标准接口来提升用户交互可见性,降低系统复杂度,促进系统组件独立演化的。但是我们需要在效率方面做出妥协,毕竟消息是通过标准格式传输的,并不能满足所有应用对消息格式的要求。
分层的系统
分层系统通过约束组件的行为来降低系统复杂度,组件不能越过它们的媒介层去访问其它层。通过组件的阻断来保持层间的独立性。遗留的组件可以被封装成新的层,不让旧的客户端访问。媒介层可以通过负载均衡来提升伸缩性。分层系统存在的主要不足,是它给数据处理增加了一些额外的开销,增加了延迟,对用户体验有所影响。
按需编码
REST允许客户端通过下载执行脚本来扩展它们的功能,简化了客户端,也提升了扩展性。但这同时也降低了可见性,所以这个约束不是必须遵循的。
元素
REST提供了以下几种元素来构建无状态,可伸缩的web API。
HTTP - 文本传输协议
REST一般使用HTTP作为它的传输协议,因为HTTP提供了一些很好用的特性,如HTTP动词,状态码和头部信息。
HTTP动词
HTTP并没有定义很多动词来描述web服务中可能出现的行为,它只用了一个标准动词集合来处理各种相似情况,从而让API变得更直观。每个动词通过两种属性的组合来满足不同的场景需求。
幂等性:操作可以被重复执行,就算在失败以后。
安全性:对客户端来说操作不会产生副作用。
GET
用来从服务器端读取状态。这个操作是安全的,所以它可以被执行很多次而不会对数据有任何影响,也就是说执行它一次跟执行十次是一样的效果。从幂等性方面来看,多次请求跟单个请求总能得到相同的结果。
POST
一般用来在服务器端创建某种状态。这个操作不具备幂等性跟安全性,所以多次请求会在服务器端创建多个资源。因为POST是不幂等的, 所以不应该被用来做跟金钱有关系的操作,试想一次失败的请求如果被执行多次,那么很可能转账或者支付也被执行了多次。
PUT
虽然它也可以被用来创建状态,但主要还是用来在服务器端更新状态的。它是幂等的,但不安全,因为它会改变服务端的状态。因为它的幂等性,PUT可以被用来处理跟金钱有关系的操作。
DELETE
用来在服务器端删除状态。它也是幂等非安全的,因为它会移除服务端的状态。它之所以是幂等的,是因为重复删除一个状态的结果是一样。
响应状态码
HTTP在请求资源的响应里提供了元数据信息,也就是状态码。它们是web平台之所以能用来构建分布式系统的重要因素。它们被分为以下几类:
1xx —— 元数据
2xx —— 正确的响应
3xx —— 重定向
4xx —— 客户端错误
5xx —— 服务端错误
头部信息
HTTP在消息头部里为请求响应提供了额外信息。每个头部由大小写敏感的关键字和值组成,中间用冒号隔开。头部信息被分为以下几类:
一般头部:在请求跟响应里都有,跟消息体里传输的数据没有关系。
请求头部:更多的是关于被请求资源或者客户端的信息。
响应头部:响应的额外信息。
实体头部:消息体的额外信息,比如content-length或MIMI-type。
资源
资源可以是由系统暴露出来的任何具有唯一标识的东西。资源在应用领域跟客户端之间建立起了联系。一张图片,一个表格,或者它们的集合,都被看作资源。资源通过某种呈现方式被获取或被创建(XML,JSON等)。
我们与之打交道的是资源的呈现形式,并不是资源本身,这个跟值传递有点像。根据之前对REST的定义,资源代表了在网络上传输的文档。服务器端关心资源的状态,因为它们代表了领域的状态。而客户端只是获取或者发送资源的呈现状态,从而让应用的状态发生变化。客户端关心的是应用的状态,因为这些状态的变化跟应用所要达成的目标有关。
资源的名字都应该是具有名词性质的,它们代表的是系统中领域的概念,并用URI标识。
URIs (Uniform Resource Identifiers)
URIs用来唯一标识资源。要访问或者操作一个资源,最起码要知道资源的地址。它们由协议+服务器地址+路径组成。
客户端不应该与资源的URI有太多耦合,因为服务端可能随意改变它们的值。在这一点上,超媒体更具优势。它提供了一种解耦客户端跟URI的方式,并在应用协议中加入新的语义。
超媒体
超媒体通过超媒体控件(比如链接跟表单)或者URI告诉客户端接下来可以做什么。针对特定应用的超媒体格式是在应用的Media Type里定义的。
超链接由href属性和rel属性组成,href指定要访问资源的URI,rel定义了资源跟URI的关系。超媒体通过它们向应用的状态转移增加新的语义。服务端通过在响应中增加新的超链接来扩展新的功能,而不会对客户端造成影响。只要服务端在响应里一直保留之前的链接,客户端可以像之前那样工作,只是在要访问新资源的时候才需要更新。超媒体的另一个优势是它引入了可发现性,它提供了一种可以暴露资源的方式,而且它是一种自文档的协议。
客户端通过一个固定的URL跟app开始了交互,服务端在每一个响应里提供了后续操作的超链接,这些链接具有良好的媒体格式,客户端沿着这些链接可以做相应的操作。
超媒体跟链接定义了服务器跟客户端之间的契约。客户端通过链接与系统进行交互,这就是HATEOAS(超媒体作为应用状态的引擎)所表达的意思。
Richardson成熟度模型
这个模型帮助我更好地理解REST,以及如何去解释web应用的属性。它把REST系统的组件分为三个等级,并提供了一种方式去理解RESTful相关的想法,概念和优势。它更像是一种理论教育模型,而不是一种评审机制。
关于Richardson成熟度模型更具体的解释可以看这里。