vlambda博客
学习文章列表

API设计原则,及Restful风格优劣分析比较

API设计原则,及Restful风格优劣分析比较

任何两个系统之间进行交互,都需要通过API(Application Programing Interface应用程序编程接口)。

可以说,API构建了当前数字化世界的通路。

现在云计算技术非常火爆,其起源实际上是亚马逊的一种API文化。

当时,贝佐斯规定,亚马逊的所有系统、团队之间的交互,都必须通过服务接口。

翻译过来就是:

  1. 所有的团队必须通过服务来对外暴露数据和功能。

  2. 所有的团队之间的功能,必须通过这些服务接口来进行交互。

  3. 网络上不允许任何除服务接口的其它交互方式。(不能通过Excel等文件传输)

  4. 不管具体使用任何的技术,都必须符合上面的3条原则

  5. 所有的服务接口必须都被外部化。

  6. 任何不遵循这个指令的人将会被解雇。

后来亚马逊发现,一些服务不仅内部团队可以使用,也可以形成解决方案暴露给外部客户,由此发展出来了云计算。

现在流行的微服务架构风格,服务化的思维,都依赖于API。

而我们的开发人员的日常工作,很大一部分是在联调API,所以对于API最好有一个整深入的把握。

首先,推荐一本老书:《Practical API design》软件框架设计的艺术。

2

API的坏味道

在介绍API知识体系之前,我们回忆一下平常有哪些痛点?

服务消费者视角

作为消费者,使用API的时候长长有如下的痛点:

  1. 从网站开放平台或者word文档或者Excel文档拿到接口文档,不知道怎么用,不知道这个功能如何编排在产品里面。

  2. 具体到某个接口,其功能描述紊乱,不知道怎么用,还需要人工咨询。

  3. 我们只想使用一个简单的功能,对方却提供了一个大而全的接口,需要使用者自己小心翼翼地区分每个字段值。

  4. 开发联调的时候,返回的错误结果描述不明确,不知道下一步该怎么办,还需要人工咨询。

  5. 好不容易投产上线了,还需要经常配合升级联调。

  6. ……

服务提供者视角

  1. 接口发布出去以后,每天接收大量的咨询,都没有时间去开发功能。

  2. 客户输入很随意,参数校验没有控制住,甚至直接到了数据库层才报错。造成性能影响。

  3. 随着发布服务的增多,已经失去了对于服务的掌控。

  4. 经常有服务越权、安全攻击等技术问题出现。

  5. ……

看吧,一个较差的API,会导致双方的生产效率都下降。

针对上面的一些API坏味道,笔者总结除了以下的API设计原则。

抛砖引玉。

3

API设计原则

一个设计良好的API,不仅能减少消费者的认知负担,还能减少服务提供者的人工咨询支持负担,也能有助于保持系统的健壮性、稳定性。

接口分离原则(The Interface Segregation Principle)

作为面向对象SOLID原则中的“I”,对我们的API接口提供了非常有实战意义的指导价值。

不要强迫用户去依赖它们不使用的接口。换句话说,使用多个专门的接口比使用单一的大而全接口要好。

所以,你提供的API接口中,不要很多个flag,如果flag是啥,然后联动另外一个或多个字段是啥这样的情况。

如果客户只需要一个USB接口,不要给他一个交换机。

API设计原则,及Restful风格优劣分析比较

其实这个原则,也蕴含着其它的原则好处。

例如,最小知识原则,减少消费者的认知负担。

单一职责原则,一个接口实际上也最好只提供一个功能。

接口自表达原则

一个消费者拿到接口后,应该很简单的就能读懂,然后进行开发联调,甚至联调报错后,根据错误信息也能够自己修正。

不需要人工咨询服务提供者。

例如,一个接口的输入、输出字段的含义,在接口文档中描述有二义性或者含糊不清。

再例如,一个接口返回的报错信息,没有指定具体的错误原因,以及要怎么做才能避免这种问题。

