
如何快速實現(xiàn)REST API集成以優(yōu)化業(yè)務(wù)流程
管理現(xiàn)狀
目前,我們的元信息統(tǒng)一管理平臺線上接口數(shù)達到12w+,應(yīng)用(含測試應(yīng)用)總數(shù)近2w。如此龐大規(guī)模的接口由平臺統(tǒng)一收集并管理,可節(jié)約大量人力維護與溝通成本,契合當前“降本增效”的主基調(diào)。
一個服務(wù)的上線過程通常分為這樣幾個階段:
1. 需求評審與分析
PM 輸出需求,PMO 組織需求評審會,業(yè)務(wù)線開發(fā)評估需求的開發(fā)方案與所需工時,從而確定迭代周期。
2. 撰寫與維護 API 文檔
在項目開發(fā)中,Web 項目的前后端分離開發(fā),需要由前后端開發(fā)共同定義接口,編寫接口文檔,之后大家都根據(jù)這個接口文檔進行開發(fā),一直維護到項目結(jié)束。API 文檔在后端技術(shù)方案確定后即可編寫。盡可能早地提供給對接方,有助于對接方提前思考實現(xiàn)方式和規(guī)避隱患。
3. 前后端聯(lián)調(diào)與測試
前端根據(jù) API 文檔初步實現(xiàn)功能,后端在開發(fā)完成后發(fā)布至測試環(huán)境提供給前端聯(lián)調(diào)。
4. 發(fā)布上線
當一切就緒后,服務(wù)被發(fā)布至線上,API 開始對外提供服務(wù),需求上線。
上述整個過程中都離不開對接口文檔的管理,一個優(yōu)秀的接口文檔能夠讓前端與后端開發(fā)人員更好地配合,提高工作效率,方便新加入的成員查看和維護接口、測試人員進行接口測試。
在過去沒有統(tǒng)一管理的模式下,雖說每個團隊每個項目都有編寫 API 文檔的意識,但免不了出現(xiàn)各種管理模式的差異,例如 A 團隊習(xí)慣將文檔編寫在知識庫中,B 團隊習(xí)慣將文檔用 swagger 生成并托管至版本管理系統(tǒng)等等。這種管理模式上的差異會直接導(dǎo)致對接溝通上的低效,無法及時得發(fā)現(xiàn)API的異常,難以管理接口的版本迭代。因此我們始終推薦對 API 進行統(tǒng)一管理,降低對接時的溝通成本,并在接口出現(xiàn)變更時及時同步調(diào)用方,減少信息 gap,通過標準化的中心收集模式敏銳地捕捉到每一次接口調(diào)整。
在整個 API 管理過程中,首先需要保證接口元信息完備性和準確性,管理平臺需要充分收集接口信息。B 站的接口元信息的收集之路:手動維護 -> 自動生成。
過去,我們在內(nèi)網(wǎng)私有化部署過一套 YAPI,通過部門、業(yè)務(wù)域、應(yīng)用的三級劃分的粒度管理著各個服務(wù)的接口。研發(fā)通過在 YAPI 的可視化界面上手動錄入應(yīng)用的詳細接口信息。接口發(fā)布后,前后端的研發(fā)根據(jù)文檔著手進行編碼,測試同學(xué)則根據(jù)文檔上的接口逐個進行測試,負責人對該文檔進行審批。總之,項目干系方始終都會圍繞著這份文檔來推進項目。
手動維護的缺點:
市面上有很多的五花八門的 API 信息管理平臺,如知名的 Eolink、YAPI、Apifox、Postman,但無論部署哪個平臺都無法解決一個非常核心的問題:數(shù)據(jù)的來源始終是“人”,需要人工去操作與更新。?尤其在項目的開發(fā)階段,接口文檔的改動頻次實際上是很高的,需要開發(fā)同學(xué)多次到平臺上調(diào)整接口文檔,保證接口數(shù)據(jù)始終正確。
手工模式下,開發(fā)同學(xué)每次完成接口的增改都需要及時到平臺上同步最新的改動并通知具體的訂閱方。若某次變更未被及時同步,造成調(diào)用方與被調(diào)方之間信息不對齊,很容易造成故障。
接口管理平臺的作用是自動采集應(yīng)用 API 并生成一份詳細且準確的接口文檔,使開發(fā)將精力全部集中在 API 本身的設(shè)計上,無需額外關(guān)注接口文檔的撰寫與維護,從而解放研發(fā)同學(xué)的雙手,提高開發(fā)效率。
為此,我們設(shè)計了如下架構(gòu),研發(fā)同學(xué)遵循統(tǒng)一的 API 標準定義接口,走完正常應(yīng)用打包上線流程,接口采集自動完成。
代碼中定義接口
對于習(xí)慣使用 Golang 進行開發(fā)的同學(xué):
// Demo service responds to incoming requests.
service DemoService {
option (google.api.default_host) = "api.example.com";
// DemoBody method receives a simple message and returns it.
rpc DemoBody(SimpleMessage) returns (SimpleMessage) {
option (google.api.http) = {
post: "/poc/probe/demo_body"
body: "*"
};
}
}
// 請求、回復(fù)消息
message SimpleMessage {
int32 id = 1 [(google.api.field_behavior) = REQUIRED];
Embedded embedded = 2;
}
message Embedded {
int64 int64_val = 1 [(gogoproto.moretags) = 'default:"1"'];
string string_val = 2;
// 一個字符串列表
repeated string repeated_string_val = 3;
// 一個字符串Map
map<string, string> map_string_val = 4;
}
上述 Proto 片段中定義了一個名稱為 DemoService 的 RPC 服務(wù),該服務(wù)包含一個簡單的 RPC方法 DemoBody,并且引入 Google 官方提供的 annotations.proto 對該 gRPC API 增加 HTTP Post 方法的拓展定義。這種使用 Protobuf IDL 定義對應(yīng)的 REST API 和 gRPC API的方式是Google API 指南中所推薦的最佳實踐,也是B站在 Kratos V2框架中定義 API 的方式。對于框架是如何注冊 HTTP 與 gRPC 服務(wù)感興趣的同學(xué)歡迎體驗 Kratos 框架,這里先不詳細展開。DemoService 中除了對 HTTP 方法的定義,還包括服務(wù)概要,默認域名等標記。在 Message 中除了字段類型的定義,某些字段還帶有屬性行為的標記。我們支持用戶使用 gogoproto 以及 google.api.field_behavior 中定義的消息對字段進行一些特殊標記,如定義字段默認值,是否必填,示例用法等參數(shù)相關(guān)的屬性。
info:
title: DemoService API
description: Demo service responds to incoming requests.
paths:
/bilibili.api.probe.v1.DemoService/DemoBody:
...(同/poc/probe/demo_body)
/poc/probe/demo_body:
post:
tags:
- DemoService
description: DemoBody method receives a simple message and returns it.
operationId: DemoService_DemoBody
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/bilibili.api.probe.v1.SimpleMessage'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/bilibili.api.probe.v1.SimpleMessage'
components:
schemas:
bilibili.api.probe.v1.Embedded:
type: object
properties:
stringVal:
type: string
default: hello
repeatedStringVal:
type: array
items:
type: string
description: 一個字符串列表
mapStringVal:
type: object
additionalProperties:
type: string
description: 一個字符串Map
bilibili.api.probe.v1.SimpleMessage:
required:
- id
type: object
properties:
id:
type: integer
format: int32
embedded:
$ref: '#/components/schemas/bilibili.api.probe.v1.Embedded'
description: 請求、回復(fù)消息
tags:
- name: DemoService
最終通過工具生成上述對應(yīng) OpenAPI 文檔,為 Proto 中對 HTTP 方法的定義提供標準 OpenAPI 格式的接口信息,將 gRPC Method 視為 POST 方法,生成一條類似的接口信息。
對于習(xí)慣使用JAVA進行開發(fā)的同學(xué)而言,同樣地:
@GetMapping("/demo")
@Operation(summary = "用戶接口 - debug",description = "示例")
@Parameter(name = "count", required = false)
public String debug(@RequestParam(defaultValue = "128", required = false) int count) {
String randomString = RandomStringUtils.randomAlphabetic(count);
LOGGER.debug(randomString);
return randomString;
}
上述代碼引入io.swagger.v3包,定義了一個path為 ‘/demo’ 的接口,使用 swagger 注解對 controller 類中的map方法進行修飾。暴露生成接口文檔方式十分簡單,在 B 站自研的JAVA web框架 Pleidaes/ Kraten下開發(fā),應(yīng)用啟動后直接調(diào)用 /api-docs 的接口就可以輕易拿到。這種利用 swagger 注解提供的來聲明和操作輸出,為 JAVA 應(yīng)用實時生成接口文檔是JAVA同學(xué)熟知的一種方式,對平臺來說,要做的只是調(diào)用接口拿到 JAVA 應(yīng)用的文檔即可。
文檔收集
我們的終極目標是做到全公司研發(fā)同學(xué)使用統(tǒng)一的框架,使用統(tǒng)一的 API 的定義和生成方式,管理平臺就可以采集的標準且權(quán)威的API元信息,即實現(xiàn)公司API標準化。但這個目標不是一蹴而就的,在研發(fā)開發(fā)流程尚不”標準”時,我們通過多種渠道盡可能得獲取到應(yīng)用的接口信息,保證平臺接口數(shù)據(jù)的完備性。
a. CI 時生成(最佳實踐)
對于采用 gRPC 協(xié)議的接口來說,B 站采用的是單倉庫管理模型管理協(xié)議文件:將協(xié)議原始文件 Proto 集中放到一個倉庫中,根據(jù)內(nèi)部服務(wù)治理的標準將文件劃分至獨立的命名空間進行細粒度管理,對外提供根據(jù) Proto生成的目標語言倉庫,例如 go 語言 proto-gen-go 倉庫, JAVA語言 proto-gen-java 倉庫,依賴某服務(wù)時直接 import 該倉庫即可。
接口管理平臺作為 Proto 以及 stub 的管理員,肩負著管理內(nèi)部統(tǒng)一 Proto倉庫的責任。但不論是協(xié)議文件還是存根代碼,只是接口的定義,包括版本、命令定義、資源定義和錯誤碼定義等等,不適宜直接作為接口文檔展示給調(diào)用方。我們在推動內(nèi)部 API 標準化的過程中,對定義 Proto 文件中接口的參數(shù)、默認行為、屬性、必要注釋等行為給出統(tǒng)一的規(guī)范,在通過在CI Pipeline 中安裝 protoc-gen-bilibili-openapi 插件,該插件負責解析代碼中的 Proto 文件,并對應(yīng)生成一份標準 OpenAPI 文檔。當用戶請求合并代碼到主分支時,自動觸發(fā)流水線中接口平臺的埋入的任務(wù):掃描代碼中的 Proto,提取出接口信息,生成對應(yīng)文檔后導(dǎo)入接口管理平臺,完成對該應(yīng)用接口文檔的自動刷新。用戶只是完成了一次基本的 Proto 的書寫,就不再需要考慮后續(xù)其他的協(xié)作方面的事宜,接口文檔,測試,樁代碼,一切交給管理平臺進行打理。
b. 在線服務(wù)采集
對于 Java 語言應(yīng)用而言,應(yīng)用部署后通過注冊中心暴露服務(wù)地址,接口管理平臺到指定環(huán)境中調(diào)用該地址下 /api-docs 接口獲取到應(yīng)用的接口文檔,并與歷史接口版本進行比對與更新,實現(xiàn)對 Java 應(yīng)用接口文檔的自動更新,整個過程對于開發(fā)者來說沒有額外維護文檔的負擔,也不再需要關(guān)心自己的接口數(shù)據(jù)如何去暴露和分享給調(diào)用方。
在API標準化尚未推廣之前,公司內(nèi)的 go 服務(wù)可能使用的是 Kratos 早期定義接口的方式,這部分應(yīng)用通過 /metadata 接口對外暴露 path 信息。平臺通過該接口采集到所有的接口路徑后,由用戶對接口的文檔進行手動補全,等應(yīng)用實現(xiàn)API標準化后再逐步由半自動進入全自動收集的模式。
API 是用戶與應(yīng)用之間的約定,包括 URI 模式,有效負載結(jié)構(gòu),字段和參數(shù)名稱,預(yù)期行為以及其他內(nèi)容。在應(yīng)用迭代的過程中,不可避免需要添加新的資源、修改資源或調(diào)整接口參數(shù),隨之會帶來的接口變更管理的問題。例如,某個應(yīng)用新加的 feature 改動了接口,但此時改動沒經(jīng)過測試,相當于只是草稿版本。開發(fā)希望能將草稿版本的接口分享給聯(lián)調(diào)的人員,又不想影響正式版本,這在過去其實是一件比較棘手的事情。接口管理平臺要做的就是通過版本管理區(qū)分好接口不同狀態(tài)、不同來源、不同時期的信息。
接口參數(shù)的每一點變更一定都源于代碼的變動,代碼的提交才可能會導(dǎo)致接口版本的升級。服務(wù)本身沒有變更,開發(fā)者代碼沒有產(chǎn)生過提交,是不會導(dǎo)致接口憑空變化的。基于此點共識,我們將應(yīng)用代碼的提交 (Commit ID) 與管理平臺上的接口版本 (API Version)?關(guān)聯(lián)。
接口管理平臺對接口的正式版本及測試版本進行區(qū)分。測試版本的來源是測試環(huán)境中的應(yīng)用,與 dev 代碼分支的某次 commit 記錄相關(guān)聯(lián);正式版本的來源是生產(chǎn)環(huán)境中的應(yīng)用,與主分支代碼的某個 tag 相關(guān)聯(lián)。
研發(fā)每次提交代碼,不管是用于測試發(fā)布還是正式發(fā)布,接口管理平臺都會為接口生成相應(yīng)新版本,對比新版本與歷史版本的差異,這在多人協(xié)作開發(fā)的項目中非常受用。研發(fā)同學(xué)將某一次實驗性版本的應(yīng)用部署到測試環(huán)境后,就可以在接口平臺上直接對剛剛提交的接口進行初步驗證,或者由自動化測試又或是 QA 進行系統(tǒng)的測試;而正式版本經(jīng)過完整的流水線測試、全鏈路灰度驗證、部署在生產(chǎn)環(huán)境后,可直接被分享給調(diào)用方。
對于接口的調(diào)用方來說,大多只會關(guān)心接口功能及使用參數(shù)。比如說調(diào)用方需要接入某個應(yīng)用時,他想知道應(yīng)用當前 V2 版本使用哪些接口可以滿足他的需求,而不會關(guān)心這些接口有哪些版本。或者說,調(diào)用方接入的是歷史版本 V1,暫時還不想升級到 V2,他想知道 V1 版本使用的接口是哪些參數(shù)。這種場景下,直接將V1版本的接口文檔發(fā)給調(diào)用方即可。
應(yīng)用是接口的集合,應(yīng)用版本是接口版本的集合。有了接口的版本控制之后,再進行應(yīng)用的版本管理就變得很容易了。接口管理平臺可以為一組接口版本創(chuàng)建一個快照,這個快照就是應(yīng)用版本。當應(yīng)用每次正式上線后,我們可以通為應(yīng)用創(chuàng)建一個該版本的接口快照,通過這樣的管理方式,我們可以觀察每個接口在各個應(yīng)用版本下的變更情況,并追蹤接口在應(yīng)用中的生命周期變化。
API 管理平臺不僅是對接口元信息進行管理,打通數(shù)據(jù)、提升研發(fā)效率以及發(fā)掘元數(shù)據(jù)本身的價值同樣是 API 管理平臺的使命。
聯(lián)動接口周邊服務(wù)
例如我們將 API 管理平臺與 API Gateway 打通,對于需要集成服務(wù)網(wǎng)關(guān)功能的接口,只需要在API 管理平臺就上可以方便得跳轉(zhuǎn)到對應(yīng)地方進行配置,其他與接口有關(guān)的配置同樣如此, 用戶可以將接口管理平臺作為入口,跳轉(zhuǎn)至其他基礎(chǔ)平臺,提高用戶效率的同時更好得與其他平臺進行配合。
文檔導(dǎo)出、分享
我們對這些接口信息以不同的格式進行展示,支持導(dǎo)出標準的 OpenAPI 格式的 json 文件。對于那些習(xí)慣使用第三方工具查看接口數(shù)據(jù)的研發(fā)同學(xué)來說,可通過工具導(dǎo)入 OpenAPI 文件或直接訂閱平臺,在本地客戶端實時查看自己關(guān)注的接口。
接口調(diào)試、運行
接口調(diào)試可以簡單分為兩種:
API 管理平臺對這種兩種接口的請求方式的進行了統(tǒng)一,用戶不需要關(guān)心自己的接口是哪種協(xié)議,就可以直接點擊調(diào)試。平臺管理本身管理著 Proto 倉庫,擁有全部內(nèi)部協(xié)作的 Proto 元信息,即使需要客戶端 Token 的 RPC 也可輕松發(fā)起,幫助用戶進行初步的接口調(diào)試。并且在平臺調(diào)試時也無需像普通調(diào)試工具一樣指定域名、IP 后才可調(diào)試。平臺側(cè)打通注冊中心,獲取服務(wù)的信息,自動為用戶鍵入目標地址。用戶對于調(diào)試這件事僅僅需關(guān)注兩點:接口及返回結(jié)果, 其他均由接口管理平臺包辦。
Mock?
Mock 的本質(zhì)是在調(diào)試期間構(gòu)造出一些虛擬的返回對象。一個常見的場景:前后端分離,前端開發(fā)某個頁面,需要后端先完成 API 的開發(fā)工作,兩者進度不一致,出現(xiàn)前端等待后端的情況。如果使用 Mock 就可以減小這種影響,通過 Mock API 事先編寫好 API 的數(shù)據(jù)生成規(guī)則,請求接口平臺動態(tài)生成 API 的返回數(shù)據(jù)。前端開發(fā)可以通過訪問 Mock API 來獲得頁面所需要的數(shù)據(jù),繼續(xù)開展工作。
Mock的好處:
通過 Mock 構(gòu)造各種正常和異常的返回結(jié)果,更充分地測試目標對象
依賴的真實行為可能延時高,資源消耗大,而模擬是一種非常快的行為,能 加快整個測試流程。
Mock 粒度由細到粗分為方法級、類級別、接口級、服務(wù)級。大多 API-test 工具由于無法覆蓋全部接口,只做到接口級別 Mock,但對于擁有全部接口元信息的接口管理平臺來說,做到服務(wù)級別的 Mock 是順水推舟的事情。
狹隘的理解服務(wù)端 Mock 是將服務(wù)的所有的接口無差別地全部 Mock,相當于是接口級別的極端做法。例如,某次在測試環(huán)境中進行服務(wù)聯(lián)調(diào)時,對于某些尚未開發(fā)完成的接口或者不能在測試環(huán)境中被調(diào)用的接口,可采用 Mock 進行過渡;但對于已經(jīng)上線的接口來說,流量應(yīng)直接透傳至真實服務(wù),待拿到真實的響應(yīng)數(shù)據(jù)后再返回給上游。這樣不管是對減輕測試用例管理的負擔還是提高測試的準確性都有很大的增益。
基于上述思想,我們設(shè)計了如下圖所示的架構(gòu):
對于需要進行被 Mock 的服務(wù),接口管理平臺會在注冊中心為該服務(wù)的注冊出一個染色實例,并與注冊中心維持心跳,在測試環(huán)境中部署的真實服務(wù)則作為兜底用的基準版本。收到流量時,匹配指定染色成功的請求會被注冊中心轉(zhuǎn)發(fā)至 Mock 實例,該實例實際是接口管理平臺在提供服務(wù)。Mock 實例判斷流量是否命中事先配置好的規(guī)則,若命中成功,則直接返回 規(guī)則中的響應(yīng); 若未能命中規(guī)則或未配置規(guī)則,則將流量原封不動地轉(zhuǎn)發(fā)給基準版本的實例,由基準返回。我們的做法實際上是通過修改注冊中心上服務(wù)與服務(wù)地址的映射關(guān)系,將依賴服務(wù)地址改成 Mock 地址實現(xiàn) Mock 注入。
我們一直致力于實現(xiàn)公司內(nèi)部的微服務(wù)的標準化,包括接口規(guī)范化、接口標準化、數(shù)據(jù)格式統(tǒng)一化,這些標準是明確、可行且統(tǒng)一的,以保證各個微服務(wù)之間的可互操作性。B 站內(nèi)部使用 Go 語言開發(fā)同學(xué)多數(shù)是在 Kratos 框架下進行開發(fā)的,使用 JAVA 語言開發(fā)的同學(xué)使用自研的 Pleiades/ Kraten 框架,這兩種框架都為平臺能順利采集到接口信息提供了極大的便利。我們借助框架的力量,將 OpenAPI 格式 API 的標準集成在框架中,編譯時期或 CI 階段產(chǎn)生接口文檔,開發(fā)各種配套的 Swagger、OpenAPI 的工具用來充分提取文檔的接口信息。
API 元信息的管理是 API 標準化中的一環(huán),我們希望利用標準的力量來做更多的事情,如監(jiān)控、自動生成代碼…探索更多的玩法,成為強有力的生產(chǎn)力工具。
本文章轉(zhuǎn)載微信公眾號@嗶哩嗶哩技術(shù)