如何大规模交付高质量、高一致性的RESTful API及配套产物
摘要
近年来互联网技术发展飞速,API生态迅速扩张,从RPC到REST,再到现在的GraphQL,不同的风格和规范,在一定程度上,确实是在让开发者更好的在项目中开发和使用API,但是在如何交付高质量、高一致性的API(尤其是RESTful风格的API)及配套产物的方面,还没有非常实质性的推进,本文作者将会结合自身开发经验以及在云厂商的大规模开放API的设计经验,通过对RESTful相关论文的阅读,典型的RESTful风格的API规范(Google Cloud和Github的RESTful风格的API规范)探索,以及对在多团队协同开发、交付高一致性的RESTful API所面临的困难,通过API交付流程、RESTful API的风格分析、规范定制,工具建设等几个方面,对如何大规模交付高质量、高一致性的RESTful API及配套产物进行分析,同时作者将会结合在腾讯云云API团队的工作经验,并以阿里云Serverless相关API举例,对高质量、高一致性的API规范设计、API发布平台的建设进行研究和讨论。
本文一共包括三个主要章节以及前言部分和总结部分,三个主要章节分别是:
- 第一章:什么是RESTful:本章将会通过对RESTful的论文、典型的RESTful风格的平台(Google Cloud、Github)的研究,通过对OpenAPI3.0、Swagger、GraphQL的进一步探索,学习和了解RESTful,以及对“如何规范好一套RESTful风格的API”进行讨论;
- 第二章:团队协同与高质量/一致性:本章将会通过团队协作与统一开放API发布时所面临的挑战,通过团队协同的风格与规范为入口,对内部团队与外部用户连接点的建设进行探索和讨论;
- 第三章:大规模交付API及配套产物解决方案:本章将会通过大规模API交付的流程、以及RESTful风格的规约、开放RESTful风格API的规范/设计指南,以及对内统一开放API发布平台建设等进行讨论,并根据自身在腾讯云云API团队对Explorer,以及内部工具等项目的设计经验,以及以阿里云Serverless相关开放API为案例,对如何大规模交付高质量、高一致性的RESTful API及配套产物进行综合性探索;
1 前言
如果说API是模块或者子系统之间交互的接口定义,那么API设计的好坏,就会直接影响系统对接时的开发复杂度,以及之后的运维复杂度;同样,一套完善的、规范的、科学的开放API,也会在一定程度上对用户的心智、产品的整体品牌形象,用户的信任感等会有比较大的影响。在大规模的开放API设计和对外发布的过程中,通常会面对两个比较大的挑战:
- 多团队共同维护的大规模开放API产品,如何做到高质量、高一致性是非常具有挑战性的事情;
与内部API不同的是,像云产品的API,支付类产品的开放API在对外暴露时,不仅仅要在性能、稳定、安全等层面付诸更多的精力,还要在规范、科学、整体性上加强建设;因为这类API通常具有以下特点:
- 系统级的对接:一旦对外暴露,就很难取消/变更,因为这很可能涉及到外部用户系统的稳定性问题;
- API数量庞大:通常涉及到多个产品,所以相对来说API数量会非常庞大,甚至可能超过数千个;如此庞大的开放API数量,其能让用户快速发现,快速理解,快速使用,快速调试就成了极具挑战的事情了;
- 高度的一致性和互通便利性:通常是由多个业务线共同维护;其风格,以及多团队间产品互通性是需要做到统一和保障的(包括不限于统一的SDK、统一的签名方法、同类的参数统一命名、风格的统一性等);
- 稳定性和安全性就是生命线:因为通常与用户的系统直接关联,如果出现问题可能直接对用户的系统造成直接的影响,所以这一部分的安全性和稳定性如同生命线,非常重要,要有一个明确的保障;
针对上述的挑战和大规模云产品/支付类API的特点,在进行大规模高质量、高一致性的RESTful API交付时则需要注意以下内容:
对内部团队的规约:
- 完善的开放API设计指南:例如基于RESTful风格的API规范等,将会包括命名规范、数据结构的规范、错误信息的规范等;
- 科学的开放API发布流程:API一旦对外就可能涉及到系统级对接,所以难以撤回,难以更改,API发布的时候一定有完整的流程,包括不限于API的录入、预发、自测、测试、审核、发布等流程;
对内部团队提供的工具/能力:
- 完整的业务抽象能力:例如频率限制、统一鉴权、统一容灾等上层抽象能力;
- 统一开放API发布工具:完整的API录入、测试、发布的内部工具链,协助内部团队更简单、更方便、更科学的进行开放API发布;
- 统一开放API发布审查和巡检工作:提供完整的开放API上线前巡检、测试等相关方案等;
对外部用户交付的产物:
- 统一风格的文档:需要为用户提供统一的、规范的开放API接口文档、接口变更记录、升级计划等;
- 统一风格的配套产物:需要为用户提供统一的SDK、API Explorer等相关的配套产物;
2 什么是RESTful
随着互联网技术的飞速发展,尽管RESTful风格的开放API已经成为了行业的“事实标准”,但是实际上RESTful描述了一个架构样式的网络系统,并没有明确的标准,正如维基百科和百度百科所说的一样:RESTful更像是一种设计风格。这种设计风格是 HTTP 规范的主要编写者之一的Roy Fielding博士在其博士论文《Architectural Styles and the Design of Network-based Software Architectures》中进行定义和描述的。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。目前也有很多典型的,优秀的RESTful风格的API接口,例如Gihub、Rapidapi、Google等。
2.1 RESTful的风格与规范
通过对Roy Fielding的博士论文《Architectural Styles and the Design of Network-based Software Architectures》阅读与分析,尤其是其第五章关于RESTful的部分学习,可以看到这篇论文仅仅是对RESTful进行了一些风格的约束,并没有进行规范制定。所以无论是在百度百科中,还是在维基百科中,RESTful的相关词条都是被非常明确的说明:RESTful是一种风格,而非一种规范。
在Roy Fielding的博士论文第五章(表述性状态转移(REST))中,Roy Fielding认为REST提供了一组架构约束,当作为一个整体来应用时,强调组件交互的可伸缩性、接口的通用性、组件的独立部署、以及用来减少交互延迟、增强安全性、封装遗留系统的中间组件,同时这组架构约束也被总结成了六个核心部分:
- 客户端-服务器
- 无状态
- 可缓存
- 分层系统
- 按需代码(可选)
- 统一的接口
2.1.1 常见的RESTful风格规范
尽管RESTful风格被更多人所接受,也被更多人应用到自身的业务中,但是由于RESTful更多来说并不是一个规范,因为他没有对API的具体表现形态进行强约束,所在实际生产中“高质量、高一致性”的RESTful风格API难以落地。此时与RESTful配套的开放API规范/设计指南,就显得尤为重要,目前来看大多数开发者,关于RESTful风格的普遍认知包括以下部分:
URI规范
- 不用大写,统一小写;
- 用中杠( - )代替下划线( _ ) ;
- URI中的词汇要用名词,尽量不要用动词;
- 名词表示资源集合,使用复数形式;
- 在URL中表达层级,用于按实体关联关系进行对象导航 ,一般根据id导航;
版本
URL中表达出API与版本,例如:
- api.example.com/v1/
- www.example.com/api/v1/
请求
- GET:安全且幂等,获取表示;
- POST:不安全且不幂等,使用服务端管理的(自动产生)的实例号创建资源、创建子资源、部分更新资源,如果没有被修改,则不更新资源(乐观锁);
- PUT:不安全但幂等,用客户端管理的实例号创建一个资源,通过替换的方式更新资源,如果未被修改,则更新资源(乐观锁);
- DELETE:不安全但幂等,删除资源;
- PATCH:在服务器更新资源(客户端提供改变的属性);
状态码
利用状态码表示服务状态,常见规约有(仅举例说明):
- 200 (OK):如果已存在资源被更改;
- 404 (not found):资源不存在;
- 503 (Service Unavailable): 服务当前无法处理请求;
服务器回应
- 不要返回纯本文,应该是一个 JSON 对象,因为这样才能返回标准的结构化数据;
- 发生错误时,不要返回 200 状态码,有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面;
- 提供链接,API 的使用者未必知道,URL 是怎么设计的。一个解决方法就是,在回应中,给出相关链接,便于下一步操作。这样的话,用户只要记住一个 URL,就可以发现其他的 URL;
2.1.2 OpenAPI3.0与Swagger
当说到RESTful风格的开放API规范,就不得提及OpenAPI3.0和Swagger。在OpenAPI3.0的官方Repo中,我们可以看到这样的描述:
The OpenAPI Specification is a community-driven open specification within the OpenAPI Initiative, a Linux Foundation Collaborative Project.this specification is not intended to cover every possible style of HTTP APIs, but does include support for REST APIs.
所以,我们是可以认为RESTful是一种风格,OpenAPI 3.0是他的最新规范,而Swagger是这个规范配套的相关工具。
OpenAPI 规范(OAS)定义了一个标准的、语言无关的 RESTful API 接口规范,它可以同时允许开发人员和操作系统查看并理解某个服务的功能,而无需访问源代码,文档或网络流量检查(既方便人类学习和阅读,也方便机器阅读)。正确定义 OAS 后,开发者可以使用最少的实现逻辑来理解远程服务并与之交互。
在规范中,可以看到对很多内容进行了明确说明和定义,例如:
- 关于OpenAPI的版本:开放API规范使用符合语义化版本 2.0.0(semver)规范的版本号;
- 格式:一份遵从开放API规范的文档是一个自包含的JSON对象,可以使用JSON或YAML格式编写;
- 文档结构:一份 OpenAPI 文档可以是单个文件也可以被拆分为多个文件, 连接的部分由用户自行决定;
- 数据类型:在 OAS 中的原始数据类型是基于 JSON Schema Specification Wright Draft 00 所支持的类型;
- 富文本格式:整个规范中的 description 字段被标记为支持 CommonMark markdown 格式;
- URL的相对引用:除非明确指定,所有 URL 类型的属性值都可以是相对地址,就如 RFC3986 中定义的那样以 Server Object 作为 Base URI;
除此之外还对结构,以及结构中的对象(例如Server、Paths等)进行了比较详细规范和定义。
2.1.3 小结
当我们仔细读完Roy Fielding的博士论文《Architectural Styles and the Design of Network-based Software Architectures》之后,我们会逐渐的发现,RESTful更多时候并非标准、也并非技术、服务、组件,而是一种风格,隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。
正如他在论文中所说的:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。”
随着时间的发展,RESTful逐渐的在风格之上产生了一些规范,有通用性的规范,普适性的规范,也有不同的组织,不同的厂商根据自身的业务诉求等定制的规范,但是无论如何我们可以肯定的是:想要拥有RESTful风格,就必须要有一套完整的规范。尤其是在多团队协同开发,拥有大量接口的前提下,这个规范明显更为重要,如果把RESTful和规范比喻人类社会,我更觉得RESTful是道德素质,OpenAPI等是法律规定;在素质参差不齐的时候,就要靠法律进行最后的规约。
2.2 典型的API平台/生态与RESTful探索
目前在应用RESTful风格的网站或者平台是非常多的。例如非常典型的包括:
- Github某repo的issue:
https://github.com/OAI/OpenAPI-Specification/issues/2674
- 阿里云函数计算的控制台:
https://fc.console.aliyun.com/fc/service/cn-hangzhou/anycodes-tiku/function/server/overview
- 知乎的问题与答案:
https://www.zhihu.com/question/24795126/answer/41691845
除了这些非常经典的网站在采用RESTful风格,很多个人的网站项目,也在采用RESTful风格,例如:
- 我的个人博客,某个标签下的文章列表:
http://bluo.cn/tags/cold-start
- 我的一个小程序获取某题目的评论内容:
https://server.anycodes-tiku.1583208943291465.cn-hangzhou.fc.devsapp.net/api/v1/comments/questions/{问题id}
当然,一些开源框架本身也是默认是RESTful风格的,例如:
- Python Web框架中的Django框架,后台进行文章处理时:
https://www.example.com/admin/article/articlemodel/3/change/
通过上面举例的这些典型的RESTful风格的网站,我们不难发现一件事情:他们看似一致,却又不同。例如:
- 在Github某repo的issue与知乎的问题/答案部分,可以看到前者的issue是复数,后者的question和answer是单数;
- 在Python Web框架,Django框架的后台更新文章的部分,可以看到其包括了一个动词change;
此时不难发现一件事,无论是个人所设计的RESTful风格API还是一些经典RESTful API案例,其都是在RESTful约束的基础上,进行了部分自定义的规范,而且这种自定义的规范往往是与自身业务高度契合的,即会根据自身业务特性进行一定的规范定制。
2.2.1.Github案例
说到RESTful风格的API,就不得不说GIthub,对于我们目前所看到的Github而言,无论是网站的路径还是开放的API,都可以认为是非常标准的RESTful风格,甚至可以认为其近乎全部接口都遵循了OpenAPI的规范,这一点在Github的文档中也可以得到印证:
OpenAPI 是描述 REST API 的标准规范。 OpenAPI 描述允许人类和计算机无需首先阅读文档或了解实现实现即可发现 API 的功能。 GitHub 已经以 OpenAPI 3.0 标准文档的形式公开其 REST API。
在Github的REST API中,可以非常清晰的感受到在REST中的一切都被认为是一种资
:当看到Github的任何一个地址,或者任何一个开放API,都可以让用户会很直观了解到其对应的资源,或许这就是REST风格非常重要的价值。
在Github的RESTful API中,我们可以看到其通过资源、媒体类型等几个层面,借用OpenAPI 3.0的规范,有针对性的对自身的开放API作出了较为严格的规范。通过对该文档的仔细阅读,不难发现其优点是非常明显的:
- 对资源的从属分类处理的非常好,可以使开发者直观的明白和理解;
- 对各种接口的变迁有对象的描述,也有对应的解决方案;
- 充分的使用了请求头,例如可以通过http的Accept头部字段进行制定使用其他的数据格式(不是json)的接口;
- 对一些错误码的处理更加细致,例如未授权的情况下访问私有资源,并不是401(Unauthorized)而是404(not found),这样做的好处是为了不让攻击者轻易找到内部的资源(按照严格意义上来说,这个并不是非常属于“普适性RESTful”的规范,这个算是定制化的规范方案,但是非常有意义);
2.2.2.Google Cloud案例
如果说Github的RESTful风格API规范,是在OpenAPI 3.0基础上进行进一步定制和完善,是借用已有规范快速形成自身规范的方法,那么Google Cloud的开放API规范,则是一种极具创造性的将自身的gRPC与RESTful进行了有机结合,诞生了别具一格,却又非常经典的开放API设计指南。
这篇设计指南中,非常清晰的定义了面向资源的设计
概念。同时也直接表明了这份设计文档是Google Cloud根据一些情况,定制化的一个设计指南,即满足RESTful的规范,但是实际上是自己定义的规范:
RPC API 通常根据接口和方法设计。随着时间的推移,接口和方法越来越多,最终结果可能是形成一个庞大而混乱的 API 接口,因为开发者必须单独学习每种方法。显然,这既耗时又容易出错。引入 REST 架构风格主要是为了与 HTTP/1.1 配合使用,但也有助于解决这个问题。 HTTP REST API 在互联网上取得了巨大成功。2010 年,大约 74% 的公共网络 API 是 HTTP REST(或类似 REST)的 API,大多数 API 均使用 JSON 作为传输格式。虽然 HTTP/JSON API 在互联网上非常流行,但它们承载的流量比传统的 RPC API 要小。例如,美国高峰时段大约一半的互联网流量是视频内容,显然出于性能考虑,很少有人会使用 HTTP/JSON API 来传送此类内容。在数据中心内,许多公司使用基于套接字的 RPC API 来承载大多数网络流量,这可能涉及比公共 HTTP/JSON API 高几个数量级的数据(以字节为单位)。
之所以说Google Cloud的这份开放API设计指南别具一格,却又非常经典
,是因为在这份指南中,不仅可以看到典型的RESTful风格开放API规范的详细设计方案,更可以清晰地看到为什么这个设计,这么设计的价值是什么
,可以认为这份API实际指南非常具有学习性和研究价值。
在这份设计指南中,可以看到Google在开放API设计过程中的一些关注点:
- 其核心原则是定义可以用少量方法控制的命名资源。这些资源和方法被称为 API 的“名词”和“动词”;
- 使用 HTTP 协议时,资源名称自然映射到网址,方法自然映射到 HTTP 的 POST、GET、PUT、PATCH 和 DELETE;
- 一个集合包含相同类型的资源列表;
- 资源具有一些状态和零个或多个子资源。 每个子资源可以是一个简单资源或一个集合资源;
- ......
在该指南中,也非常清楚的描述了在设计面向资源的 API 时应该采取的步骤:
- 确定 API 提供的资源类型;
- 确定资源之间的关系;
- 根据类型和关系确定资源名称方案;
- 确定资源架构;
- 将最小的方法集附加到资源;
除此之外,这份设计指南还对部分规则进行了定义,例如一些命名规则等。以命名规则为例,可以看到Google Cloud在定义这份规则时的考量:
- 要有统一的目标:简单、直观、一致;
要针对目标有一定的规范:
- 避免名称过载,使用不同的名称命名不同的概念;
- 为了简化命名,可以使用已被广泛接受的简写形式或缩写。例如,API 优于
Application Programming Interface
; - 除字段名称和枚举值外,
.proto
文件中的所有定义都必须使用由 Google Java 样式定义的UpperCamelCase
格式的名称; - .....
- 要针对特殊情况,考虑到开发者的素质问题等:由于很多开发者不是以英语为母语,所以这些命名惯例的目标之一是确保大多数开发者可以轻松理解 API。对于方法和资源,我们鼓励使用简单、一致和少量的词汇来命名;
有目标,有规范,有案例,有人道主义
,Google Cloud的这份开放API文档的设计指南,可谓是很多开发者设计API时的一个重要参考,也可以称得上是RESTful风格下的开放API设计文档经典案例。
2.2.3.小结
虽然目前已经有一些普适性的RESTful风格的开放API规范,也有一些组织推动建设RESTful风格规范标准(例如OpenAPI 3.0等),但是在实际生产中,若想大规模交付高质量、高一致性的RESTful风格API,还是非常有必要制定一套针对业务自身的RESTful风格规范;无论是和Github方案类似,在OpenAPI 3.0基础上进行部分的“升级”;还是Google Cloud的“自成一派”,根据自身需求,将gRPC与RESTful有机结合;类似这种的RESTful风格的开放API规范总是要有的。
如果说RESTful风格更多是对开发者或者开发团队的一种道德素质
,那么在这个风格之上制定的规范才是真正的法律
,只有大家都有道德素质,又有法律作为支撑的前提,才能保证高质量、高一致性的RESTful风格API交付。
2.3.RESTful和GraphQL
即使RESTful风格已经得到了大规模的应用,并逐渐成为Web API的事实上的标准,但是其仍然是不完美的,存在着巨大的挑战:
- 多端 (多次数据交互):例如,当获取一个文章的时候,可能要先后请求:
GET /posts/<postId>
和GET /posts/<postId>/comments
等多个接口; - 过度获取数据:例如客户端已经拥有某些数据,只需要获取部分内容时,请求后端之后仍然是拿到了预定的全部数据,存在过度获取的问题;
- API版本控制:API进行版本升级时,可能会带来巨大的迁移成本,不可控的维护风险;
- 弱类型:很多内容通过URL传递之后可能类型发生变化,不容易指定特定的数据类型,对后端的处理可能存在一定的风险;
- 客户端对数据结构的把握不可控:在请求某个接口之前,客户端没办法规约或者明确其返回的数据结构,这就导致过度依赖文档的详细程度;
- 对于一些规范难以适配:例如有的规范表示RESTful风格在url中不允许出现动词,但是在实际生产中,特殊情况一旦出现,就会产生比较难以抉择的现象;
为了克服 RESTful风格和OpenAPI规范的相关缺点,Facebook提出了GraphQL的规范。众所周知QL是一种查询( Query Language),所以GraphQL更多时候指的是Web API 的查询语言。相对比RESTful而言,GraphQL具备的优势也是非常明显的:
- 一次请求获取到所有:通常情况下,GraphQL 服务只需要暴露一个端点让客户端能传输必要的查询语句即可满足整体的数据获取/查询需求;
- 客户端驱动:GraphQL 提供了一种声明式语法,以便客户端精确地指定它们所需的字段。 这消除了由于客户端根据模式向 GraphQL 服务器声明其数据需求而导致数据冗余和不充分的可能性;
- API版本控制:因为在 GraphQL 中一切都是模式(schema)驱动, 新增字段不会影响现存字段,而且 GraphQL 还为废弃字段提供 @deprecated 注释,所以对 GraphQL 的扩展并不是问题。 这就消除了 API 版本控制的需要;
- 强类型:与RESTful不同的是,GraphQL是可以明确出数据类型的,或者强行规约清楚数据类型;
- 传输层不可知:这是 GraphQL 的一个非常棒的优点。 API 服务器可以通过类似 HTTP, HTTPS, WebSockets, TCP, UDP 等协议进行信息交换。 这是因为 GraphQL 甚少关心信息如何在客户端与服务器之间进行交换;
当然就目前来看GraphQL也并不是完美的,他也有一些缺点:
- 缓存功能不成熟:不支持浏览器和移动手机缓存,这一点区别于使用本地 HTTP 缓存机制的 RESTful 服务;
- 检验与错误报告:RESTful 服务利用 HTTP 状态代码来处理可能遇到的不同错误。对开发人员来说,这使得 APIs 的检验变得非常简单和轻松,但是使用 GraphQL 服务总是返回 200 OK 响应;
- 暴露模式和资源攻击: 和RESTful服务不同,GraphQL服务要求客户端必须知道要查询的数据模式;如果您将API暴露给第三方,则基本上暴露了您的内部数据结构; 必须非常小心,因为客户端不用很高的代价就可以发起连接查询,这可能会导致服务器上的拒绝服务(DoS)攻击;
- 安全 - 身份验证和授权:GraphQL社区仍然对如何处理GraphQL服务的安全部分感到困惑;
- N + 1 次查询问题:在RESTful服务中,很容易记录执行的SQL查询并进一步优化它,但在GraphQL的情况下,解析性质是动态的,因此很难获得精确的查询并进一步优化它;
尽管GraphQL也并不完美,但是这并不影响其逐渐被更多人、平台、厂商所应用,例如Github目前就已经在v4版本的API上全面支持了GraphQL。当然,也是因为RESTful风格与GraphQL在一定程度上存在互补关系,所以在很多开放API确定形态时,都会讨论要用那种方案/规范/风格。其实站在个人角度:
- 以RESTful风格的方案进行推进;
- 在后期如果GraphQL逐渐更成熟,并且逐渐解决了安全、暴露模式和资源攻击等问题之后,可以像Github一样,同时支持两种方案/规范/风格;
2.4.如何规范好一套RESTful风格的API
在准备定义RESTful风格的开放API规范之前,除了要了解RESTful的典型案例是如何进行规约的之外,还要对自身的业务有比较深入的了解,并根据自身的业务实际情况做一些特殊化的处理,例如Google Cloud的API,根据网络需求将RESTful风格禺gRPC的某些规范进行融合;Github从安全的角度出发,规定了未授权查看某些Repo效果等于资源不存在。
在进行RESTful风格的开放API规范定制时,可以充分参考Google Cloud的开放API设计指南,通过资源相关的定义、方法相关的定义、对字段进行标准化、对错误进行定义,以及对通用能力的抽象、安全和权限等规范进行定义等几个方面进行进行详细的定义。同时要遵循以下几个原则:
- 规范定制目的:帮助开发者设计简单、一致且易用的网络 API;
规范定制原则:
- 规范文档应该是考虑全面的,描述精准无二义性的(要使用要求级别关键字(“必须”、“不得”、“必需”,“应”、“不应”、“应该”、“不应该”、“建议”、“可以”和“可选”),并按 RFC 2119 中的描述进行解释);
- 应该以RESTful风格的六个约束作为基础准则,应该以普适性的规范或者OpenAPI 3.0规范作为参考或者基础,不应该大规模推翻已有的认知,可以适当的根据业务需求设计特定的规范模型;
- 该规范文档应该具有一定的可拓展性,随着时间的推移,要采纳和批准新的风格和设计模式,为规范文档增加相关内容。本着这种精神,要不断完善规范文档,并为 API 设计的艺术和技巧提供充足的空间;
- 规范文档在定制时,要充分考虑到开发者素质以及不同人对业务理解的差异性,在不影响规范整体表达的前提下,保证高质量,高一致性的前提下,要提供一定的容错性方案,即使是容错性方案,也要提供完整的规则,例如命名规则,表达规则,结构规则等;
规范的普适性:
- 首要目的是要对内部协同开发团队的开发者,进行开放API开发和暴露进行规约,确保开放API的统一性,规范性;
- 其次要对外进行暴露,让开放API的使用者,可以充分了解这套开放API的设计目的、理念、规范;
- 最后要形成有影响力的,具有普适性的,典型的RESTful风格的开放API规范,例如Google Cloud的开放API设计指南就可以称之为是该领域内的最佳实践,具有一定的参考性和学习性,同样也极具有影响力;
3 团队协同与高质量/高一致性
当一个项目或者一个产品决定对外开放API时,通常会遇到一个非常具有挑战的问题:如何保证对外开放的API的质量和一致性?这样的挑战在云厂商中尤为巨大,无论是腾讯云还是阿里云,在对外暴露高质量、高一致性的API
时,都面临着非常严峻的质量问题、一致性问题,甚至云厂商一度将所有的开放API对外暴露的出口,统一收敛到同一个团队,进行强规约、强限制,例如腾讯云云API团队,阿里云的POP团队等。
那么在团队协作的前提下,对外发布开放API的过程中,如何保证开放API的高质量、高一致性呢?通过一套完整的规范就可以解决这个问题么?腾讯云、阿里云为何要在有统一的规范前提下还要收敛到一个团队作为统一出口?除了风格的明确、流程的清晰,规范的完善,是否还要有配套的工具协助高质量、高一致性的API
对外发布?
3.1 团队协同与API生态的冲突与融合
一个产品/项目由多个团队共同协作实现是非常正常的事情,但是往往这种协作带来的是一种开放API生态的“不融合”。这其中的原因有很多:
- 不同成员对业务的理解是不同的;
- 不同团队所负责的业务功能是不同的,进而不同团队的关注点是不同步的;
- 不同的开发者素质是不同的(包括工作的态度、英文的水平以及个人的性格等);
以作者在某云做云 API产品经理是的真实事情为例:不同的云产品都有获取实例的接口,但是即使同样是获取实例,在不同的团队中对其的感觉和定义是不同的;团队A定义的行为是GetInstance
,团队B则定义成了DescribeInstance
,从不同的人的角度去理解其实这两者都是可以的,没有谁对谁错的说法,但是如果在作为一个云API团队,统一暴露出API的前提下,出现两种形式的表达,则是不合理的,更何况在云厂商中,不同的产品都有获取实例
的接口,那么抛弃掉刚刚所说的GetInstance
和DescribeInstance
,是不是还有可能出现InstanceInformation
、ObtainInstance
等更多的形态?如果一个开发者在使用云产品的开放API时,不同的产品同一个接口的行为表现完全不同,岂不是会让开发者觉得该云厂商很不专业,内容很乱,很随意;进而丧失对产品的信任。同样的事情,也可以在Github的API中进行研究和思考:
- 什么时候用
edit
,什么时候用update
; - 什么时候用
delete
,什么时候用remove
;
从上面的真实案例,我们可以发现团队协作是必然,协作后的产物,尤其是对外开放的API产物,如何保持高度的一致性,在没有绝对的收口之前,这将会是“协作”与“统一”的巨大冲突,所以如何解决这个冲突,就成了大部分跨团队协作对外开放API的产品/项目,所面临的巨大挑战。
无论是在阿里云的POP团队,还是在腾讯云云API团队,在解决这个冲突的事情时,都采用了软硬兼施
的方案,所以为的“软”是说要潜意识的培养和规范的明确;所谓的“硬”是说要有强制收口的统一发布平台,保障最后发布时的统一、兼容性、安全性以及高质量。以RESTful风格的开放API为例,需要有三个层面的要求:
- 潜意识:要使用RESTful风格定义API
- 规范层面:需要有一个非常明确的设计指南,这份设计指南不仅对内,同样要对外;而且这份指南不能有二义性;
- 强制收口:所谓的强制收口是我们对外暴露API的最后一道防线,这一道防线需要进行人工审核,要保证整体的一致性就必然要尽可能的收敛到一个部分进行统一审核;
综上所示,想要解决冲突,弱化挑战,让团队协同与高质量/高一致性不再冲突,有机融合,那就要通过上述的潜意识、强规范以及强制收口三个方面进行探索。
3.2 团队协同的风格与规范
所谓的团队协同的风格与规范是在设计开放API前和过程中所需要关注的。
3.2.1 风格明确
在API准备开发/升级之前,首先需要明确对外暴露的开放API风格,例如RESTful,GraphQL,或者是其他的规范/标准,例如RPC等。尽管开放API的设计指南在一定程度上要保持活性,不断的兼容更新的规范等(例如Github的开放API在v3版本支持RESTful风格,在v4版本支持GraphQL规范),但是还是要在设计之初,明确当前版本的风格,本文将会以RESTful风格为主要探索的方向,在未来如果GraphQL进行了更快速的发展和迭代,用户的诉求逐渐强烈,可以和Github蕾丝,同时考虑RESTful风格GraphQL的方案。
3.2.2 规范定制
规范的制定是在风格确定之后,以RESTful风格为例,需要制定资源相关、方法相关、字段进行标准化、错误等若干层面的规范。
3.2.2.1 资源相关的定义
因为RESTful在一定程度上是面向资源的,所以需要在规范制定时明确资源详细情况:资源的种类、关系;资源的上层抽象,多产品/业务下同类资源的统一;自定义资源的定义规范;具体表述如下:
- 资源内容梳理:尤其是不同团队的表述可能一致的资源,例如以云产品为例,在云主机处可能通过将某个机器的id定义为
instanceId
,在数据库处可能某个数据会被定义成resourceId
,此时就需要评估这两个是否表示同类内容,都是资源或者实例的id,如果可以确定是同类内容,那就要在上层规约好他们的资源名,例如就叫做instance
或者resource
等,避免同类概念出现在不同的接口中,表述是不同的问题出现; - 资源从属关系的确定:按照道理从左到右应该是集合逐渐包括资源,所以在进行资源定义的时候要明确清楚资源的从属关系,避免出现A查看的
x模块
包括了y模块
,而B则是y模块
包括了x模块
这种产品间的包含关系冲突,例如某产品定义是服务下包括了项目
,而另一个产品时项目下包括了服务
,此时就要进行概念的融合和调整,以实现对外的统一性表述; - 资源命名规范:指的是是否可以使用动词,什么时候使用动词,链接符用什么,每个资源命名最长是多少;例如
查看某个计算机类目下的所有图书
可以表示为//api.example.com/v1/category/computer/books
; - 资源表达规范:是使用某个资源名称,还是使用资源id;例如1.1中使用资源名称的例子也可以用资源id这样表达:
//api.example.com/v1/category/1/books
,但是什么时候用id,什么时候用名称,这个是需要规约清楚的; - 某些参数的确定:业务不可能完全通过URI进行所有的功能确定,例如查询操作,就可能会被设计为
//api.example.com/v1/search?query={关键词}
,此时也需要对一些通用参数进行规约;除此之外某些请求方法(例如POST)等,也可能需要对某些数据结构进行明确和规范; - 对自定义内容进行明确:尽可能多的提供一些示例或者案例,但是往往在规范的时候也会出现一些极特殊的情况,所以此时还需要对一些业务团队可自定义的内容进行额外的约定。例如Google Cloud中所描述的情况: 由于很多开发者不是以英语为母语,所以这些命名惯例的目标之一是确保大多数开发者可以轻松理解 API。对于方法和资源,我们鼓励使用简单、一致和少量的词汇来命名;
3.2.2.2 方法相关的定义
除了对资源相关内容进行明确的定义,还需要对一些通用方法进行抽象,以及部分自定义方法进行约定,同时还要将相关的方法映射到HTTP的方法上:
- 规约核心的方法:在制定规范时,需要规定好所有团队可以使用的通用方法,例如List、Get、Create、Delete、Start、Restart、Stop、Update等方法;同时要明确这些方法与HTTP的映射关系,例如标准方法List和Get映射到HTTP的方法是Get,Create映射到HTTP的方法是POST等;
规约自定义方法:即使我们规约了大量的核心方法之后,抽象出来了很多通用方法之后,在实际生产中,由于各个团队处理的业务是复杂的,所以还会出现一些自定义方法,此时的解决方法有两种:
- 类似于Google Cloud的解决方法:即在URL中直接表现:
https://service.name/v1/some/resource/name:customVerb
; - 将指定的方法放到Header中:例如在Header中指定
Action: customVerb
;
- 类似于Google Cloud的解决方法:即在URL中直接表现:
3.2.2.3 对字段进行标准化
这一部分包括了抽象的业务字段以及自由字段,同时还要明确规定自定定义的规范:
- 规约标准化字段:此时可以通过一些抽象的名词标准化一些字段,例如创建实体的时间可以固定为
create_time
,这一部分和(资源相关的定义)中的资源内容梳理部分有些类似,但是前者更注重于资源,而这里对字段标准化时则不仅仅是资源,也包括时间、状态等; 规约自定义字段规范:针对一些无法抽象的字段,可以采用三段式进行规范:
- 尽量采用提供的标准字段;
- 标准字段无法满足时,可以采用所提供的独立字段进行组合,例如提供
describe
和information
字段,组合成describe_information
; - 标准字段无法满足时,可以进行自定义字段,但是要遵循一定的规则(此处规则可以尽可能的详细,例如只能是名词,链接符用下划线等);
3.2.2.4 对错误进行定义
除了上述所述内容之外,规范中还需要对错误的状态进行定义,以及对错误的结构,错误码进行定义:
- 系统错误:需要对系统级错误进行统一和规范,所谓的系统级错误指的是因为不可控因素导致一些问题的出现,例如后端业务无响应,此时平台侧进行指定的错误码和错误结构返回;
- 业务错误:因业务本身导致的一些错误,例如用户传递的参数错误、不存在等;此时需要抽象出部分错误码(必要时可以提供一级错误码和二级错误码,例如
INVALID_ARGUMENT
可以增加二级错误码ARGUMENT_TYPE_ERROR
、ARGUMENT_NAME_ERROR
等;); - 自定义错误:自定义错误是指在极特殊情况下,系统错误和业务错误无法覆盖的场景,可以支持业务团队自定义错误码,但是这种错误需要按照一定的规范进行制定;
- 错误结构/组成:虽然说会抽象出一些错误码,例如Google Cloud规约的错误结构如下:
{
"error": {
"code": 400,
"message": "API key not valid. Please pass a valid API key.",
"status": "INVALID_ARGUMENT",
"details": [
{
"reason": "API_KEY_INVALID",
"domain": "example.com",
"metadata": {
"service": "translate.example.com"
}
}
]
}
}
此时,可以根据实际业务需求规约定制化的错误信息。
3.2.2.5 其他的定义
- 相应内容的定义:在一些响应头,Body中,也需要定义好固定的结构或者规范,例如RequestId的位置;长期实践运行无法快速得到结果的请求,返回体构成;
- 通用能力的抽象:例如分页能力、排序能力、筛选能力是否需要作为上层抽象能力统一定义;
- 权限与安全的定义:例如越权查看某些资源是返回401还是返回404(例如Github在匿名访问私有Repo就会提醒404,这在一定程度上是为了防攻击,放数据猜测,数据泄露);
- 其他:其他号包括频率、包括缓存策略、包括Etag等;
3.3 内部团队与外部用户的连接点建设
如果说API风格的明确和设计指南的完成是API开发之前要做的事情,同时API开发过程重要遵循相关的规范。那么API对外发布平台
则是API开发之后,上线之前的一个非常重要的关键点,这个关键点也可以认为是内部团队与外部用户的核心连接点,通过这个连接点,将会正式对外大规模暴露高质量、高一致性的RESTful风格的开放API以及配套产物。所以这个链接点的重要性可想而知,通常情况下,该API对外发布平台
包括的内容有:
- API录入:这一部分包括了API基础信息的入库,以及上层的抽象能力,例如统一的鉴权规则、针对不同业务抽象的超频限制,容灾策略等;
- API的测试/体验:该部分包括了API上线之前的自测、预发布测试以及上线后的状态预览,以便开发团队和API平台更加清晰、直观的确定出API的最终对外交付的形态;当然,这里也包括配套产物的预览,例如文档、SDK、API Explorer等;
- API风格、规范的复审:这一部分是API团队需要在该平台上进行的操作,主要要保证API的风格一致、兼容性以及规范的正确使用等;
- API最终确定与发布:当一切都确定完成之后,该平台要提供完整的对外发布确定逻辑以及对外统一发布的能力;
API配套资源的自动发布:这一部分主要是API对外发布之后,还需要有相关的配套能力标准化生产与上线:
- API文档
- 对应多语言的SDK
- 对应的沙箱环境等
- API Explorer的更新
- 对应的资源聚合页的更新(例如错误中心等,API中心等)
综上所述,该平台既然承载了开放API统一收口的,对外暴露的关键工作,那么该平台存在的价值不言而喻:
对内:
- 确定API的整体质量,统一性
- 对内提供API层面的规范、维护方案、发布工具、测试验证平台
- 收敛整体问题的最后一道防线
对外:
- 交付统一的心智
- 交付科学规范的心智
- 交付配套的设施(例如文档、SDK等)
4 大规模交付API及配套产物解决方案
在多团队协作的前提下,如何大规模交付高质量、高一致性的RESTful API及配套产物是一个非常具有挑战性的事情。正如前文所说的,在某些前提下,团队协作是必然,但是这种必然会和对外暴露“一致性”的开放API产生巨大的冲突,所以如何大规模交付,同时又能保持一致性,降低冲突带来的负面影响,就需要有明确的开放API风格、完整的API发布流程、明确的开放API设计指南以及相对应的工具/平台:
如图所示,在多个开发团队,决定要统一对外发布开放API时:
- 首先就要明确风格和规范,例如采用RESTful风格,采用OpenAPI规范,并根据自身特性进行部分定制化;
- 当完成API开发之后,就需要走统一的API发布平台,进行API的录入、调试、测试等;
- 完成一系列流程之后,统一的API发布平台必然要有人工审核,审核通过才能进行正式的对外发布;
- 发布完成由统一的工具进行API文档、SDK、API Explorer等产品的更新升级;同时要在社区生态进行同步的更新以及告知;
整个流程的核心在于:统一的收口。其根本思路是:不再单纯依靠风格作为道德约束,也要有规范作为法律约束,更要有工具作为法律的执行,只有三者协作,才能保证“社会的和谐与问题”,即API生态的规范、统一、科学。
4.1 流程、风格、规范、工具的协同建设
若想大规模交付高质量,高一致性的RESTful风格的开放API,必然要从流程、风格、规范和工具四个角度进行建设。
就目前情况来看,典型的大规模交付高质量,高一致性的API平台非各云厂商莫属,无论是阿里云的POP网关团队还是腾讯云的云API团队,其核心的做法都是:
- 有完整的API发布流程,发布暴露统一收口到一个团队;
- 有明确的风格影响用户的和开发者的心智;
- 有明确的规范接口的名称、参数、错误、请求/响应进行限制;
- 有完善的API发布平台,做统一的录入、测试、预发、发布等,以及统一对外提供配套服务;
所以本章将会从流程是整个API从零到一的生命线、风格是是API的气质,是道德约束、规范是法律条款,是一种强约束、工具是执行规则,是最终收口与统一性的保障等四个方面进行详细的探索,并以阿里云Serverless相关的API为例,对API规范的定制进行更深入的举例。
4.1.1 流程是API从零到一的生命线
在项目开始之前,首先要让所有人知晓开放API发布的完整流程,只有这个过程非常清晰明确,才有助于后续工作的开展:
如上图所示,整个流程分为两个大类别团队以及三个核心流程:
团队:
- API团队:即最终对一些能力,功能收口的团队;例如腾讯云云API团队,阿里云POP网关团队;
- 业务团队:业务能力开发的团队,具体的功能提供者;
流程:
- 开发前:主要是一些规则、规范的明确,有主有后续开发的和发布;
- 开发中:主要是接口的开发;
- API发布:主要是高质量、高一致性的发布;
4.1.1.1 开发前明确职责,了解流程
开发前主要要做的事情是,API团队要明确API的发布流程,以及开放API的风格,并且输出API的规范/设计指南,这三个内容非常重要。同时,还需要和业务团队保持沟通,例如一个统一的群进行信息的动态更新(例如发布了某个特性,新版本发布等),并让业务团队清楚我们的风格、规范以及流程。
这里需要额外注意,开发前要明确API发布的周期,以及特殊情况下API紧急上线的流程。例如:
- 规定每周四晚上11点进行API发布,如果第二天休息,则向前推进一天;
- 如果发布的API存在隐患或者问题,需要团队相关负责人发起紧急API发布申请,由API网关团队的相关同学快速响应,尽快发布;
- 在非紧急情况下不可走紧急发布,紧急发布之后需提交紧急发布的复盘,例如为什么出现紧急发布,具体出现问题的过程,有什么方法可以规避等;
开发前就要尽可能做到大家思路统一,问题最小化。
4.1.1.2 开发中了解风格,遵循规范,
- 开发之初,推荐先将要开发的API预录到系统中,并由API网关团队的同学进行API的初审,在这个时候可以对一些风格、规范等进行初步的确定,尽可能的提前暴露一些问题,以便与API的开发更快速,错误率更低;
- 在API开发的过程中,要以RESTful风格的API为核心基础,严格遵守开放API的设计指南进行API的参数设计、错误设计以及资源定义、行为定义等;
4.1.1.3 API发布流程严格,对外标准化
- 完成API开发之后,需要再次确定预录信息的准确性,如果有部分内容发生了变更,要及时在系统中进行更新;
- 由API网关团队同学再次审核确定之后才可以进行之后的发布流程;
- API业务团队需要在测试环境对API进行调试、完成之后发布到预发环境,再次进行测试,确定无误,可以提交到API的正式发布;
API网关团队的同学需要对要发布的API进行审查,包括:
- 规范性的审查;
- 兼容性的审查;
- 测试结果的审查;
- 完成上面所有流程之后,API网关团队的同学可以正式对外发布API(在发布之前需要和业务团队再次确定发布时间);
API发布完成的同时,要进行相关配套服务的更新,包括不限于:
- API Explorer
- SDK
- API文档
- 相关的聚合页面
- 最后在生态和相关社群,进行相关信息的告知,以及
changelog
的更新等;
4.1.2 风格是API的气质,是道德约束
当已经存在了完整的,清晰的API开发发布流程之后,还需要对API的风格进行定义。对API风格采用的架构进行定义其非常核心的意义和价值是:
- 寻找一个普适性非常好,被广大用户所了解的风格/架构可以大规模降低用户的学习成本,接受成本,使用成本;
- 可以在一定程度上表现出团队的专业性,可以给用户交付一种安全感;
就目前而言,RESTful风格的API已经得到了广大开发者的认可,并且在市场上占有较高的使用比例,所以完全可以以RESTful风格为开放API的风格,并借力其周边生态(例如Apache基金会的OpenAPI规范等)。
4.1.3 规范是一种强约束 - 以阿里云函数计算部分API为例
如果将风格比喻成API的气质,是开发者的道德约束;那么规范则是法律条款,是一种强约束。因为单纯的“道德约束”是没办法规约清楚具体的事情,它是一种弱约束,所以针对已有的风格,提出一种规范是非常必要的。这种强约束需要以一种文档输出,例如Google Cloud的设计文档。
如上图所示,通常情况下,在API设计指南中要包括资源、方法、错误、字段等几个大类的规约,其核心目的是可以让开发者和使用,辅助最小的成本,得到最高效率的开发体验。以目前阿里云函数计算产品为例,根据上图所展示的一个设计指南,对相关部分进行设计。
4.1.3.1 资源
4.1.3.1.1 资源种类
首先要明确,阿里云函数计算产品目前所有的资源种类,并将这些资源进行抽象和定义,以其中部分资源为例:
- 服务(Service):service
- 函数(Function):function
- 触发器(Trigger):trigger
- 自定义域名(CustomDomain):custom-domain
- 版本(Version):version
- 别名(Alias):alias
4.1.3.1.2 资源关系
对资源种类进行定义,有助于梳理产品的具体资源,但是资源之间是存在着一定的关系的,所以在对资源种类定义后,还需要对资源关系进行进一步的分析。以服务、函数、触发器三个资源为例,确定彼此之间的关系:
4.1.3.1.3 资源的上层抽象
除了资源种类以及资源关系之外,必要时还需要根据周边产品定义的API,以及自身产品的拓展性,进行一些资源的抽象,或者说是进行资源的上层首相。由于用户在使用阿里云函数计算产品还包括应用、项目等概念,所以可以在上层单独抽象出一个应用的概念,也算是与其他产品联动时做关联:
- 应用(Application):application
4.1.3.1.4 资源属性
既然有资源存在,那必然就要有资源的相关描述。关于资源的相关描述,可以定义为是资源的属性。对资源属性的定义有助于大盘属性的一致性,统一性,也有助于更规范,科学的描述资源。不同资源会有不同的属性,此处仅以函数计算的服务、函数两个资源举例说明:
服务(Service):service
- name: 名称
- region:地区
- vpc:网络
- logs:日志
函数(Function):function
- name: 名称
- runtime:运行时
- timeout:超时
- memory-size:内存
4.1.3.1.5 资源的数据结构
针对上面的资源种类、关系以及资源的属性等,可以对资源的数据结构进行规约。可以从资源、属性两个层面进行规约,例如:
资源层级:
- 服务:
/services/{service-name}
- 函数:
/services/{service-name}/functions/{function-name}
- 触发器:
/services/{service-name}/functions/{function-name}/triggers/{trigger-name}
- 服务:
属性层级:
- 服务:
{ "Name": "名称", "Region": "地区", "Vpc": "网络", "Logs": "日志" }
- 函数:
{ "Name": "名称", "Runtime": "运行时", "Timeout": "超时", "MemorySize": "日志" }
此时的数据结构只是一个非常简单的数据结构,虽然可以比较直观的展示,但是却不能被直观的描述,例如参数的必要性、取值范围、取值类型等。所以这一部分可以参考OpenAPI 3.0的规范,对属性进行规约,包括:
- 描述
- 取值
- 是否必填
- ......
以Path字段为例:
paths:
/services/{service-name}:
description: '服务相关操作'
summary: '可以进行服务的查询与服务的升级,删除'
get:
description: '获取服务详情'
operationId: get service information
tags:
- service
- get
parameters:
- name: service-name
in: path
description: 服务名称
required: true
schema:
type: string
- name: Aignature
in: cookie
description: 用户签名
required: true
schema:
type: string
可以看到,通过这样一个描述,不仅可以清楚的知道路径的结构,还可以对参数等有一个比较直观,清晰的定义。
4.1.3.1.6 自定义资源
在规范制定的过程中,自定义资源的规范是非常重要的。即使在上层做了足够的抽象,随着业务的不断发展,API相关资源也会出现比较明显的升级,此时就会出现一些之前没有抽象和定义的资源;当然,由于某些业务的属性,也可能出现某些特殊的资源没有被抽象。在进行资源自定义时,通常需要明确:
资源名:
- 只能是名词;
- 必须要小写;
- 链接符一律用'-';
资源属性:
- 属性必须用JSON表达;
- 属性名只能用名词;
- 属性名必须小写;
- 属性名链接符一律用'-';
- 属性规约时要明确取值的类型;
资源与其他资源的关联关系:
- 要写出上下层的关系,以及是否必须存在;
4.1.3.2 方法
4.1.3.2.1 上层抽象方法
同样以阿里云函数计算产品为例,在日常使用过程中通常会有一些通用的方法,例如获取服务列表、获取函数列表、获取触发器列表、获取版本列表、获取别名列表等,类似于这种比较通用的方法,在规范制定的时候是要进行统一抽象的,例如阿里云函数计算部分抽象的方法可以包括:
- 获取资源列表:LIST
- 获取资源详情:GET
- 删除资源:DELETE
- 升级资源:UPDATE
- 创建资源:CREATE
4.1.3.2.2 上层抽象方法与HTTP映射
由于方法需要与HTTP有比较明确的映射,所以在抽象方法之后,还需要对上层抽象的方法与HTTP进行有效映射,这种映射同样不可出现二义性。同样以上面抽象的方法为例,对应的方法与HTTP方法的映射可以是:
获取资源列表:LIST
- 映射为GET方法
- 获取服务列表:
GET /services
获取资源详情:GET
- 映射为GET方法
- 获取服务列表:
GET /services/{service-name}
删除资源:DELETE
- 映射为DELETE方法
- 获取服务列表:
DELETE /services/{service-name}
升级资源:UPDATE
- 映射为POST方法
- 获取服务列表:
POST /services/{service-name}
,同时JSON作为请求体,传入变更资源的属性和取值
创建资源:CREATE
- 映射为POST方法
- 获取服务列表:
POST /services/{service-name}
,同时JSON作为请求体,传入创建的资源属性和取值
4.1.3.2.3 自定义方法
与资源一样,尽管我们抽象出来了很多上层方法或者通用方法,但是由于业务的多样性以及复杂性,在实际生产过程中,往往还需要对自定义方法进行规范。自定义方法指的是,有一些方法可能在上面的内容中未涉及到,但是实际产生了,例如在未来使用函数计算的时候,可能会有停止实例,那么此时就可以规约一个规范:
/some/resource/name:customVerb
例如,暂停某个函数的某个实例就可以是:
POST /services/{service-name}/functions/{function-name}/instances/{instances-id}/name:stop
4.1.3.3 错误
4.1.3.3.1 错误状态码
在RESTful的规范中,通常会利用HTTP的状态码进行错误状态的描述,以阿里云函数计算产品为例,可以通过HTTP的状态码进行一些错误类型的判定,以400、403、404、500等相对简单的错误码为例:
- 400:参数、路径等相关问题
- 403:权限等相关问题
- 404:资源不存在等相关问题
- 500:系统错误等相关问题
4.1.3.3.2 错误码/多级
在实际生产中,由于业务的错误往往是非常复杂的,单纯依靠某些状态码是没办法更好的做错误的捕捉或者定位,此时可以考虑错误码的引入以及多级错误码的引入,同样以阿里云函数计算常见的错误为例:
400:
- InvalidArgument:参数无效
- MissingRequiredHeader:缺失必要的请求Header
- PathNotSupported:请求的API路径不正确
- EntityTooLarge:函数的入参太大
403:
- AccessDenied:账号权限不足
- SignatureNotMatch:请求的数字签名不匹配
- InvalidAccessKeyID:传入的AccessKey ID不正确
404:
- ServiceNotFound:服务不存在
- AliasNotFound:别名不存在
- DomainNameNotFound:域名不存在
- VersionNotFound:版本不存在
500:
- InternalServerError:内部错误
当然如果业务涉及到的服务/产品过多,也可以考虑多级错误码,例如
400:
InvalidArgument:
- TypeError:类型错误
- RangeError:取值范围错误
4.1.3.3.3 自定义错误
所谓的自定义错误是说,在实际生产过程中,所提取出来的通用的错误码实际上并不能满足所有业务诉求,却是在一定的时候有一些业务需要自定义错误码,所以此时需要给出自定义错误的规范:
- 大驼峰形式
- 具备一定的抽象能力
- 全部为名词
4.1.3.3.4 错误响应结构
此处需要规约一个错误的响应结构,例如Google Cloud的结构为:
{
"error": {
"code": 400,
"message": "API key not valid. Please pass a valid API key.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "API_KEY_INVALID",
"domain": "googleapis.com",
"metadata": {
"service": "translate.googleapis.com"
}
}
]
}
}
可以在此基础上,定义我们的多级错误结构:
{
"error": {
"code": 400,
"message": "Type error message",
"status": ["InvalidArgument", "TypeError"]
"details": [
{
"reason": "API_KEY_INVALID",
"domain": "example.com",
"metadata": {
"service": "fc.aliyun.com"
}
}
]
}
}
4.1.3.4 字段
4.1.3.4.1 命名规范
在定制相关的规范中,要明确命名的意义:
- 简单
- 直观
- 一致
所以,就要对命名进行一个详细的规约,例如:
- url参数全部采用小写,用"-"进行参数链接;
- 请求体参数全部采用大驼峰形式;
4.1.3.4.2 组合抽象
可以提供一些单独的抽象词汇,供用户组合。
例如:动词create和名次time组合到一起可以是:create-time或者CreateTime(根据不同位置,可能组合方法不同);类似的抽象词汇可以有:get
、list
、create
、update
、remove
、delete
、service
、function
、trigger
、version
、layer
等;
4.1.3.4.3 上层抽象
可以对一些通用的内容进行命名,这样有助于业务在使用的时候,将某些内容更加统一和规范,例如:
- 创建时间:CreateTime
- 运行时:Runtime
- 内存限制:MemorySize
除此之外还应该多一些固定的,常用的缩写进行抽象提取:
- config (configuration)
- id (identifier)
- spec (specification)
- stats (statistics)
4.1.4 工具是执行规则,是最终收口与统一性的保障
工欲善其事,必先利其器。若想在多团队协作的模式下,大规模交付高质量、高一致性的RESTful API及配套产物,不仅要有完善的流程和规范,更要有规范对应的检验工具,否则“总会有人在触犯法律的边缘线上不断试探”,此时一个“统一API发布平台”就显得尤为重要。无论是阿里云的POP团队,还是腾讯云的云API团队,其都有一套自己的统一API发布平台,通过这样一个统一平台:
- 一方面可以保证所发布的API在一定程度上的统一性;
- 另一方面也便于管理和统一抽象API网关层的能力,以及便于更加规范,统一的发布周边配套能力;
如上图所示,在这个API发布平台上,基本角色(除系统管理员之外)主要分为业务开发,业务负责人,API网关团队;每个角色有每个角色的主要工作内容,整个平台,通过不同角色的协同,大规模交付高质量、高一致性的RESTful API及配套产物。同样,在这个平台上其主要功能包括产品/业务的管理,API的录入,测试,审核,发布,同时也会包括API发布后的配套能力的发布,例如API、SDK、API Explorer、错误中心等。
平台的优势:
- 可以让API正式对外暴露的部分统一收口,进行统一的审核,可以确保API的高度一致性;
- 可以最大可能性的保证API发布时的规范性和兼容性;
- 当规范高度统一时,更有利于上层统一抽象SDK,API文档以及API Explorer等能力;(如果规范不统一,哪怕只有细微差距,可能都会对上层的建设带来极大的维护成本,包袱)
- 可以尽可能保证API的整体流程的规范性,经过多次审核和验证,可以尽可能的保障接口的安全和稳定;
可能带来的问题:
- 由于流程加长,在一定程度上会降低“敏捷性”;
- 由于需要在一个新的系统中维护API,可能会增加一定的学习成本和运维成本;
针对上面的问题,进行以下解答:
- 敏捷性是建立在安全和稳定的前提下,流程虽然增加,但是整体的安全性和稳定性得到了更大的保障,这是值得的;
- 引入一个新的系统目的,并不是引入一个“麻烦”,其核心价值在于后期正整体的维护流程的缩短和自动化,包括测试的自动化流程化等,也包括周边配套措施和服务的流程化、自动化;
4.2 对外工具的高度一致性建设方案
众所周知,一个大的产品往往是由内部多个部门协同进行研发,一个大的产品对外暴露的API,往往也是由内部多个团队协同完成。但是对外暴露时,外部用户并不会关注是多少个团队,关注的更多是功能是否完整,是否统一,对自己的开发、对接是否有帮助,自己是否可以信任这个团队。
此时一致性就显得尤为重要,此时的一致性其实是包括了几个方面:
- API的形态、风格、规范对外表现的一致性;
- 不同模块的的辅助工具,以及工具的相关使用方法的一致性;
所以往往我们的开放API在对外暴露之后,往往不仅要有一致的API风格,规范,其周边的配套产物的一致性也是至关重要的,而配套产物的一致性在一定程度上高度依赖。
对外工具或者说时对外的配套产物一致性其实是对整体API规范的遵循程度的一种巨大的考验。试想,一个大型服务对外暴露1000个RESTful风格的API,其中999个均遵循预定的规范和流程进行发布,其中有一个时特殊的API,完全不符合规范,此时在进行对外的统一工具的封装,或者配套产物的生成,就要针对这一个API做额外的判断和处理,如果这类不规范的API不止是1个,而是十个,二十个,那么所带来的就是对外配套产物的安全性和稳定的巨大挑战。
如上图所示,对外工具的高度一致性建设方案是要建设在内部风格的统一、规范的统一、流程的规范以及对外发布的API的一致性,只有这样才可以通过规则,安全、稳定的生成统一风格的配套产物。
5 总结
综上所述,虽然RESTful API更多来说是一种风格,但是其凭借着近些年的发展,已经逐渐的成为了API生态中的事实标准。随着时间的发展,不仅有开发者普适性认同的RESTful规范,也有Apache基金会的明星项目OpenAPI3.0等;当然,各个厂商为了可以让自己的开放API与自身业务匹配度更高,也逐渐的推出定制化的RESTful风格的开放API,这其中比较典型的例子是Github以及Google Cloud。
尽管RESTful这种风格非常流行,已经逐渐成为领域内的事实标准,OpenAPI 3.0这些规范也日趋成熟,行业内也有Google Cloud,Github这类优秀的API设计文档作为参考和学习,但是在如何大规模交付高质量、高一致性的RESTful API及配套产物这个问题上,依旧面临着巨大的挑战。
本文通过腾讯云云API平台以及阿里云POP平台进行分析,以阿里云Serverless产品为例,总结出:
- 流程是整个API从零到一的生命线;
- 风格是是API的气质,是道德约束
- 规范是法律条款,是一种强约束
- 工具是执行规则,是最终收口与统一性的保障
即若想大规模交付高质量、高一致性的RESTful API及配套产物,我们必然要从风格的明确、规范的定制、工具的完善、流程的清晰入手,只有在完善、科学的流程下,让每个人都清楚风格,遵循规范,再通过工具进行强校验,审核,收口,才有助于大规模交付高质量、高一致性的RESTful API,同时也有助于通过标准化、自动化的手段生成API文档、SDK,API Explorer等配套产物。
无论是腾讯云还是阿里云,统一对外暴露的开放API平台都是必不可少的收口工具;无论是Google Cloud还是Github,一套优秀的RESTful风格的API,都是离不开完善的规范文档,设计指南的约定。