这里还有两点小问题,需要明确。

  1. 错误信息。

    具有自表达的错误信息字段,应该包含两点内容。

    A:明确的错误指向。例如“amount金额字段金额格式输入有误”。

    B:下一步解决方式。针对复杂的业务场景,例如“您还未开通此功能,请去URL开通此功能”。给出明确的解决问题路径。

  2. 错误码设计

    错误码的设计也有几点需要注意。

    A:屏蔽内部逻辑。不要轻易的暴露出内部处理逻辑的规律,防止猜测、攻击。

    B:便于快速定位问题。

    例如,我们参考HTTP的状态码方式。

    我们就可以这么设计错误码:

    尤其是如果一个API串联了多个服务,明确出来哪一个步骤出错,能够帮助开发人员便捷的定位问题。

    有了统一的 code,我们就可以通过 Nginx 或者 APM 工具统计 API 请求 Code 数量及分布信息。

    我们可以根据单位时间内 99999 的数量来做 API 的异常告警我们可以根据 Code 的返回饼图,帮助我们发现系统、业务流程中的问题等等总之,好的返回码设计,可以帮助我们提高沟通效率,降低代码的维护成本。

快速失败原则

一个API接口,要快速验证条件,返回失败。

不要等到已经击穿到应用最底部,再去报错。

这是“防御式编程”的一种理念,一个具体的实践就是对于API入口参数的校验。

可以参考笔者的另外一篇文章《参数校验外部化,让参数校验与业务逻辑分离》。

具体的实现,可以利用spring自带的@ControllerAdviceResponseBodyAdvice

内外隔离原则

这个原则有两重意义。

首先,要通过API网关对外暴露接口。

这样,就能够控制如下四点问题:

  1. 统一接入(高性能、高并发、高可靠、负载均衡……)

  2. 协议适配(HTTP\DUBBO\WEBSERVICE)

  3. 流量管控与容错(限流、降级、熔断……)

  4. 安全防护(黑白名单、安全校验……)

其次,对内发布的API与对外部客户发布的API要进行越权判断。


4

RESTful API

现在业界比较流星的API风格,称为RESTful API。

REST,全称是Resource Representational State Transfer,即:资源在网络中以某种形式进行状态转移

REST最早是由Roy Fielding博士发表的论文中提到的,他也曾参与设计了HTTP协议,一种系统架构设计风格(而非标准),一种分布式系统的应用层解决方案。

由此产生的RESTful API就是目前比较成熟的的一套应用程序API设计理论。

REST的一些特征:

1、客户端-服务器(Client-Server):提供服务的服务器和使用服务的客户端分离解耦; 

优点:提高客户端的便捷性(操作简单); 简化服务器提高可伸缩性(高性能、低成本);允许客户端服务端分组优化,彼此不受影响。

2、无状态(Stateless):来自客户的每一个请求必须包含服务器处理该请求所需的所有信息(请求信息唯一性); 

优点:提高可见性(可以单独考虑每个请求);提高可靠性(更容易故障恢复) ;提高了可扩展性(降低了服务器资源使用)

3、可缓存(Cachable):服务器必须让客户端知道请求是否可以被缓存?如果可以,客户端可以重用之前的请求信息发送请求; 

优点:减少交互连接数    减少连接过程的网络时延。

4、分层系统(Layered System):允许服务器和客户端之间的中间层(代理,网关等)代替服务器对客户端的请求进行回应,而客户端不需要关心与它交互的组件之外的事情; 

优点:提高了系统的可扩展性    简化了系统的复杂性

5、统一接口(Uniform Interface):客户和服务器之间通信的方法必须是统一化的。(例如:GET,POST,PUT.DELETE)

优点:提高交互的可见性    鼓励单独优化改善组件

6、支持按需代码(Code-On-Demand,可选):服务器可以提供一些代码或者脚本并在客户的运行环境中执行。

优点:提高可扩展性

RESTful API的组成

如果使用一个示例来表达:https://api.example.com/v1/zoos

