REST API在实际工作中的挑战
摘要
软件开发从单机软件到互联网化,从互联网单体应用到系统拆分、前后端分离,再到现在的微服务。软件架构越来越庞大复杂,相关联的领域划分更细致、职责更专一,这样各部分之间的交互就很频繁。RESTful作为一种架构风格、一种接口设计规范,简单、标准、易扩展让其得到了越来越多的应用。然而在实际工作中,REST API也会带来一些新的挑战:接口无法抽象为资源、多变的URI不容易做监控等。
什么是REST API?
REST 全称 Representational State Transfer,翻译为资源表现层状态转化,资源指服务中的实体或信息;表现层指信息的展现形式,如json、xml、html等;状态转化指接口访问过程中数据和状态的变化,作为http无状态服务,业务的实现必然要涉及到数据和状态的变化。
RESTful的本质是一种软件架构风格,核心在于面向资源设计接口,即将所有的接口描述为对资源的操作,目的在于降低开发的复杂性,提高系统的可伸缩性。设计的准则主要有以下几点:
- REST API 围绕资源设计,资源是可由客户端访问的任何类型的对象、数据或服务。每个资源有一个标识符,即唯一标识该资源的 URI。
- 客户端通过交换资源的表示形式来与服务交互。REST API 使用统一接口,这有助于分离客户端和服务实现。 对于基于 HTTP 构建的 REST API,统一接口包括使用标准 HTTP 谓词对资源执行操作。 最常见的操作是 GET、POST、PUT、PATCH 和 DELETE。
- REST API 使用无状态请求模型。
URI标识资源
REST API采用面向资源的思想,所以在设计之初首先需要考虑的是有哪些资源可供操作。资源是一个很宽泛的概念,任何寄宿于Web可供操作的事物均可视为资源。资源可以体现为经过持久化处理保存到磁盘上的某个文件或者数据库中某个表的某条记录,也可以是Web应用接受到请求后采用某种算法计算得出的结果。资源可以体现为一个具体的物理对象,它也可以是一个抽象的流程。
一个资源必须具有一个或者多个标识,既然我们设计的Web API,那么很自然地应该采用URI来作为资源的标识。作为资源标识的URI最好具有可读性,因为具有可读性的URI更容易被使用,使用者一看就知道被标识的是何种资源,比如如下一些URI就具有很好的可读性。
http://www.artech.com/employees/c001(编号C001的员工)
http://www.artech.com/sales/2013/12/31(2013年12月31日的销售额)
http://www.artech.com/orders/2013/q4(2013年第4季度签订的订单)
除了必要的标志性和可选的可读性之外,标识资源的URI应该具有可寻址性。也就是说,URI不仅仅指明了被标识资源所在的位置,而且通过这个URI可以直接获取目标资源,即每个资源有一个唯一标识该资源的 URI。
HTTP方法表示动作
对于Web来说,针对资源的操作通过HTTP方法来体现。常见的HTTP方法有其中:GET、HEAD、DELETE、PUT、POST、PATCH。它们的含义如下:
- GET 检索位于指定 URI 处的资源的表示形式。 响应消息的正文包含所请求资源的详细信息。
- HEAD 获取目标资源的元数据信息,服务器一般将对应资源的元数据置于响应的报头集合返回给客户端,这样的响应一般正文。
- POST 在指定的 URI 处创建新资源。 请求消息的正文将提供新资源的详细信息。
- PUT 在指定的 URI 处创建或替换资源。 请求消息的正文指定要创建或更新的资源。
- PATCH 对资源执行部分更新。 请求正文包含要应用到资源的一组更改。
- DELETE 删除位于指定 URI 处的资源。
需要注意的是:对于发送PUT请求以修改某个存在的资源,服务器一般会将提供资源将原有资源整体覆盖掉;如果需要进行局部修改,推荐采用PATCH方法,因为从语义上讲Patch就是打补丁的意思。PUT 请求必须是幂等的。 如果客户端多次提交同一个 PUT 请求,结果应始终相同,而我们不需要保证保证 POST 和 PATCH 请求的幂等性。
无状态性
REST只维护资源的状态,而不需要维护客户端的状态。对于它来说,每次请求都是全新的,它只需要针对本次请求作相应的操作,不需要将本次请求的相关信息记录下来以便用于后续来自相同客户端请求的处理。REST 独立于任何基础协议,并且不一定绑定到 HTTP。由于HTTP协议本身就是无状态的, 最常见的 REST 实现使用 HTTP 作为应用程序协议。
遇到的挑战
RESTful的设计风格可以极大提高接口的易用性,目前已经越来越多的web服务已经采用RESTful风格的API对外提供服务,例如:ElasticSearch、MongoDB、Docker等。作为一名后台开发工程师,我在实际工作中也是以RESTful风格指导自己进行接口设计的过程中,主要会遇到以下两个问题:
-
某些接口无法抽象为资源,例如:登入接口、登出接口、搜索接口等。实际工作中经常会遇到这样的接口:这些接口是针对资源的操作行为,而无法抽象为一种新的资源。ElasticSearch对外提供了非常完善的RESTful风格的API,但是针对搜索操作,用到的URI却是
/index/doc/_search
。参照这种方式,我在工作中如果需要提供类似的接口,也会提供包含动作含义的URI。 -
另一个棘手的问题是RESTful风格的API在框架层面不好做监控。通常情况下监控为了不侵入业务代码,框架层面记录每个HTTP请求的URI、方法、返回状态码等信息,然后上报至一个统一的监控中心处理分析数据。以查询雇员信息的API为例,
GET /employees/c001
和GET /employees/c002
分别代表查询c001和c002的信息,业务层面上使用了同一份查询代码,但是监控数据却显示在两个不同的URI下。目前我们的解决方案是在业务层面为每个API配置一条监控的URI,这样GET /employees/c001
和GET /employees/c002
这两种请求的监控都会现在查询雇员信息API下,这样可以更容易反应服务执行状态。但是导致的结果就是工作量的增加,本来在框架层面可以统一处理的逻辑却要侵入至业务代码。
结论
REST不仅可以对外提供易用方便的API,同时面向资源的设计风格可以指导数据层面上的领域建模,在实际工作中非常值得推荐。但是REST风格的API也会带来一些新的挑战,我们没必要做一个死板的REST原教旨主语者,应该根据场景,灵活运用,最大程度保证接口定义的清晰明了以及功能的稳定正常。
参考资料
[1] 浅谈RESTful接口设计和开发 https://www.jianshu.com/p/d674b6153326
[2] 我所理解的RESTful Web API [设计篇] https://www.cnblogs.com/artech/p/restful-web-api-02.html
[3] Web API 设计 https://docs.microsoft.com/zh-cn/azure/architecture/best-practices/api-design