得物API元數(shù)據(jù)中心探索與思考
一、背景
目前市面上針對(duì)API的管理平臺(tái)很多,但由于各種客觀因素,這些平臺(tái)的功能都更多聚焦在API文檔的消費(fèi)側(cè)。而對(duì)于API文檔的生成都非常依賴開(kāi)發(fā)人員的手動(dòng)創(chuàng)建,很難保障文檔的實(shí)時(shí)性和有效性。市面上常見(jiàn)的API管理平臺(tái),由于缺乏有效的管理機(jī)制,完全依賴開(kāi)發(fā)人員的主動(dòng)維護(hù),在文檔體量變大之后就出現(xiàn)了文檔歸屬混亂、文檔重復(fù)上傳、文檔信息更新不及時(shí)等問(wèn)題。
圖片
由于文檔缺乏有效的維護(hù),很大程度上局限了API文檔在消費(fèi)側(cè)的作用。舉個(gè)例子,如果一份API文檔更新不及時(shí),那么前端就很難基于過(guò)時(shí)的文檔進(jìn)行數(shù)據(jù)Mock。如果平臺(tái)大多數(shù)的文檔都存在更新不及時(shí)的問(wèn)題,那其他的平臺(tái)也很難把平臺(tái)的API文檔作為有效信息使用。
二、Mooncake API文檔維護(hù)
為了解決文檔的維護(hù)問(wèn)題,得物技術(shù)部自研了Mooncake平臺(tái),并從文檔組織規(guī)范、文檔生成效率等方面做了大量的嘗試。
API文檔組織規(guī)范
平臺(tái)用戶對(duì)于接口文檔的存儲(chǔ)管理、交付時(shí)間和交付質(zhì)量均有一定的訴求。平臺(tái)通過(guò)規(guī)范的方式統(tǒng)一起來(lái),建立接口文檔項(xiàng)目和目錄組織規(guī)范,降低接口查找難度和用戶使用費(fèi)力度。
規(guī)范應(yīng)用名稱
如果應(yīng)用名稱可以任意創(chuàng)建,從技術(shù)部現(xiàn)有數(shù)據(jù)看來(lái),各域定義的巨大差異將會(huì)導(dǎo)致用戶使用存在一定的理解成本。為統(tǒng)一項(xiàng)目命名規(guī)范,同時(shí)更清晰的展示接口與項(xiàng)目之間的關(guān)系,平臺(tái)計(jì)劃與發(fā)布平臺(tái)&CMDB&網(wǎng)關(guān)等系統(tǒng)保持一致,統(tǒng)一采用CMDB中的應(yīng)用名作為項(xiàng)目名稱,降低文檔查找的難度。
通過(guò)打通CMDB數(shù)據(jù),統(tǒng)一CMDB應(yīng)用名,打通與公司內(nèi)部平臺(tái)的數(shù)據(jù),主要包含:
- 建立與發(fā)布平臺(tái)的關(guān)系,自動(dòng)獲取應(yīng)用染色環(huán)境列表,降低接口調(diào)試難度;
- 建立與Gitlab平臺(tái)的關(guān)系,自動(dòng)獲取應(yīng)用需求迭代數(shù)據(jù),降低文檔與需求綁定的費(fèi)力度;
- 建立與網(wǎng)關(guān)平臺(tái)的關(guān)系,一鍵同步接口網(wǎng)關(guān)自動(dòng)關(guān)聯(lián)路由組等數(shù)據(jù)信息;
- 打通交易網(wǎng)關(guān)、APM的數(shù)據(jù),獲取接口文檔信息,豐富文檔信息密度。
通過(guò)將應(yīng)用名稱規(guī)范化,Mooncake平臺(tái)建立了一個(gè)規(guī)范化的應(yīng)用命名體系,讓用戶可以更方便地查找和使用文檔,并提高了團(tuán)隊(duì)的協(xié)作效率和產(chǎn)品質(zhì)量。
規(guī)范文檔分類
如果以類名或注解作為文檔分類的依據(jù),導(dǎo)致文檔的可維護(hù)性逐漸降低,文檔和業(yè)務(wù)的關(guān)系也逐漸削弱。為了解決這個(gè)問(wèn)題,平臺(tái)通過(guò)規(guī)范文檔的分類,降低文檔的查找和管理難度。
- 在技術(shù)層面上,提供多級(jí)分類能力,規(guī)范化維護(hù)文檔分類,并完成商家、客服、供應(yīng)鏈、交易等規(guī)范性分類的推動(dòng)以及約束文檔的落地;
圖片
- 在規(guī)范層面上,推進(jìn)各個(gè)團(tuán)隊(duì)根據(jù)自己的業(yè)務(wù)場(chǎng)景按照統(tǒng)一的規(guī)范來(lái)分類文檔,從而提高文檔的可維護(hù)性和管理效率。例如,推動(dòng)客服、商家、交易等各個(gè)域落地接口目錄規(guī)范文檔,項(xiàng)目負(fù)責(zé)人或Owner定期檢查分類規(guī)范的執(zhí)行情況,并對(duì)分類不規(guī)范的文檔進(jìn)行整理和優(yōu)化。
通過(guò)技術(shù)和規(guī)范手段相結(jié)合,規(guī)范文檔分類,可以降低文檔的查找難度,提升文檔的可維護(hù)性和管理效率。
API文檔生成
MooncakeUpload Idea插件
得物技術(shù)部研發(fā)的MooncakeUpload Idea插件可以幫助解決API文檔創(chuàng)建和錄入的問(wèn)題。該插件通過(guò)解析Java項(xiàng)目里的注解和注釋,實(shí)現(xiàn)了一鍵生成API文檔的功能,降低了API文檔創(chuàng)建的費(fèi)力度。相較于手動(dòng)創(chuàng)建接口文檔,使用插件上傳API文檔所需的時(shí)間僅為幾秒鐘,而且規(guī)范了接口的分類屬性,使得上傳文檔過(guò)程更加簡(jiǎn)便和快速。在每個(gè)迭代中,使用插件可以節(jié)約將近667小時(shí)的時(shí)間。
實(shí)現(xiàn)原理
基于IntelliJ Platform自身的基礎(chǔ)架構(gòu),依靠PSI(Program Structure Interface)核心特性,通過(guò)分析解析出來(lái)的語(yǔ)法樹(shù)可以獲取準(zhǔn)確的代碼信息,例如獲取文件中包含的類、方法、字段和注釋等信息。
核心實(shí)現(xiàn)
- 配置信息
通過(guò)IntelliJ Platform提供的虛擬文件系統(tǒng)(Virtual File System)功能,讀取插件的信息配置,主要包括Mooncake的項(xiàng)目信息,人員的域賬號(hào)等。從而能夠獲取Mooncake的分類數(shù)據(jù),以及接口的變更人員。
// 解析misc配置文件
File miscFile = new File(editor.getProject().getProjectFile().getPath());
Element miscElement = JDOMUtil.load(miscFile);
// 讀取token
public static String getToken(Element element, PsiFile psiFile) {
try {
String token = getHistoryConfig(element, psiFile, MooncakeConstant.HistoryToken);
if (token.equals("")) {
token = getProjectConfig(element, psiFile, MooncakeConstant.Token);
}
if (token.equals("")) {
Messages.showErrorDialog("請(qǐng)先去idea/misc.xml配置MooncakeUploadApi配置", "獲取配置失?。?);
}
return token;
} catch (Exception e) {
Messages.showErrorDialog("請(qǐng)先去idea/misc.xml配置MooncakeUploadApi配置", "獲取配置失??!");
return "";
}
}
原有的配置功能,會(huì)通過(guò)用戶配置的項(xiàng)目名稱信息和當(dāng)前路徑進(jìn)行二次校驗(yàn),增加了用戶理解的難度,平臺(tái)插件使用的問(wèn)題中,80%的問(wèn)題來(lái)源于配置的繁瑣。因此在2.0版本之后,通過(guò)內(nèi)置數(shù)據(jù)校驗(yàn),降低了項(xiàng)目信息的配置難度,配置信息僅需一個(gè)參數(shù)即可:
<component name="mooncakeUpload">
<option name="token">xxxxxxxx</option>
</component>
- 出入?yún)⑿畔?/li>
依靠PSI核心特性,通過(guò)解析選中文件的語(yǔ)法樹(shù),提取字段信息,組裝API文檔的出入?yún)⒑妥⑨?,主要核心邏輯?/p>
// 獲取偏移量
PsiFile editorFile = e.getDataContext().getData(CommonDataKeys.PSI_FILE);
PsiElement referenceAt = psiFile.findElementAt(editor.getCaretModel().getOffset());
// 獲取選中的類或者方法
PsiClass selectedClass = PsiTreeUtil.getContextOfType(referenceAt, PsiClass.class);
PsiMethod selectedMethod = PsiTreeUtil.getContextOfType(referenceAt, PsiMethod.class);
// 獲取選中類下的所有方法
PsiMethod[] psiMethods = selectedClass.getMethods();
// 獲取類上的注解
String apiValue = PsiAnnotationSearchUtil.getPsiParameterAnnotationParam(selectedClass, SwaggerConstants.API, "tags");
// 獲取參數(shù)所屬類
PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(psiParameter.getType().getCanonicalText(),
GlobalSearchScope.allScope(project));
// 獲取參數(shù)所有字段
PsiField[] fields = psiClass.getAllFields();
// 字段源類型,可以獲取所有信息
PsiType type = field.getType();
// 字段名稱
String name = field.getName();
- 可視化面板
Mooncake平臺(tái)支持API文檔的多級(jí)分類,為了降低接口文檔的分類難度,降低對(duì)代碼的侵入,基于Java的Swing GUI庫(kù),我們提供了可視化操作面板,用戶可以選擇需要上傳的接口和分類信息,以及需求信息。
圖片
- 版本更新
MooncakeUploadApi上傳插件是得物技術(shù)部自主研發(fā)的插件,由于存在公司的業(yè)務(wù)信息,無(wú)法上傳到插件市場(chǎng),只能將插件打包成Jar文件給開(kāi)發(fā)使用。這種情況下,可能會(huì)出現(xiàn)以下問(wèn)題:
- 用戶無(wú)法及時(shí)感知到插件修復(fù)過(guò)的版本,導(dǎo)致升級(jí)新版本時(shí)存在困難。如果出現(xiàn)問(wèn)題,用戶還需要找Mooncake維護(hù)人員定位問(wèn)題,并進(jìn)行手動(dòng)修復(fù)和更新,維護(hù)成本比較高;
- 由于不能上傳插件市場(chǎng),用戶升級(jí)插件需要手動(dòng)找到Mooncake維護(hù)的插件文檔,并下載相應(yīng)的Jar包進(jìn)行更新,費(fèi)力度高。
針對(duì)以上可能存在的問(wèn)題,得物技術(shù)通過(guò)搭建私有倉(cāng)庫(kù)方式,最終實(shí)現(xiàn)了插件更新方案,如圖所示:
圖片
最終實(shí)現(xiàn)了插件啟動(dòng)的自動(dòng)檢查版本更新,并進(jìn)行通知。
圖片
結(jié)果
通過(guò)自研Mooncake Idea上傳插件,實(shí)現(xiàn)Mooncake平臺(tái)以下收益:
- 快速響應(yīng)并定制化需求:通過(guò)打通Gitlab,插件可以根據(jù)代碼分支來(lái)自動(dòng)綁定接口的業(yè)務(wù)需求,以便快速響應(yīng)相關(guān)問(wèn)題;
- 提升開(kāi)發(fā)效率:使用插件可以大幅度降低API文檔創(chuàng)建的成本和負(fù)擔(dān),從而讓開(kāi)發(fā)人員更加專注于代碼的開(kāi)發(fā)和測(cè)試等任務(wù)中;
- 規(guī)范API文檔:插件通過(guò)可視化面板交互方式,規(guī)范了文檔的格式和內(nèi)容,并能夠快速選擇上傳的文檔分類和字段信息,以便更好地管理和使用API文檔信息,從而提高了規(guī)范化程度。
目前研發(fā)部門接通過(guò)插件每個(gè)迭代上傳API文檔的次數(shù)(如圖所示),平均每個(gè)迭代產(chǎn)生數(shù)千次的變更,大大提升了維護(hù)文檔效率,達(dá)到降本提效的目的。
圖片
基于Gitlab MR自動(dòng)解析
背景
圖片
平臺(tái)提供了API Upload插件之后,整個(gè)文檔生產(chǎn)端現(xiàn)狀如下:
- 服務(wù)端在開(kāi)發(fā)階段通過(guò)手動(dòng)/插件上傳API文檔到Mooncake側(cè);
- 提測(cè)節(jié)點(diǎn)時(shí)從網(wǎng)關(guān)同步當(dāng)前迭代新增的API接口,與Mooncake側(cè)接口比對(duì),查看是否存在,不存在則要求開(kāi)發(fā)上傳接口。
通過(guò)網(wǎng)關(guān)來(lái)進(jìn)行新增接口卡點(diǎn),可能存在以下問(wèn)題:
- 網(wǎng)關(guān)側(cè)配置的接口僅為需要走網(wǎng)關(guān)流量的接口,不走網(wǎng)關(guān)的接口,如Dubbo接口、內(nèi)部接口并不能保證接口在Mooncake存在;
- 即使在Mooncake存在的接口,如果在上傳之后又產(chǎn)生了變更,通過(guò)網(wǎng)關(guān)的卡點(diǎn)并不能保證最新的變更也同步到了平臺(tái)。
因此,平臺(tái)在通過(guò)上傳插件降低API生成費(fèi)力度的同時(shí),也需要將現(xiàn)有的研發(fā)流程仍然強(qiáng)依賴使用者的習(xí)慣、API文檔的質(zhì)量不穩(wěn)定的風(fēng)險(xiǎn)考慮進(jìn)去。
針對(duì)這個(gè)問(wèn)題,平臺(tái)在Gitlab的流水線中,新增了一個(gè)自動(dòng)解析代碼的節(jié)點(diǎn)。對(duì)于每個(gè)向release分支合并的MR,將其中和接口定義相關(guān)的部分進(jìn)行解析并自動(dòng)在平臺(tái)生成/更新對(duì)應(yīng)的API文檔,從而保證所有接口在發(fā)布前一定將最終的接口定義同步到Mooncake平臺(tái)。
實(shí)現(xiàn)
圖片
- 配置Gitlab流水線任務(wù)
自動(dòng)解析需要拉取項(xiàng)目的全量代碼和依賴包代碼,因此占用的內(nèi)存空間較大;
自動(dòng)解析項(xiàng)目耗時(shí)較長(zhǎng),例如公司內(nèi)部某個(gè)項(xiàng)目,7k+的文件需要耗時(shí)3.5min,自動(dòng)解析feature分支消耗太多資源。
因此我們最終針對(duì)HTTP接口只做每個(gè)迭代的兜底,通過(guò)解析Release分支,保障每個(gè)迭代結(jié)束時(shí),文檔都是完整的和最新的。
- 獲取二方包源碼
由于二方包在編譯為Jar之后,代碼的注釋會(huì)丟掉,而API文檔需要解析字段的注釋和注解來(lái)描述字段的含義,自動(dòng)解析要保證接口的完整性,需要補(bǔ)全二方包的注釋。因此我們掃描了Pom文件的依賴包,并將公司的二方包全部下載到當(dāng)前項(xiàng)目目錄里面,并反編譯解析原始數(shù)據(jù)。
獲取公司內(nèi)所有的二方包源代碼數(shù)據(jù):
JSONArray allModuleDepsTreeData = new JSONArray();
for (String fileDepTree : arrayListScannerMgr_Dep_Tree_POM) {
JSONObject treeDependeces = dependcesParse(fileDepTree);
allModuleDepsTreeData.add(treeDependeces);
}
// 過(guò)濾公司二方包
String group = nodeChild.getGroupId();
if (group.contains("xxx")
|| group.contains("xxxx")
|| group.contains("xxx")
|| group.contains("xxxx")) {
return true;
}
// 下載所有二方包
File tempFile = new File(jarScanPath.trim());
String fName = tempFile.getName();
fName = FilenameUtils.removeExtension(fName);
fName = fName.replaceAll("-", "_");
- 解析項(xiàng)目所有的代碼
通過(guò)調(diào)研,Qdox工具包具備體積小,解析效率快,使用文檔簡(jiǎn)單的特性,因此采用使用Qdox將項(xiàng)目中的所有代碼解析為Java語(yǔ)法樹(shù),并實(shí)現(xiàn)API文檔的信息提取。
核心邏輯如下代碼所有,解析所有class,并基于Swagger注解和RestController注解提取所有的Http接口。
// 初始化builder
JavaProjectBuilder builder = new JavaProjectBuilder();
builder.setEncoding(StandardCharsets.UTF_8.name());
// 讀取所有class信息
builder.addSourceTree(new File(sourceDir));
Collection<JavaClass> classes = builder.getClasses();
// 過(guò)濾所有http接口
// 獲取所有http api class
Collection<JavaClass> httpClasses = new ArrayList<>();
for (JavaClass javaClass : classes) {
if (CommonHelper.isHttpClass(javaClass)) {
httpClasses.add(javaClass);
}
}
而接口文檔信息的解析與Idea插件解析思路基本一樣,最終將所有的接口方法解析為JSON格式的API文檔,如圖所示:
圖片
- 將解析的接口數(shù)據(jù)與Mooncake平臺(tái)數(shù)據(jù)對(duì)比
- 接口在Mooncake不存在的,直接上傳到Mooncake平臺(tái),保證API文檔的完整性;
- 接口存在的,比對(duì)接口文檔核心數(shù)據(jù),包含出入?yún)⒑吐窂降?,不一致則更新接口文檔,保證API文檔的一致性。
三、Mooncake API元數(shù)據(jù)中心
Mooncake平臺(tái)通過(guò)不斷完善從生產(chǎn)到消費(fèi)鏈路的信息,延長(zhǎng)API文檔的生命周期,完成API文檔元數(shù)據(jù)中心的閉環(huán)。在平臺(tái)的探索過(guò)程中,逐漸沉淀了豐富API文檔信息,解決了API的利用率低,API信息密度低的問(wèn)題。目前,API信息主要包含以下:
- API描述信息:如 Swagger、OpenAPI 等格式的 API 描述文件,包括 API 名稱、版本、路徑、參數(shù)、響應(yīng)等;
- 接口規(guī)范:定義API請(qǐng)求和響應(yīng)協(xié)議,規(guī)范接口分類;
- 穩(wěn)定性:API的版本管理、生命周期、周期變更率等數(shù)據(jù);
- 文檔和示例:API的使用文檔、示例代碼、調(diào)用等;
- 開(kāi)發(fā)平臺(tái):提供API開(kāi)發(fā)者所需的工具和SDK,例如IDEA插件、Go cli等;
- 研發(fā)流程規(guī)范:提供接口版本周期穩(wěn)定性數(shù)據(jù),例如:調(diào)試是否成功、自動(dòng)化測(cè)試用例等。
通過(guò)打造得物API元數(shù)據(jù)中心,更有效的提高API開(kāi)發(fā)和管理效率,使得API能夠更加透明化、可靠化、易于使用?;谪S富的API文檔信息,平臺(tái)圍繞調(diào)試、Mock、數(shù)據(jù)開(kāi)放等API消費(fèi)側(cè)的功能也做了大量的探索嘗試。
調(diào)試
由于平臺(tái)沉淀了精確的接口字段定義,因此基于這些定義對(duì)接口進(jìn)行調(diào)試自測(cè)就非常方便。在提供基礎(chǔ)調(diào)試功能的同時(shí),平臺(tái)也通過(guò)以下手段對(duì)調(diào)試的體驗(yàn)進(jìn)行了優(yōu)化:
- 基于文檔信息,自動(dòng)填充入?yún)⒆侄涡畔?;同時(shí)基于文檔信息進(jìn)行簡(jiǎn)單的類型校驗(yàn);
- 通過(guò)同步CMDB應(yīng)用名稱,自動(dòng)獲取染色環(huán)境名稱,支持調(diào)試自動(dòng)填充染色環(huán)境參數(shù);
- 打通內(nèi)部核心平臺(tái),優(yōu)化接口簽名和鑒權(quán)問(wèn)題,降低接口調(diào)試難度。
對(duì)于部分仍然習(xí)慣于使用postman進(jìn)行調(diào)試的用戶,平臺(tái)也支持將postman調(diào)試記錄進(jìn)行一鍵同步。
之所以這么執(zhí)著的推動(dòng)大家到Mooncake來(lái)進(jìn)行調(diào)試,主要是期望將調(diào)試記錄作為研發(fā)完成自測(cè)的一種“憑證”,并將其同得物現(xiàn)有的研發(fā)協(xié)同面板系統(tǒng)進(jìn)行結(jié)合,把“自測(cè)憑證”作為生成前后端聯(lián)調(diào)單的前置條件,通過(guò)保障聯(lián)調(diào)過(guò)程中的接口質(zhì)量從而提升聯(lián)調(diào)效率。
整個(gè)聯(lián)調(diào)過(guò)程大致如下:
圖片
Mock
- 由于項(xiàng)目接口的完整性和及時(shí)性,前端可以基于平臺(tái)的Mock功能,在開(kāi)發(fā)階段,前端可以Mock需求下的所有接口,充分完成功能的自測(cè),前置聯(lián)調(diào)流程,降低因?yàn)楹蠖搜舆t提供接口的帶來(lái)延期的風(fēng)險(xiǎn)。
- 基于入?yún)⒆侄蔚臏?zhǔn)確性和完整性,在Mock數(shù)據(jù)過(guò)程中,平臺(tái)可以校驗(yàn)入?yún)⒌男畔⒌臏?zhǔn)確性,包括是否完整,數(shù)據(jù)入?yún)㈩愋褪欠駵?zhǔn)確等,提前發(fā)現(xiàn)問(wèn)題,提升前端交付質(zhì)量。
- 通過(guò)API文檔的調(diào)試功能,平臺(tái)沉淀了基于接口文檔的真實(shí)數(shù)據(jù),因此,平臺(tái)可以自動(dòng)為前端提供更加精準(zhǔn)的具備業(yè)務(wù)屬性的數(shù)據(jù)Mock,以及不同的異常狀態(tài)碼數(shù)據(jù)場(chǎng)景,更加真實(shí)對(duì)頁(yè)面場(chǎng)景進(jìn)行還原。
圖片
API元數(shù)據(jù)平臺(tái)
圖片
將公司所有的API文檔收斂到Mooncake平臺(tái),通過(guò)保障接口的完整性和及時(shí)性,可以保障所有消費(fèi)平臺(tái)都能拿到接口的詳細(xì)信息,同時(shí)通過(guò)與其他平臺(tái)協(xié)作,將接口的不同維度信息收斂到平臺(tái)信息,例如接口的等級(jí)、讀寫屬性等,豐富文檔的信息密度。
通過(guò)提供OpenApi,為公司內(nèi)部平臺(tái)提供API信息,例如:
- 提供API文檔包括接口名稱,字段語(yǔ)義,出入?yún)⑼暾越oAPM監(jiān)控平臺(tái),提升Trace鏈路的可讀性;
- 提供完整的出入?yún)⑿畔ⅲc流量平臺(tái)的接口數(shù)據(jù)比對(duì),及時(shí)發(fā)現(xiàn)接口版本問(wèn)題;
- 提供接口的變更率、是否調(diào)試成功等數(shù)據(jù)給提測(cè)平臺(tái),作為質(zhì)量管控?cái)?shù)據(jù)面板的一部分;
- 提供接口相關(guān)聯(lián)的域名信息給網(wǎng)關(guān)平臺(tái),作為網(wǎng)關(guān)平臺(tái)監(jiān)控接口流量信息的依據(jù);
- 將接口信息提供給接口自動(dòng)化平臺(tái),可以提升測(cè)試編寫接口測(cè)試用例效率。
四、展望
目前得物面對(duì)日益增長(zhǎng)的業(yè)務(wù),尤其是涉及分布式架構(gòu)、微服務(wù)等技術(shù)和架構(gòu)時(shí),通過(guò)API元數(shù)據(jù)中心集中化管理API接口文檔,在協(xié)同管理各團(tuán)隊(duì)、保證接口的一致性和完整性、快速演進(jìn)變更、降低溝通成本等方面有著至關(guān)重要的作用。后續(xù),平臺(tái)依然會(huì)圍繞已經(jīng)沉淀的API信息,在接口自動(dòng)化測(cè)試、API文檔管理、接口健康度監(jiān)控等上下游領(lǐng)域進(jìn)行持續(xù)的探索。