其中指定了:

  1. 通讯协议https

  2. 专用域名:api.example.com

  3. API版本号:v1

  4. 路径(Endpoint):zoos

    在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

  5. HTTP动词

    GET(SELECT):从服务器取出资源(一项或多项)。

    POST(CREATE):在服务器新建一个资源。

    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

    DELETE(DELETE):从服务器删除资源。

  6. 过滤信息(Filtering)

    ?limit=10:指定返回记录的数量

    ?offset=10:指定返回记录的开始位置。

    ?page=2&per_page=100:指定第几页,以及每页的记录数。

    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

    ?animal_type_id=1:指定筛选条件

  7. Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

       比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {

"rel": "collection https://www.example.com/zoos",

"href": "https://api.example.com/zoos",

"title": "List of zoos",

"type": "application/vnd.yourformat+json"

`      }}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Github的API就是这种设计。

  1. 身份验证

    一般来说,让任何人随意访问公开的 API 是不好的做法,验证和授权是两件事情。

    验证(Authentication):确定用户是其申明的身份,比如提供账户的密码。不然的话,任何人伪造成其他身份(比如其他用户或者管理员)是非常危险的;

    授权(Authorization):保证用户有对请求资源特定操作的权限。

    比如用户的私人信息只能自己能访问,其他人无法看到;有些特殊的操作只能管理员可以操作,其他用户有只读的权限等。如果没有通过验证,需要返回401 Unauthorized状态码,并在 body 中说明具体的错误信息;

    而没有被授权访问的资源操作,需要返回403 Forbidden状态码,还有详细的错误信息。

    PS:Github API 对某些用户未被授权访问的资源操作返回404 Not Found,目的是为了防止私有资源的泄露(比如黑客可以自动化试探用户的私有资源,返回 403 的话,就等于告诉黑客用户有这些私有的资源)。

  2. 文档

    对于接口文档来说,最好使用自动生成文档的开源工具。

5

RESTful API 辩证考量

RESTful API很好很强大。

但是却存在一个问题:有一定的认知门槛,有不明确的地方,需要开发人员去理解判断。

所以,如果你的公司员工技术水平较高,我们可以采用这种先进的方式。

如果团队成员水平一般偏下,建议对于框架进行进一步的封装,最简单的一种实践形式。

URL不变,所有的内容都放在Http的body里面,通过json报文里面的参数来区分。

还有几点小建议:

对外的API接口如果都是String类型,会不会对于开发方简单一点。

6

安全

对于API来说,最重要的一点就是安全。

首先,API的组成,从协议上来看,分为两种。

首先是通讯协议,例如Http、Https、Socket等,一般情况下使用HTTPS通讯协议,就会比较安全。

其次是报文协议,例如JSON、XML、定长字符串等,需要使用加密、签名、token等技术,来保证安全。

除了签名加密之外,还有注意如下几点:

  1. 时间戳机制

数据是很容易被抓包的,但是经过如上的加密,加签处理,就算拿到数据也不能看到真实的数据;但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求;这时候可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如5分钟内;这样恶意请求的数据包是无法更改里面时间的,所以5分钟后就视为非法请求了;

  1. 限流机制。

    本来就是真实的用户,并且开通了appid,但是出现频繁调用接口的情况;这种情况需要给相关appid限流处理。

  2. 黑名单机制。

    如果此appid进行过很多非法操作,或者说专门有一个中黑系统,经过分析之后直接将此appid列入黑名单,所有请求直接返回错误码;

  3. 数据合法性校验

    防止业务越权。

  4. 网络防控。

    除了应用层面的防护,在网络层面也可以做防护,包含黑白名单,SSL双向认证等。

7

另外2020第五届中国金融科技安全大会上。光大银行安全管理处处长牟健君以《构建API安全体系护航银行数字生态》为题进行了分享。

对于金融行业的API应用来说,《商业银行应用程序接口安全管理规范》是一个非常值得学习的内容。