REST什么是REST?从RoyFielding于2000年提出REST至今已有20多年,它对Web技术产生了深远的影响。REST本身并不产生新技术或中间件。REST传达的是一种设计思想,它提供了一种约束原则和条件。REST的全称是RepresentationalStateTransfer,中文意思是具象状态转移。感觉前面其实少了一个主题“资源”。我个人的理解应该是“representationalstatetransferofresources”。而其核心是通过创建资源定义和描述原则,形成标准化的规范,从而降低技术人员的开发和沟通成本。当REST风格的框架被称为Restful架构,而我们主要使用HTTP作为这个规范的载体时,本文也以HTTP的形式进行讨论。但我认为只要符合REST设计思想的功能描述方式都可以算作REST的实现,并不局限于HTTP协议。UnderstandingRESTRepresentationalStateTransfer,其实已经罗列了REST的整体概念,再加上我们的补充主题“resource”,可以清晰的体现出REST中的两个主要概念:resource,resourcerepresentationalaction,以及statetransfer的简单理解也就是说,REST就是把一个接口action的描述拆分成两部分:resource和action。其中,resource是描述资源位置,resourcerepresentation是这些资源应该如何显示(具体是JSON或XML),statetransition可以简单理解为对这个资源执行的动作。REST只是将这两个核心定义的逻辑进行了分离和标准化,让“接口”和“操作”的定义更容易理解和阅读(更完整和权威的介绍请参考:https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)。资源REST中的一个核心概念就是对资源的描述,而这个资源是一个抽象的概念,不一定是静态资源,而是一个完整资源的一部分。其实只要是可以被引用的,我们都可以称之为资源。在网络上,我们使用URI(统一资源标识符)来标识资源。根据定义,URI是一个唯一的标识符,可以说是资源的名称或资源的地址。从这个角度来看,如果一个内容不能用一个URI来表示,那么它就不能说是一个资源。一些正面的例子:https://blog.csdn.net/losorick/article/details/123311537。https://xie.infoq.cn/article/3ddce663b21acd89f41582aa3。一些反面例子:/export/create。资源定义中的多个名词用“-”分隔(最好不要使用“_”,在某些情况下会不完整),“/”用来表示资源增肌的概念。您也可以使用“,”或“;”划分多个资源。当然,这些都是建议。具体实现只需要在项目中统一即可。比如在github中,用“...”作为多个资源的划分,比如“/git/git/compare/master...next”。资源表示URI只是定位一个特定的资源,但是对于客户端来说是一个统一的抽象。如果客户端要使用,不能直接使用,需要指明要使用的资源形式。目前主流的文本交互表示都是JSON格式。当然,对于要求严格的回复格式的团队,还是使用XML格式,媒体资源也可以使用PNG、MP4等格式。资源定位器原则上只负责资源的标识,并不关注如何展示资源,资源需要展示的形式由客户端根据自己的需要来申请。在HTTP中,我们的服务器通常使用“content-type”来描述资源的表现形式,而客户端使用“Accept”请求头来表示需要的格式类型。同时,资源的表示不限于资源的输出类型,资源之间的关系也是资源表示的一部分。例如,当我们查询一个详细信息时,查询后的响应体本身就是一个资源,用来描述详细信息。如果需求要求我们在该详细信息后描述下一个详细信息的访问链接,用于提示用户进一步浏览。这个信息“下一个链接”信息如果我们放在响应体中似乎不是很合适,因为对于一个GET请求来说,这个“下一个链接”信息并不是资源本身的一部分。并且随着系统中推荐算法的运行,这部分信息甚至变成了不可缓存的内容(因为它随时可能发生变化)。基于上面的讨论,或许我们可以增加一个LINK响应头来描述“下一个链接”信息,这样就达到了目的,保证了响应体对资源本身描述的正确性。因此,资源表示不是数据库的CURD,它体现了资源作为超媒体(资源到资源连接关系)的一部分的概念。同时,我们可以使用HTTP的每一部分独立描述各种概念,而不用将它们全部扔到响应体中。状态转换通过上面的描述,我们可以将资源理解为一种静态内容。那么我们如何理解我们对资源的操作呢?REST使用状态转换来表达这件事。当我们创建一个新的资源时,资源从“无”状态转移到现有状态;当我们更新资源时,它会更改其内部状态。REST使用HTTP方法来描述这些操作类型。这样做的好处是我们可以对操作类型进行统一规范。例如,在REST中,所有GET请求都应该是可缓存的,而所有PUT请求都应该是幂等的。我们通过将界面操作约束为明确的HTTP方法或其他统一的方法来降低查看界面时的通信成本。常用的HTTP方法有:GET、POST、PUT、DELETE。但早期版本的客户端可能只有GET和POST。协议升级支持LOCK、UNLO??CK等方式,也支持自定义方式。但公司通常会根据他们的主要受众设备调整这些设计。从Restful开始的接口规范对于一个接口,URI部分应该只用于描述操作的目标资源。“HTTP方法”应该只用于解释操作类型。但是,企业要实现REST接口规范,还存在一些需要调整和确认的问题。原因可能是内部历史原因或者当前框架不匹配REST。本节给出两个示例供您参考。向后兼容由于在实际业务开发过程中可能会发生业务逻辑变更,因此我们处理这个问题的主要方式是在接口中添加版本信息。但是从上面可以看出,URI本身应该是用来定义资源的名称和地址的。所以对于同一个资源,如果内容改变了,就已经改变了。资源本身没有版本的概念。我们实际调整的是资源的不同表示方式,而这种方式对应的是“版本”的概念,而不是资源本身。从这个角度来看,对于REST的设计来说,URI的资源定位器上不应该出现这个版本的概念,因为资源的名字是一样的。那么对于REST,我们可以通过在Accept响应头中附加版本信息(version)来区分具体的表示方式。例如:接受:version=1.0。接受:版本=2.1。接受:版本=3.0。但在实际业务中,我们通常不会这样做。原因有很多,其中之一就是在业务通信中,通常只关注URI和HTTP方法。在实际通信中,我们使用URI和HTTP方法作为相互通信的主要方式,可以在一行中表达所有信息,例如:{GET}http://api.example.com/trade/order/1。所以如果将版本信息直接添加到URI地址中,可以这样表示:{GET}http://api.example.com/trade/v1/order/1。{获取}http://api.example.com/trade/v2/order/1。{获取}http://api.example.com/trade/v3/order/1。这样做的优点是可以直接通过URI描述兼容性信息,缺点是破坏了REST原教旨主义的资源定位方法。然而REST已经推出了20多年,实际业务中的“资源”数量也呈爆炸式增长。因此,当服务治理等各种新概念成为今天的必然需求时,顺应现状的调整是恰当的。自定义操作在REST中,我们需要使用HTTP方法来定义操作的类型,因此存在一个主要问题:现有的方法无法描述当前操作要做什么。一些常用的操作(在Postman中有)有PATCH(在github中有)、COPY、LINK等。另外,BATCH-CREATE等批处理操作也很常见。按照REST原教旨主义,应该在HTTP方法中进行扩展,但是由于系统兼容性等问题,我们希望保证各个版本的客户端都能支持该方法。首先,无论我们用什么方法来表达自定义操作,都需要满足统一的表达方式,并且可以适用于所有的方法。针对这种情况,本总结列出了几种解决方案,供参考:扩展方法HTTP中的方法可以自己扩展,Github增加了PATCH方法,WebDAV扩展了LOCK、UPLOCK等方法,但这些都不是标准的HTTP方式,如果使用这种方式,需要考虑客户端的支持能力。参数定义通过保留参数定义扩展方法,如使用_method=DELETE定义请求方法,在服务端路由具体方法。这种方法的优点是可以将参数代入URI而不会影响原始版本URI中资源位置的含义。URI后缀描述可以通过为URI添加后缀来定义,比如在后缀中添加/actions:delete,或者在URI末尾使用/actions标签,在第一层描述扩展的动作类型请求正文。这样,虽然在URI定义中增加了资源地址以外的额外信息,但可以保证整体访问界面中的信息完整。总的来说,确定自定义操作的扩展方案应该在客户端的支持、下游网关改造的难易度、接口的可读性之间进行权衡。Restful风格的问题使用Restful风格构建项目的主要问题是path-variable的处理。在一些项目管理项目中,由于path-variable导致的不同路径默认是不同的url,所以不能直接使用,需要有开源软件二次开发的能力和要求。举个Sentinel的例子,Sentinel是根据url生成的资源名称,而REST中由于使用path-variable来定义资源,所以同一类型资源的定义者会被识别为不同的资源。但是我们查看Sentinel中的代码可以发现,Sentinel中默认使用CommonFilter来处理请求的url,资源重命名主要是通过UrlCleaner接口中的clean方法,所以我们可以通过重写来达到满意的效果clean方法rest的资源明显有问题(其实官方也提供了sentinel-spring-webmvc-adapter来支持rest风格定义的接口)。由于REST风格是主流的接口规范风格之一,使用量大的中间件短期内会有相应的解决方案,但对于自研工具来说,需要考虑REST风格的兼容性。可以参考一些开源软件如Swagger匹配实现。最后,本文讨论了REST中的相关概念,但并不是完全按照剧本来表述。比如,REST的六大指导原则就不介绍了。REST本身是一个比较大的概念,但是后端分离、微服务等概念逐渐流行起来,原来的REST概念并不完全适用。但是REST的核心理念对于我们的接口规范设计有着非常重要的指导意见。
