本期作者 陈雨润哔哩哔哩开发工程师 引言 API管理是应用开发中不可或缺的一部分。在早期服务数量不多的情况下,团队可以自行负责API管理。但随着公司规模逐渐扩张,业务接口数量爆炸式增长,此时API管理的任务应由统一的接口管理平台来承担,结束各自为政的局面。统一管理能够最大程度地发挥API的价值,减少跨部门沟通与协作的成本。本期文章将带领大家一窥B站在API管理方面所作的设计与思考,重点介绍我们是如何收集API元信息并对其进行井井有条的管理,又是如何配置这些庞大API资源来减轻业务管理负担,粘合跨部门间合作。 管理现状 目前,我们的元信息统一管理平台线上接口数达到12w,应用(含测试应用)总数近2w。如此庞大规模的接口由平台统一收集并管理,可节约大量人力维护与沟通成本,契合当前降本增效的主基调。 01服务上线流程 一个服务的上线过程通常分为这样几个阶段: 1。需求评审与分析 PM输出需求,PMO组织需求评审会,业务线开发评估需求的开发方案与所需工时,从而确定迭代周期。 2。撰写与维护API文档 在项目开发中,Web项目的前后端分离开发,需要由前后端开发共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发,一直维护到项目结束。API文档在后端技术方案确定后即可编写。尽可能早地提供给对接方,有助于对接方提前思考实现方式和规避隐患。 3。前后端联调与测试 前端根据API文档初步实现功能,后端在开发完成后发布至测试环境提供给前端联调。 4。发布上线 当一切就绪后,服务被发布至线上,API开始对外提供服务,需求上线。 上述整个过程中都离不开对接口文档的管理,一个优秀的接口文档能够让前端与后端开发人员更好地配合,提高工作效率,方便新加入的成员查看和维护接口、测试人员进行接口测试。 02API管理 2。1为什么需要对API进行统一管理 在过去没有统一管理的模式下,虽说每个团队每个项目都有编写API文档的意识,但免不了出现各种管理模式的差异,例如A团队习惯将文档编写在知识库中,B团队习惯将文档用swagger生成并托管至版本管理系统等等。这种管理模式上的差异会直接导致对接沟通上的低效,无法及时得发现API的异常,难以管理接口的版本迭代。因此我们始终推荐对API进行统一管理,降低对接时的沟通成本,并在接口出现变更时及时同步调用方,减少信息gap,通过标准化的中心收集模式敏锐地捕捉到每一次接口调整。 2。2API元信息的收集与更新 在整个API管理过程中,首先需要保证接口元信息完备性和准确性,管理平台需要充分收集接口信息。B站的接口元信息的收集之路:手动维护自动生成。 2。2。1手动维护 过去,我们在内网私有化部署过一套YAPI,通过部门、业务域、应用的三级划分的粒度管理着各个服务的接口。研发通过在YAPI的可视化界面上手动录入应用的详细接口信息。接口发布后,前后端的研发根据文档着手进行编码,测试同学则根据文档上的接口逐个进行测试,负责人对该文档进行审批。总之,项目干系方始终都会围绕着这份文档来推进项目。 手动维护的缺点:人力成本高 市面上有很多的五花八门的API信息管理平台,如知名的Eolink、YAPI、Apifox、Postman,但无论部署哪个平台都无法解决一个非常核心的问题:数据的来源始终是人,需要人工去操作与更新。尤其在项目的开发阶段,接口文档的改动频次实际上是很高的,需要开发同学多次到平台上调整接口文档,保证接口数据始终正确。信息同步不及时 手工模式下,开发同学每次完成接口的增改都需要及时到平台上同步最新的改动并通知具体的订阅方。若某次变更未被及时同步,造成调用方与被调方之间信息不对齐,很容易造成故障。 2。2。2自动生成 接口管理平台的作用是自动采集应用API并生成一份详细且准确的接口文档,使开发将精力全部集中在API本身的设计上,无需额外关注接口文档的撰写与维护,从而解放研发同学的双手,提高开发效率。 为此,我们设计了如下架构,研发同学遵循统一的API标准定义接口,走完正常应用打包上线流程,接口采集自动完成。 代码中定义接口 对于习惯使用Golang进行开发的同学: Demoservicerespondstoincomingrequests。serviceDemoService{option(google。api。defaulthost)api。example。com;DemoBodymethodreceivesasimplemessageandreturnsit。rpcDemoBody(SimpleMessage)returns(SimpleMessage){option(google。api。http){post:pocprobedemobodybody:};}}请求、回复消息messageSimpleMessage{int32id1〔(google。api。fieldbehavior)REQUIRED〕;Embeddedembedded2;}messageEmbedded{int64int64val1〔(gogoproto。moretags)default:1〕;stringstringval2;一个字符串列表repeatedstringrepeatedstringval3;一个字符串Mapmapstring,stringmapstringval4;} 上述Proto片段中定义了一个名称为DemoService的RPC服务,该服务包含一个简单的RPC方法DemoBody,并且引入Google官方提供的annotations。proto对该gRPCAPI增加HTTPPost方法的拓展定义。这种使用ProtobufIDL定义对应的RESTAPI和gRPCAPI的方式是GoogleAPI指南中所推荐的最佳实践,也是B站在KratosV2框架中定义API的方式。对于框架是如何注册HTTP与gRPC服务感兴趣的同学欢迎体验Kratos框架,这里先不详细展开。DemoService中除了对HTTP方法的定义,还包括服务概要,默认域名等标记。在Message中除了字段类型的定义,某些字段还带有属性行为的标记。我们支持用户使用gogoproto以及google。api。fieldbehavior中定义的消息对字段进行一些特殊标记,如定义字段默认值,是否必填,示例用法等参数相关的属性。 info:title:DemoServiceAPIdescription:Demoservicerespondstoincomingrequests。paths:bilibili。api。probe。v1。DemoServiceDemoBody:。。。(同pocprobedemobody)pocprobedemobody:post:tags:DemoServicedescription:DemoBodymethodreceivesasimplemessageandreturnsit。operationId:DemoServiceDemoBodyrequestBody:content:applicationjson:schema:ref:componentsschemasbilibili。api。probe。v1。SimpleMessagerequired:trueresponses:200:description:OKcontent:applicationjson:schema:ref:componentsschemasbilibili。api。probe。v1。SimpleMessagecomponents:schemas:bilibili。api。probe。v1。Embedded:type:objectproperties:stringVal:type:stringdefault:hellorepeatedStringVal:type:arrayitems:type:stringdescription:一个字符串列表mapStringVal:type:objectadditionalProperties:type:stringdescription:一个字符串Mapbilibili。api。probe。v1。SimpleMessage:required:idtype:objectproperties:id:type:integerformat:int32embedded:ref:componentsschemasbilibili。api。probe。v1。Embeddeddescription:请求、回复消息tags:name:DemoService 最终通过工具生成上述对应OpenAPI文档,为Proto中对HTTP方法的定义提供标准OpenAPI格式的接口信息,将gRPCMethod视为POST方法,生成一条类似的接口信息。 对于习惯使用JAVA进行开发的同学而言,同样地: GetMapping(demo)Operation(summary用户接口debug,description示例)Parameter(namecount,requiredfalse)publicStringdebug(RequestParam(defaultValue128,requiredfalse)intcount){StringrandomStringRandomStringUtils。randomAlphabetic(count);LOGGER。debug(randomString);returnrandomString;} 上述代码引入io。swagger。v3包,定义了一个path为demo的接口,使用swagger注解对controller类中的map方法进行修饰。暴露生成接口文档方式十分简单,在B站自研的JAVAweb框架PleidaesKraten下开发,应用启动后直接调用apidocs的接口就可以轻易拿到。这种利用swagger注解提供的来声明和操作输出,为JAVA应用实时生成接口文档是JAVA同学熟知的一种方式,对平台来说,要做的只是调用接口拿到JAVA应用的文档即可。 文档收集 我们的终极目标是做到全公司研发同学使用统一的框架,使用统一的API的定义和生成方式,管理平台就可以采集的标准且权威的API元信息,即实现公司API标准化。但这个目标不是一蹴而就的,在研发开发流程尚不标准时,我们通过多种渠道尽可能得获取到应用的接口信息,保证平台接口数据的完备性。 a。CI时生成(最佳实践) 对于采用gRPC协议的接口来说,B站采用的是单仓库管理模型管理协议文件:将协议原始文件Proto集中放到一个仓库中,根据内部服务治理的标准将文件划分至独立的命名空间进行细粒度管理,对外提供根据Proto生成的目标语言仓库,例如go语言protogengo仓库,JAVA语言protogenjava仓库,依赖某服务时直接import该仓库即可。 接口管理平台作为Proto以及stub的管理员,肩负着管理内部统一Proto仓库的责任。但不论是协议文件还是存根代码,只是接口的定义,包括版本、命令定义、资源定义和错误码定义等等,不适宜直接作为接口文档展示给调用方。我们在推动内部API标准化的过程中,对定义Proto文件中接口的参数、默认行为、属性、必要注释等行为给出统一的规范, 在通过在CIPipeline中安装protocgenbilibiliopenapi插件,该插件负责解析代码中的Proto文件,并对应生成一份标准OpenAPI文档。当用户请求合并代码到主分支时,自动触发流水线中接口平台的埋入的任务:扫描代码中的Proto,提取出接口信息,生成对应文档后导入接口管理平台,完成对该应用接口文档的自动刷新。用户只是完成了一次基本的Proto的书写,就不再需要考虑后续其他的协作方面的事宜,接口文档,测试,桩代码,一切交给管理平台进行打理。 b。在线服务采集 对于Java语言应用而言,应用部署后通过注册中心暴露服务地址,接口管理平台到指定环境中调用该地址下apidocs接口获取到应用的接口文档,并与历史接口版本进行比对与更新,实现对Java应用接口文档的自动更新,整个过程对于开发者来说没有额外维护文档的负担,也不再需要关心自己的接口数据如何去暴露和分享给调用方。 在API标准化尚未推广之前,公司内的go服务可能使用的是Kratos早期定义接口的方式,这部分应用通过metadata接口对外暴露path信息。平台通过该接口采集到所有的接口路径后,由用户对接口的文档进行手动补全,等应用实现API标准化后再逐步由半自动进入全自动收集的模式。 03版本管理 API是用户与应用之间的约定,包括URI模式,有效负载结构,字段和参数名称,预期行为以及其他内容。在应用迭代的过程中,不可避免需要添加新的资源、修改资源或调整接口参数,随之会带来的接口变更管理的问题。例如,某个应用新加的feature改动了接口,但此时改动没经过测试,相当于只是草稿版本。开发希望能将草稿版本的接口分享给联调的人员,又不想影响正式版本,这在过去其实是一件比较棘手的事情。接口管理平台要做的就是通过版本管理区分好接口不同状态、不同来源、不同时期的信息。 3。1接口版本控制 接口参数的每一点变更一定都源于代码的变动,代码的提交才可能会导致接口版本的升级。服务本身没有变更,开发者代码没有产生过提交,是不会导致接口凭空变化的。基于此点共识,我们将应用代码的提交(CommitID)与管理平台上的接口版本(APIVersion)关联。 接口管理平台对接口的正式版本及测试版本进行区分。测试版本的来源是测试环境中的应用,与dev代码分支的某次commit记录相关联;正式版本的来源是生产环境中的应用,与主分支代码的某个tag相关联。 研发每次提交代码,不管是用于测试发布还是正式发布,接口管理平台都会为接口生成相应新版本,对比新版本与历史版本的差异,这在多人协作开发的项目中非常受用。研发同学将某一次实验性版本的应用部署到测试环境后,就可以在接口平台上直接对刚刚提交的接口进行初步验证,或者由自动化测试又或是QA进行系统的测试;而正式版本经过完整的流水线测试、全链路灰度验证、部署在生产环境后,可直接被分享给调用方。 3。2应用版本管理 对于接口的调用方来说,大多只会关心接口功能及使用参数。比如说调用方需要接入某个应用时,他想知道应用当前V2版本使用哪些接口可以满足他的需求,而不会关心这些接口有哪些版本。或者说,调用方接入的是历史版本V1,暂时还不想升级到V2,他想知道V1版本使用的接口是哪些参数。这种场景下,直接将V1版本的接口文档发给调用方即可。 应用是接口的集合,应用版本是接口版本的集合。有了接口的版本控制之后,再进行应用的版本管理就变得很容易了。接口管理平台可以为一组接口版本创建一个快照,这个快照就是应用版本。当应用每次正式上线后,我们可以通为应用创建一个该版本的接口快照,通过这样的管理方式,我们可以观察每个接口在各个应用版本下的变更情况,并追踪接口在应用中的生命周期变化。 04接口协作、分享、调试 API管理平台不仅是对接口元信息进行管理,打通数据、提升研发效率以及发掘元数据本身的价值同样是API管理平台的使命。 联动接口周边服务 例如我们将API管理平台与APIGateway打通,对于需要集成服务网关功能的接口,只需要在API管理平台就上可以方便得跳转到对应地方进行配置,其他与接口有关的配置同样如此,用户可以将接口管理平台作为入口,跳转至其他基础平台,提高用户效率的同时更好得与其他平台进行配合。 文档导出、分享 我们对这些接口信息以不同的格式进行展示,支持导出标准的OpenAPI格式的json文件。对于那些习惯使用第三方工具查看接口数据的研发同学来说,可通过工具导入OpenAPI文件或直接订阅平台,在本地客户端实时查看自己关注的接口。 接口调试、运行 接口调试可以简单分为两种:对于HTTP接口,类似Apifox、Postman,拿到接口数据,对接口进行的简单的临时调试对于GRPC接口,类似Bloomrpc,导入Proto文件及其依赖后就可以对服务中的方法进行调试。 API管理平台对这种两种接口的请求方式的进行了统一,用户不需要关心自己的接口是哪种协议,就可以直接点击调试。平台管理本身管理着Proto仓库,拥有全部内部协作的Proto元信息,即使需要客户端Token的RPC也可轻松发起,帮助用户进行初步的接口调试。并且在平台调试时也无需像普通调试工具一样指定域名、IP后才可调试。平台侧打通注册中心,获取服务的信息,自动为用户键入目标地址。用户对于调试这件事仅仅需关注两点:接口及返回结果,其他均由接口管理平台包办。 05接口Mock 5。1为什么需要对测试对象的依赖进行 Mock? Mock的本质是在调试期间构造出一些虚拟的返回对象。一个常见的场景:前后端分离,前端开发某个页面,需要后端先完成API的开发工作,两者进度不一致,出现前端等待后端的情况。如果使用Mock就可以减小这种影响,通过MockAPI事先编写好API的数据生成规则,请求接口平台动态生成API的返回数据。前端开发可以通过访问MockAPI来获得页面所需要的数据,继续开展工作。 Mock的好处:提高测试覆盖率 通过Mock构造各种正常和异常的返回结果,更充分地测试目标对象避免真实依赖的对测试产生影响提高测试效率 依赖的真实行为可能延时高,资源消耗大,而模拟是一种非常快的行为,能加快整个测试流程。 5。2服务级Mock架构设计 Mock粒度由细到粗分为方法级、类级别、接口级、服务级。大多APItest工具由于无法覆盖全部接口,只做到接口级别Mock,但对于拥有全部接口元信息的接口管理平台来说,做到服务级别的Mock是顺水推舟的事情。 狭隘的理解服务端Mock是将服务的所有的接口无差别地全部Mock,相当于是接口级别的极端做法。例如,某次在测试环境中进行服务联调时,对于某些尚未开发完成的接口或者不能在测试环境中被调用的接口,可采用Mock进行过渡;但对于已经上线的接口来说,流量应直接透传至真实服务,待拿到真实的响应数据后再返回给上游。这样不管是对减轻测试用例管理的负担还是提高测试的准确性都有很大的增益。 基于上述思想,我们设计了如下图所示的架构: 对于需要进行被Mock的服务,接口管理平台会在注册中心为该服务的注册出一个染色实例,并与注册中心维持心跳,在测试环境中部署的真实服务则作为兜底用的基准版本。收到流量时,匹配指定染色成功的请求会被注册中心转发至Mock实例,该实例实际是接口管理平台在提供服务。Mock实例判断流量是否命中事先配置好的规则,若命中成功,则直接返回规则中的响应;若未能命中规则或未配置规则,则将流量原封不动地转发给基准版本的实例,由基准返回。我们的做法实际上是通过修改注册中心上服务与服务地址的映射关系,将依赖服务地址改成Mock地址实现Mock注入。 06微服务标准化 我们一直致力于实现公司内部的微服务的标准化,包括接口规范化、接口标准化、数据格式统一化,这些标准是明确、可行且统一的,以保证各个微服务之间的可互操作性。B站内部使用Go语言开发同学多数是在Kratos框架下进行开发的,使用JAVA语言开发的同学使用自研的PleiadesKraten框架,这两种框架都为平台能顺利采集到接口信息提供了极大的便利。我们借助框架的力量,将OpenAPI格式API的标准集成在框架中,编译时期或CI阶段产生接口文档,开发各种配套的Swagger、OpenAPI的工具用来充分提取文档的接口信息。 API元信息的管理是API标准化中的一环,我们希望利用标准的力量来做更多的事情,如监控、自动生成代码。。。探索更多的玩法,成为强有力的生产力工具。 参考文献: 〔1〕API定义Kratos:https:gokratos。devdocscomponentapi 〔2〕自定义方法APIDesignGuide:https:googlecloud。gitbook。ioapidesignguidecustommethods 〔3〕对API进行版本控制的重要性和实现方式EOLINEKRBLOG:http:blog。eolinker。com?p2644 〔4〕干货!用大白话告诉你什么是Mock测试51CTO。COM:https:www。51cto。comarticle647732。html 作者:陈雨润 来源:微信公众号:哔哩哔哩技术 出处:https:mp。weixin。qq。comsWAXKw9hLnQZz6bce18TA