API设计原则,及Restful风格优劣分析比较
API设计原则,及Restful风格优劣分析比较
任何两个系统之间进行交互,都需要通过API(Application Programing Interface应用程序编程接口)。
可以说,API构建了当前数字化世界的通路。
现在云计算技术非常火爆,其起源实际上是亚马逊的一种API文化。
当时,贝佐斯规定,亚马逊的所有系统、团队之间的交互,都必须通过服务接口。
翻译过来就是:
所有的团队必须通过服务来对外暴露数据和功能。
所有的团队之间的功能,必须通过这些服务接口来进行交互。
网络上不允许任何除服务接口的其它交互方式。(不能通过Excel等文件传输)
不管具体使用任何的技术,都必须符合上面的3条原则
所有的服务接口必须都被外部化。
任何不遵循这个指令的人将会被解雇。
后来亚马逊发现,一些服务不仅内部团队可以使用,也可以形成解决方案暴露给外部客户,由此发展出来了云计算。
现在流行的微服务架构风格,服务化的思维,都依赖于API。
而我们的开发人员的日常工作,很大一部分是在联调API,所以对于API最好有一个整深入的把握。
首先,推荐一本老书:《Practical API design》软件框架设计的艺术。
2
API的坏味道
在介绍API知识体系之前,我们回忆一下平常有哪些痛点?
服务消费者视角
作为消费者,使用API的时候长长有如下的痛点:
从网站开放平台或者word文档或者Excel文档拿到接口文档,不知道怎么用,不知道这个功能如何编排在产品里面。
具体到某个接口,其功能描述紊乱,不知道怎么用,还需要人工咨询。
我们只想使用一个简单的功能,对方却提供了一个大而全的接口,需要使用者自己小心翼翼地区分每个字段值。
开发联调的时候,返回的错误结果描述不明确,不知道下一步该怎么办,还需要人工咨询。
好不容易投产上线了,还需要经常配合升级联调。
……
服务提供者视角
接口发布出去以后,每天接收大量的咨询,都没有时间去开发功能。
客户输入很随意,参数校验没有控制住,甚至直接到了数据库层才报错。造成性能影响。
随着发布服务的增多,已经失去了对于服务的掌控。
经常有服务越权、安全攻击等技术问题出现。
……
看吧,一个较差的API,会导致双方的生产效率都下降。
针对上面的一些API坏味道,笔者总结除了以下的API设计原则。
抛砖引玉。
3
API设计原则
一个设计良好的API,不仅能减少消费者的认知负担,还能减少服务提供者的人工咨询支持负担,也能有助于保持系统的健壮性、稳定性。
接口分离原则(The Interface Segregation Principle)
作为面向对象SOLID原则中的“I”,对我们的API接口提供了非常有实战意义的指导价值。
不要强迫用户去依赖它们不使用的接口。换句话说,使用多个专门的接口比使用单一的大而全接口要好。
所以,你提供的API接口中,不要很多个flag,如果flag是啥,然后联动另外一个或多个字段是啥这样的情况。
如果客户只需要一个USB接口,不要给他一个交换机。
其实这个原则,也蕴含着其它的原则好处。
例如,最小知识原则,减少消费者的认知负担。
单一职责原则,一个接口实际上也最好只提供一个功能。
接口自表达原则
一个消费者拿到接口后,应该很简单的就能读懂,然后进行开发联调,甚至联调报错后,根据错误信息也能够自己修正。
不需要人工咨询服务提供者。
例如,一个接口的输入、输出字段的含义,在接口文档中描述有二义性或者含糊不清。
再例如,一个接口返回的报错信息,没有指定具体的错误原因,以及要怎么做才能避免这种问题。
这里还有两点小问题,需要明确。
错误信息。
具有自表达的错误信息字段,应该包含两点内容。
A:明确的错误指向。例如“amount金额字段金额格式输入有误”。
B:下一步解决方式。针对复杂的业务场景,例如“您还未开通此功能,请去URL开通此功能”。给出明确的解决问题路径。
错误码设计
错误码的设计也有几点需要注意。
A:屏蔽内部逻辑。不要轻易的暴露出内部处理逻辑的规律,防止猜测、攻击。
B:便于快速定位问题。
例如,我们参考HTTP的状态码方式。
我们就可以这么设计错误码:
尤其是如果一个API串联了多个服务,明确出来哪一个步骤出错,能够帮助开发人员便捷的定位问题。
有了统一的 code,我们就可以通过 Nginx 或者 APM 工具统计 API 请求 Code 数量及分布信息。
我们可以根据单位时间内 99999 的数量来做 API 的异常告警我们可以根据 Code 的返回饼图,帮助我们发现系统、业务流程中的问题等等总之,好的返回码设计,可以帮助我们提高沟通效率,降低代码的维护成本。
快速失败原则
一个API接口,要快速验证条件,返回失败。
不要等到已经击穿到应用最底部,再去报错。
这是“防御式编程”的一种理念,一个具体的实践就是对于API入口参数的校验。
可以参考笔者的另外一篇文章《参数校验外部化,让参数校验与业务逻辑分离》。
具体的实现,可以利用spring自带的@ControllerAdvice加 ResponseBodyAdvice。
内外隔离原则
这个原则有两重意义。
首先,要通过API网关对外暴露接口。
这样,就能够控制如下四点问题:
统一接入(高性能、高并发、高可靠、负载均衡……)
协议适配(HTTP\DUBBO\WEBSERVICE)
流量管控与容错(限流、降级、熔断……)
安全防护(黑白名单、安全校验……)
其次,对内发布的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
其中指定了:
通讯协议https
专用域名:api.example.com
API版本号:v1
路径(Endpoint):zoos
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
HTTP动词
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
过滤信息(Filtering)
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
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就是这种设计。
身份验证
一般来说,让任何人随意访问公开的 API 是不好的做法,验证和授权是两件事情。
验证(Authentication):确定用户是其申明的身份,比如提供账户的密码。不然的话,任何人伪造成其他身份(比如其他用户或者管理员)是非常危险的;
授权(Authorization):保证用户有对请求资源特定操作的权限。
比如用户的私人信息只能自己能访问,其他人无法看到;有些特殊的操作只能管理员可以操作,其他用户有只读的权限等。如果没有通过验证,需要返回401 Unauthorized状态码,并在 body 中说明具体的错误信息;
而没有被授权访问的资源操作,需要返回403 Forbidden状态码,还有详细的错误信息。
PS:Github API 对某些用户未被授权访问的资源操作返回404 Not Found,目的是为了防止私有资源的泄露(比如黑客可以自动化试探用户的私有资源,返回 403 的话,就等于告诉黑客用户有这些私有的资源)。
文档
对于接口文档来说,最好使用自动生成文档的开源工具。
5
RESTful API 辩证考量
RESTful API很好很强大。
但是却存在一个问题:有一定的认知门槛,有不明确的地方,需要开发人员去理解判断。
所以,如果你的公司员工技术水平较高,我们可以采用这种先进的方式。
如果团队成员水平一般偏下,建议对于框架进行进一步的封装,最简单的一种实践形式。
URL不变,所有的内容都放在Http的body里面,通过json报文里面的参数来区分。
还有几点小建议:
对外的API接口如果都是String类型,会不会对于开发方简单一点。
6
安全
对于API来说,最重要的一点就是安全。
首先,API的组成,从协议上来看,分为两种。
首先是通讯协议,例如Http、Https、Socket等,一般情况下使用HTTPS通讯协议,就会比较安全。
其次是报文协议,例如JSON、XML、定长字符串等,需要使用加密、签名、token等技术,来保证安全。
除了签名加密之外,还有注意如下几点:
时间戳机制
数据是很容易被抓包的,但是经过如上的加密,加签处理,就算拿到数据也不能看到真实的数据;但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求;这时候可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如5分钟内;这样恶意请求的数据包是无法更改里面时间的,所以5分钟后就视为非法请求了;
限流机制。
本来就是真实的用户,并且开通了appid,但是出现频繁调用接口的情况;这种情况需要给相关appid限流处理。
黑名单机制。
如果此appid进行过很多非法操作,或者说专门有一个中黑系统,经过分析之后直接将此appid列入黑名单,所有请求直接返回错误码;
数据合法性校验
防止业务越权。
网络防控。
除了应用层面的防护,在网络层面也可以做防护,包含黑白名单,SSL双向认证等。
7
另外2020第五届中国金融科技安全大会上。光大银行安全管理处处长牟健君以《构建API安全体系护航银行数字生态》为题进行了分享。
对于金融行业的API应用来说,《商业银行应用程序接口安全管理规范》是一个非常值得学习的内容。