API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API 则注定会导致系统的后续发展和维护非常困难。比较好的API设计样板可以参考 github 和 k8s ,它们都是典型的RESTful接口。云服务对外开放的窗口就是OpenAPI,今天要讨论的话题是“云服务场景下OpenAPI设计的挑战”。
为什么要有API规范
之所以强调“云服务”的原因在于,小规模独立API的设计与大规模批量生产API面临的问题是不一样的。同样,只专注于自身产品API的可用性与从更高的层次去看云服务整体API体系的健壮性,要建设的体系也是不一样的。
例如,做一个WEB页面使用的API,只需要考虑性能、稳定性、鉴权就好,因为页面与API是一体的,可以一起发布和回滚,只要功能正常,即便API设计有缺陷,用户也可以接受。而云服务要开放API考虑问题就多了:
- 首先,云服务开放的是基础设施和服务接口,一般是系统级的对接,API一旦开放想要变更就非常困难;
- 其次,云服务并非单独运行,不同的产品实际场景中是互相组合的,需考虑产品间的一致性和互通便利性;
- 云服务API数量庞大,为了更方便使用,配套的API查找、编排、自动化生成SDK等需求也比普通服务强烈;
- 云服务的稳定性非常重要,核心产品的稳定性就是客户的生命线,要求非常高。
所以云服务由于产品线众多,需要统一的API规范来保证云产品间API体验一致,在底层平台层面互相兼容,便于上层应用平台化,同时要兼顾到围绕公有云服务的第三方生态、开源生态、企业生态。
在具体规范层面,业界也在不断尝试,比较知名的是OpenAPI Specfication和 Google API Design Guide。前者针对RESTful API设计在细节层面给出了非常具体的规定,已经成为RESTful API设计领域的事实标准,而后者则主要从云厂商的角度提出许多最佳实践性质的规范与建议,这些原则不仅仅适用于RESTful API,也适合其他类型API设计。对API设计感兴趣的开发者,可以详细研究一下资料。
2018年Openapi Specification发布了3.0版本
图片来源:https://swagger.io/
因此,对于云服务商来说,在关键环节制定明确的API规范有助于云服务对内提高产品间互通的效率,对外提供一致的使用体验,有助于云服务更好地被集成。那么要做好云服务的横向规范会碰到哪些困难呢?本文就从实践中总结了7大挑战。
挑战1:选择API设计模式
当你在考虑单个产品的API表现形式时,首先会选择一种具体的API风格,常见的有RPC(Remote Procedure Call)和ROA(the Rest-Oriented Architecture)两种模式,然后针对复杂的数据结构你会考虑使用什么样的序列化协议,常见的包括Json/Xml/WSDL/Hessian等,用于封装传输数据。但涉及到不同的产品时,在具体选型时考虑的问题可能就不太一样了。
gRPC示意图
图片来源:https://www.grpc.io/docs/guides/
虽然RESTful设计风格曝光率很高,但并不是所有云服务商都选择了完全遵循RESTful,例如AWS和阿里云RPC风格反而占了大多数,Google和Azure则RESTful居多。
Restful API的优势是HTTP具备更好的易用性,让异构系统更容易集成,且开发执行效率比较高,面向资源要求也比较高。而RPC API可以使用更广泛的框架和方案,技术层面更底层也更为灵活,设计起来相对简单,掌握起来有一定门槛,架构上更加复杂。
RESTful与RPC模式对比
不同的团队根据实际情况和业务形态可能选择不同的方案,那云服务作为一个整体应该强制统一好还是随意选择好?如果强制统一风格,有些适合RESTful风格的服务非要使用RPC的话,看起来就会比较丑陋,如果只是一个过程化的服务调用,往RESTful资源化设计方向去靠会比较困难。但如果不强制使用统一风格,会造成针对API的体系化支持会更加复杂,例如为兼容两种风格SDK的自动化支持需要两套代码。
通常RESTful风格对API设计者的要求是比较高的,主要的难点在于面向资源设计要求开发者事先做好规划,将后端数据模型与API服务模型相匹配。所以RESTful API的开发者应该是熟知系统整体实体关系模型的,很难想象一个不懂后台业务的新手能设计出合理的API来。那么是不是RPC风格API就不需要面向资源设计了呢?从实践中来看,并不是!RPC风格的API也需要资源模型来支持,在下一节中会重点分析。
所以,对于云服务商来说,选择API风格时要考虑几个问题:
- 选择支持哪种风格,才能更好地体现业务特性,让客户操作起来更加方便;
- 设计API时能否面向资源设计,相应的工程人员是否具备做这种设计的能力;
- 针对这种风格工具链的支持是否到位,投入产出比如何;
- 业界流行的趋势如何,是否需要考虑与其他系统体系的互操作。
选定了具体设计模式后,就要努力做到统一,避免多种模式混杂带来管理成本的上升。如果确实有必要两种方式都支持(例如阿里云就同时支持RPC和ROA),就需要在技术上做好充分的准备。
挑战2:面向资源设计API
用户使用API来访问云服务,本质上是想通过对某种云资源执行特定的操作来完成一个业务动作。Restful API需要面向资源设计众所周知,那为什么RPC接口在云服务场景下也需要有资源模型呢?
RPC形式的API组织形态是领域和行为(对象和方法),随着时间的推移,API越来越多最终形成一个庞大的集合。以阿里云为例,对外开放的OpenAPI数量已经达到10000多个,涵盖了接近200个不同的产品。因为开发者必须单独学习每个API,耗时又容易出错,如果没有一个脉络的理解起来比较困难。如果有一套标准的资源模型,API就可以按照资源模型的维度分类组织,用户使用起来也会有迹可循,体验上会更好,否则面对如此多的API一点点学习无疑是个痛苦的过程。
另外,云服务并非单个服务的简单排列,它是多个体系的横向整合,总体对外呈现出有机连接。随着云计算的发展,企业客户对对云服务的要求不断提高。最典型的就是当企业客户大规模开始上云后,对虚拟化的云产品提出了各种管理需求,例如鉴权、编排、弹性伸缩等。
企业客户对云服务的管理需求
以最常用的鉴权功能为例,客户创建了一组云服务器来跑业务,运维人员需要有重启服务器的权限,其他角色人员则不需要此类权限,针对RestartServer这么一个API就需要进行权限控制了。权限在云平台中一般是中心式管理的,单独的云产品不需要分别管理。如果统一处理鉴权,就需要知道当前API正在操作什么资源,与此相关的资源有哪些(例如与服务器有关的资源包括磁盘、网卡、网络等关联资源),然后针对所有相关资源逐一鉴权才能确认API操作是否可行。
上面提到的资源有两个关键点,一是要有统一的资源模型,便于云产品对特定的API进行鉴权,二是要明确资源关系,当出现资源依赖的时候,需要关联鉴权。在Google的API Guide中就明确提到需要资源关系,可以看出资源关系不仅仅是用来做API设计建模的,它对于平台功能的实现也有至关重要的作用。不了解资源关系,有可能把多个资源封装到一个API中,使用和变更都很痛苦,即便后期发现了问题再补救拆开,由于很多用户已经在使用了,要付出的开发成本和沟通成本也是极大的,甚至成为不可能。所以清晰的资源模型有利于梳理清楚API体系。
定义清晰的实体关系图有助于设计出更为完整的API
而且,明确的资源模型对于构建云上运维管理基础设施至关重要,例如可以通过对资源打Tag来对资源进行分类管理(参考阿里云资源Tag),分组授权(参考阿里云资源组),资源审计(参考阿里云CloudConfig),类似开源软件Terraform这样的编排工具,由于有资源模型会更容易接入和使用。
所以,统一的资源模型对云服务的帮助是巨大的:
- 它可以使API具有更清晰的结构,帮助用户理解;
- 它可以帮助对比API与后台实体关系模型,更容易提供更完整的API服务;
- 它可以使产品协作更加顺畅,对资源的操作也更加规范化;
- 它可以使云服务底层平台实现起来更统一、更方便;
- 它可以使围绕API的生态集成起来更加简单、高效。
既然有这么多好处,那么众多的云产品在实际设计API的时候能否坚持以资源为中心,充分考虑到上下游的需求就变成一个很大的挑战了。
挑战3:API设计风格
确定了设计模式和资源模型后,是时候进入到API设计细节了。诸如API名称、参数名、属性名称、数据格式、错误码之类的信息,看起来根本不是问题。单个产品问题还不大,只要保证内部风格一致即可,如果考虑到云服务多产品对外的整体体验,就有必要考虑以下问题:
- 在API命名的时候,遵循什么样的范式来确保大体风格相似?动词、名词、介词如何组合才能保持API风格看起来比较统一,降低理解成本?
- 对于类似的操作,有没有使用规范?有哪些公共的标准词汇使得同类型的操作可以比较容易理解,避免使用晦涩奇怪的词汇(例如读操作,Read/Query/Describe/List/Get中都在什么场合使用什么动词)?
- 被广泛使用的参数如何尽可能保持一致,避免不同产品的表达混乱的情况(例如分页参数用PageNumber还是PageNum)?
- 对于常用的场景,例如幂等、分页、异步API的设计有没有统一的规范,避免使用体验不一致?
- 错误码应该怎么设计?公共错误码怎么统一,业务错误码怎么表达?
上述问题都是实际研发过程中要注意的,要全部罗列的话远不止这些。API的用词描述是云服务展现给外部用户的第一印象,绝非随意写就。对人员有一定规模,内部有多条产品线的组织来说,如何协调组织的各个部分对外具有统一的体验是个很大挑战。
回顾下HTTP协议,最广为认知的是对HTTP Mehod的约定,使用9个单词就完成了对基本动作的规范,为开发者提供了清晰的思维模型:
Http Method 类型
图片来源:https://tools.ietf.org/html/rfc7231
同样,在HTTP Header里面也对浏览器信息、语言、网络连接属性等做了详细的规定,这样开发者在使用HTTP服务的时候都有一个大致的约定,在关键信息上面不会有偏差,保障了异构系统的接口一致性。
因此云服务在管理API时应该考虑一些具体的规范,对命名规则、标准词汇、最佳实践模式、错误码等信息都有明确的规定,同时用系统化、平台化的手段来管理API,确保不走偏。设计风格不是云服务API设计中致命的问题,但是它关乎云服务外表形象,不可不察。
挑战4:服务端容错处理
API是后端服务的外部表达,是服务就有可能出现问题,无论这个问题是可预期的还是不可预期的。如果只考虑功能本身功能特性,而忽视对异常情况的设计,当问题出现的时候业务本身可能无法感知造成服务异常,更重要的是站在客户角度去看,不能有效获取错误原因是非常痛苦的,很多时候只能束手无策,降低云服务提供商的整体口碑,甚至损害营收。
假设有个创建资源的API,每调用一次都会创建新的资源,考虑以下情况:
- 同样的请求多次提交,是否会重复创建资源?
- 请求处理时间过长,客户端是长时间等待,还是先异步返回一个任务ID?
- 如果需要等待,Timeout最大值是多少?
- 如果Timeout最大值达到,客户端的策略是重试还是放弃
- 如果最终处理还是失败了,具体是哪个环节的问题?如何给出准确的错误信息?
- 如果异步方式,异步处理完成后是主动查询还是另有通知?
- 第三方工具和集成商到哪里去获取这些信息?能不能有标准化的处理?
实践中,如果不做好问题a的处理,可能会造成系统异常情况下大批资源被重复创建,有可能造成用户或云服务商资损;问题b-e没有处理好,可能会让用户陷入盲目等待或者盲目重试,使用体验和效率极差;而f-g关系到自动化处理工具如何做到效率最大化,也关系到被集成的效率。
一个异步重试的状态机
当出现异常的时候,API一般是要靠错误码来告知用户有什么问题。HTTP协议本身对错误码做出了详尽的规定,Restful的API要尽可能地符合标准。除此之外,云服务有必要在此基础上进一步提供业务错误码和错误信息,来描述错误具体的细节。
当前云服务的错误码很多,看起来非常专业,但问题主要集中在以下几个方面:
1.错误类型过多:错误码越多客户端处理起来越方便吗?看一下HTTP的错误码,只有5大类加几十个子类的错误码就涵盖了所有场景,而通常大家耳熟能详的无非是200、400、404、500、502等屈指可数的状态码。有的人考虑错误码越多,客户端可以switch/case一下针对每个错误码做不同的操作,这个就见仁见智了,一般的程序员可能不会写那么多的分支流程,必要性也不大。
2.错误信息表达不够充分:相比于提供大量的错误码,错误信息的明确表达更为重要。如果错误信息不够详细,作为用户不了解细节无法掌控的云服务,几乎是无法明确发生了什么,要么提工单,要么只能被动等待,客户体验很差。
3.业务错误码与HTTP错误码含义不匹配:例如参数错误应该返回4xx系列Code,返回5xx系列就不够专业。即便是RPC风格的API,也要大致符合HTTP规则,否则可能会给一些依赖HTTP Code的系统造成误导。网上有种思路觉得无论后台响应如何,HTTP Code统统返回200,在Body里面的错误信息体现异常信息,这种不利于对接口的监控,因为监控系统很难通过识别消息体来鉴别功能是否正常响应。
4.相同错误不同云产品表达不一致:这会给客户端开发造成困扰,增加开发工作量,不利于自动化集成,用户体验比较差。
5.错误码应该是可枚举集合:一个API能够产生的错误码类型应该是可预期的,即便是业务升级,也应该给客户提供明确的错误码列表,不能随心所欲。因为用户端需要明确知道可能会发生什么,而不是随时可能出现不可预知的错误类型。如果错误类型不确定,就意味着针对错误码分支处理基本是无效的。
要做好服务端容错上述问题,需要从云服务整体层面加强API的容错设计,做好错误码规范,加强对错误信息的管理,来提升用户体验。
挑战5:版本管理
API都是不断迭代的,通常都需要版本管理。云服务API的版本管理尤其重要,主要是以下原因:
- 云服务API直接面向用户,由于调用量大,变更影响的用户范围也更广,版本变更要非常谨慎;
- 云服务有多种形态,主要是公有云、私有云、细分的行业云等,不同的云对同样功能API的要求可能不一样,API更加多样化。既要保障不同版本功能正常,又要能快速部署维护不同版本,挑战很大;
- 不兼容变更的推广极其困难,很难让用户在短时间内快速升级,维护多版本API成本很高;
- 必须变更的情况,要确保调用方能够及时感知并且平滑切换的技术难度很大。
针对API各种场景的管理,需要一套成熟的API管理平台,照顾到各种场景的需求,也是一项不小的挑战。
挑战6:API该如何开放
API如何开放看起来是奇怪的问题,难道API做出来不就是开放给别人用的吗?做好就开放就开放啊?但在云服务场景下,情况会更复杂一点!
产品技术都是在不断迭代的,功能始终在增加,新的API层出不穷。从客户视角来看,他们对已开发完毕的API是否开放的需求是什么?假设一个云产品有100个功能特性,20个只能保留在内部,官网上总共提供了80个特性,而公开API只开放了50个,这是用户期望的状态吗?理想状态应该是什么?
围绕云服务有一个庞大的生态,除了普通的开发者,还有许多第三方服务商、企业客户、开源工具。当我们考虑所有这些生态成员的需求时会发现:云服务自身拥有的功能集合与客户能使用的功能集合之间的差异比较能体现产品的开放程度。这里的差异强调的是云服务已经拥有的,不包括云服务自身没有的服务。客户能使用的功能与云服务能提供的功能之间的差距越大,说明云产品的开放程度越低,因为很多功能都作为保留节目被雪藏了,导致客户和第三方服务商无法享受与云厂商接近的服务,最终生态无法拓展。理想状况是,云服务商自身能通过API使用的功能,客户都能通过OpenAPI使用,内外看到的是一致的。
但具体到某个API应不应该开放,实践中会做如下考虑:
- API刚刚上线尚未打磨充分,贸然开放可能会留下隐患,再想调整为时已晚,所以选择先不开放。
- API本身并非原子化,封装了若干业务场景,主要目的是优化性能或者服务特定的客户,并不需要开放给所有用户;而且当业务场景发生变化的时候,调整起来也比较困难,不适合开放出去。
- 某些API不适合开放给全部用户,只能部分开放,例如特定行业专业的API。
- API仅供特定场景或私有场景使用,需要外部能够访问,但是不能开放给用户。
针对这些问题,需要在API的管理机制上面下功夫,能够区分不同的场景,并做好元数据的管理。针对API是否开放要有明确的规范和度量机制,确保该开放的API都可以开放,确保内外客户看到的功能集合基本一致,才有利于云服务更好地被集成,在客户的业务中发挥更大的作用。
挑战7:API体系如何打通
在云服务场景中,API并非孤立存在:
首先,API发布以后,用户要想顺利地使用API,配套设施必不可少,SDK、文档、工具链的集成都需要考虑到,这里的重点是如何保障准确性、及时性和一致性。开发工程师一般都不太喜欢写文档,专业写文档的又可能不太懂技术,再考虑到国际化的问题,就十分有挑战了。SDK方面,一个API要有多种语言的实现,每种语言还要保障其专业性、可用性,非常考验对开发人员编程语言掌握的深度和对API的理解,业界经常采用的自动化生成SDK的方式也会考验对多语言的兼容能力。工具链比如阿里云的 API Explorer、CloudShell等产品也需要及时与API的最新状态保持同步。
其次,云服务由于产品线众多,如何让用户能够快速学习使用API和相关工具,需要在教程、案例、运行时环境等诸多方面加强建设。围绕云服务,已经发展出许多上层生态工具,例如terraform/ansible/spinnaker等开源软件,它们能够帮助云服务更好地使用起来,必须对它们提供支持,如何能够快速覆盖也对平台开发能力是个考验。
另外,API本身的质量保障也是非常重要的。一般都要考虑性能、稳定性、安全等方面的保障体系,通过压测、监控、部署防护软件等方式来确保API在服务的时候不会掉链子。传统的套路在解决系统问题时非常有效,但具体到业务问题的时候就无能为力了。例如,一个创建服务器的API一般来说都是要求幂等的,怎么检测该API实际上有没有做到幂等呢?推而广之,其他业务流程的正确性又如何保障呢?等API开放了发现问题再修复就为时已晚,显然应该在上线前通过测试来发现这类问题。但是随着业务的发展,如何能保障这类问题可以有统一的解决方案,能够长期跟进及时发现风险避免损失呢?
阿里云API体系简易图
所谓量变引起质变,上述问题针对单个API的时候都好解决,但是当API规模达到成千上万的时候,就必须通过平台化、系统化的手段来解决了。例如,API服务可靠性SLA指标如果要达到4个9,需要制定明确的标准,并且有手段监控到所有API的运行结果,通过分析成功率来判断其是否达到预期水平,这对云服务本身的底层系统建设提出了较高的要求。
所以,以API为中心完善相关体系,保障用户体验的一致性、及时性、稳定性、易用性是非常有挑战的。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
https://tools.ietf.org/html/rfc7231#section-4
https://www.cnblogs.com/sparkdev/p/10052310.html
https://www.grpc.io/docs/guides/
https://www.terraform.io/docs/index.html
http://www.grabsun.com/article/2015/1135807.html