新聞中心
?在以云計(jì)算為主角的開(kāi)發(fā)者視界中,OpenAPI 是絕對(duì)的主角。要發(fā)短信,用 OpenAPI;要管理資源,用 OpenAPI;要管理權(quán)限,用 OpenAPI。如果一個(gè) OpenAPI 解決不了你的問(wèn)題,那就再來(lái)一個(gè)。在今天,開(kāi)放平臺(tái)及 OpenAPI 隨處可見(jiàn),它是系統(tǒng)與系統(tǒng)之間集成的重要橋梁。但 OpenAPI 用起來(lái)是否真的舒服,這要打一個(gè)大大的問(wèn)號(hào)。本文將介紹 OpenAPI 領(lǐng)域下的難題和一些解決方案。

背景
阿里云有位工程師叫樸靈,熱愛(ài)開(kāi)源,是活躍在 Github 上的國(guó)內(nèi)技術(shù)大牛之一。在阿里工作 6 年之際,樸靈產(chǎn)生了離職的想法,打算去一家創(chuàng)業(yè)公司再戰(zhàn)高峰。走之前,樸靈做了一些研究工作,他發(fā)現(xiàn)阿里云在功能和產(chǎn)品上可以說(shuō)是一流的云計(jì)算廠商,是創(chuàng)業(yè)公司的首選,但由于過(guò)去的業(yè)務(wù)中寫(xiě)過(guò)大量的 Node.js SDK,對(duì)開(kāi)發(fā)者體驗(yàn)有著自己的體感,他覺(jué)得在開(kāi)發(fā)者體驗(yàn)關(guān)懷上,阿里云做得還不夠好。來(lái)自一個(gè)熱血工程師最樸素的想法,自己何不先留下來(lái),去把這件事情做好,于是,樸靈加入了阿里云開(kāi)放平臺(tái)負(fù)責(zé) SDK 業(yè)務(wù),期間,他和團(tuán)隊(duì)研發(fā)了專(zhuān)利 TeaDSL,下面樸靈將分享 TeaDSL 如何解決多語(yǔ)言 SDK 的問(wèn)題。
使用 OpenAPI 的痛苦
在過(guò)去,我們經(jīng)常說(shuō)的 OpenAPI,通常的做法是,開(kāi)發(fā)好服務(wù)端的接口,然后在文檔里簡(jiǎn)單寫(xiě)幾個(gè)參數(shù)描述,就直接丟給客戶(hù)去用。反正我是開(kāi)發(fā)好了,我這里是好的,客戶(hù)能不能用起來(lái)我是不用管的。
?
??
??
圖 1 第一代的 OpenAPI 通常僅由簡(jiǎn)單的文檔及實(shí)際的接口構(gòu)成
然而接下來(lái)的問(wèn)題就來(lái)了。首先,文檔上寫(xiě)得不清不楚的參數(shù),沒(méi)有試過(guò),完全不知道它到底能不能 Work。其次,OpenAPI 總得有一定的權(quán)限認(rèn)證吧,那么總得有一個(gè)簽名啥的,每個(gè)客戶(hù)都要寫(xiě)一遍,關(guān)鍵是總是沒(méi)法寫(xiě)對(duì)。再次,不同的客戶(hù)所使用的編程語(yǔ)言不一樣,得把接口重新包裝才能用。
總算費(fèi)心費(fèi)力調(diào)通了接口,以為可以高枕無(wú)憂(yōu)的時(shí)候,咋接口老是報(bào)錯(cuò),網(wǎng)絡(luò)連不上,返回的數(shù)據(jù)不對(duì),諸如此類(lèi)。再往后,OpenAPI 可能總是要發(fā)生一點(diǎn)變化什么的,總是出現(xiàn)一些數(shù)據(jù)結(jié)構(gòu)發(fā)生變化,不兼容之類(lèi)的問(wèn)題。
一個(gè) OpenAPI 到最后,不光是用戶(hù)使用起來(lái)覺(jué)得很氣,作為維護(hù)者也是很艱難的。當(dāng)公布一個(gè) OpenAPI 后,第一步給出簡(jiǎn)單的文檔后,會(huì)發(fā)現(xiàn)除了要把參數(shù)詳情寫(xiě)得越來(lái)越完善準(zhǔn)確外,還得給出簽名算法,讓不同語(yǔ)言的開(kāi)發(fā)者來(lái)接入。然而給出簽名算法后,會(huì)發(fā)現(xiàn)只有一些開(kāi)發(fā)者能順利完成,大部分的開(kāi)發(fā)者只能眼巴巴地請(qǐng)你幫忙提供一個(gè) SDK。好吧,那就提供一下我最拿手的 Java 語(yǔ)言的簽名,提供一個(gè)核心 SDK 唄。
?
??
??
圖 2 第二代的 OpenAPI 會(huì)有 SDK 的實(shí)現(xiàn),但僅有少許的語(yǔ)言支持
隨著這個(gè) OpenAPI 接口的用戶(hù)越來(lái)越多,一個(gè)客戶(hù)說(shuō)我要用 C++ 來(lái)對(duì)接你,另一個(gè)客戶(hù)說(shuō)我要用 Python 來(lái)對(duì)接你,于是,我一個(gè) Java 程序員,怎么就要寫(xiě)那么多語(yǔ)言的 SDK 呢。沒(méi)有辦法,如果不提供良好的 SDK,客戶(hù)說(shuō),沒(méi)有 IDE 提示呢,我怎么寫(xiě)代碼呢。
總而言之,在 OpenAPI 的應(yīng)用過(guò)程中,一件簡(jiǎn)單的事情,會(huì)變得非常復(fù)雜:
- 需要提供良好的 API 文檔,作為最基本的要求
- 需要提供 SDK,保障開(kāi)發(fā)者的編碼體驗(yàn),封裝細(xì)節(jié),代碼提示等
- 需要提供 Code Sample,更理解接口的使用效果
- 如果有 CLI 就更好了,這樣連 bash 腳本寫(xiě)起來(lái)也更方便
- 如果沒(méi)有 Test Cases 作為日常的持續(xù)集成,接口質(zhì)量可能存在問(wèn)題
上面這些要求,如果加上多種編程語(yǔ)言的條件,就會(huì)演變?yōu)橐患?xì)碎而又繁多的體力活。并且這中間不能有任何的變動(dòng),因?yàn)閮H僅是一點(diǎn)點(diǎn)的 OpenAPI 變動(dòng),就需要連帶整個(gè)下游發(fā)生變化。如果一個(gè)地方?jīng)]有保持一致,那么客戶(hù)問(wèn)題就會(huì)出現(xiàn)。
?
??
??
圖 3 當(dāng)用戶(hù)量變多,OpenAPI 的提供者需要提供完善的工具及更多的編程語(yǔ)言支持
通常為了解決此類(lèi)的問(wèn)題,以及 OpenAPI 的諸如簽名校驗(yàn),限流,生成 SDK、文檔等等,業(yè)界通常會(huì)使用 API 網(wǎng)關(guān)來(lái)承擔(dān)這些橫向的責(zé)任。
然而,作為筆者所在的環(huán)境下,會(huì)發(fā)現(xiàn),我們身邊的網(wǎng)關(guān)有點(diǎn)多。于是不同的網(wǎng)關(guān)有不同的風(fēng)格,不同的簽名算法,不同的序列化格式。于是上述的過(guò)程要根據(jù)不同網(wǎng)關(guān)的數(shù)量,進(jìn)行翻倍:
?
??
??
圖 4 當(dāng)一個(gè)企業(yè)變得龐大時(shí),不同風(fēng)格的 OpenAPI 及網(wǎng)關(guān)都會(huì)出現(xiàn)
當(dāng)我們?cè)诒г故褂貌煌a(chǎn)品的 OpenAPI/SDK 體驗(yàn)不一致,文檔不對(duì),Demo 出錯(cuò)等等問(wèn)題時(shí),真不是因?yàn)樽鲞@些事情太難,而是太多,太瑣碎。一件簡(jiǎn)單的事情,需要做一百次,也就不是簡(jiǎn)單的事情了。
TeaDSL 的解決之道
TeaDSL 是由阿里云開(kāi)放平臺(tái) SDK 團(tuán)隊(duì)主導(dǎo)設(shè)計(jì)的一門(mén)領(lǐng)域特定語(yǔ)言。主要用于解決如下問(wèn)題:
- 通過(guò)一門(mén)中間語(yǔ)言,可以支持不同風(fēng)格的網(wǎng)關(guān)。即使網(wǎng)關(guān)下的 OpenAPI 風(fēng)格各異,也能一致地表達(dá)到。
- 可以通過(guò)翻譯的能力,實(shí)現(xiàn)對(duì)不同編程語(yǔ)言的代碼生成。也就是可以基于統(tǒng)一的中間表達(dá),生成多語(yǔ)言的 SDK。
- 基于中間表達(dá),我們可以將一組 OpenAPI 視為一個(gè) library,因此可以在這個(gè)基礎(chǔ)上實(shí)現(xiàn) OpenAPI 接口的 Code Sample 編寫(xiě)。進(jìn)而實(shí)現(xiàn)多語(yǔ)言的 Code Sample 統(tǒng)一生成。
因此 TeaDSL 的核心能力就是通過(guò)一種中間語(yǔ)法來(lái)描述 OpenAPI,提供類(lèi)似編程語(yǔ)言的能力,來(lái)將 OpenAPI、SDK、Code Sample 等場(chǎng)景及語(yǔ)言有機(jī)地結(jié)合在一起。
在沒(méi)有 TeaDSL 之前,對(duì)于不同的網(wǎng)關(guān),我們要為它制定獨(dú)立的工作流程,即從 OpenAPI 定義到不同語(yǔ)言的 SDK 生成,是獨(dú)特的。換一個(gè)新的網(wǎng)關(guān)風(fēng)格,就要重新實(shí)現(xiàn)這套流程。
?
??
??
圖 5 M 個(gè)網(wǎng)關(guān)都要支持 N 種編程語(yǔ)言,整個(gè)工作量是 M * N 的關(guān)系
而具有 TeaDSL 后,我們則形成一個(gè)中間層。可以將原來(lái)的工作收斂起來(lái),我們僅需要關(guān)注不同的網(wǎng)關(guān)到 TeaDSL 的轉(zhuǎn)換工作,以及 TeaDSL 到各個(gè)編程語(yǔ)言的生成工作。
?
??
??
圖 6 經(jīng)過(guò)中間層的隔離,整個(gè)工作量變?yōu)?M + N 的關(guān)系
也就是說(shuō),TeaDSL 是在做一件 M * N 到 M + N 的工作。當(dāng)網(wǎng)關(guān)越多,支持的編程語(yǔ)言越多,收益則越大。
一旦這個(gè)中間層建立起來(lái),整個(gè) OpenAPI 的應(yīng)用形式都可以基于它來(lái)構(gòu)建。比如,編寫(xiě)一個(gè) OpenAPI 的 Code Sample,Test Case 等。
接下來(lái)簡(jiǎn)單介紹 TeaDSL 是如何實(shí)現(xiàn)支持任意風(fēng)格的網(wǎng)關(guān)和多種編程語(yǔ)言的。
如何支持任意風(fēng)格的網(wǎng)關(guān)
對(duì)于不同的 API 網(wǎng)關(guān),或者不同產(chǎn)品的 OpenAPI 而言,它們之間的風(fēng)格可能都千差萬(wàn)別,因此在很大的程度上,每種風(fēng)格的 OpenAPI 都有它自己的元數(shù)據(jù)定義格式。為了減少網(wǎng)關(guān)、風(fēng)格帶來(lái)的差異化,業(yè)界主要推動(dòng)的方式是盡量采用標(biāo)準(zhǔn)的定義格式。比如 Swagger 就是其中的佼佼者,它依托于 OpenAPI Specification ,以 RESTful 風(fēng)格的 OpenAPI 作為基準(zhǔn),形成了一套業(yè)界標(biāo)準(zhǔn)。
但這個(gè)世界就是這樣不完美,我們現(xiàn)有的大量 OpenAPI 并不是 RESTful 風(fēng)格的。這導(dǎo)致很多的產(chǎn)品現(xiàn)存的 OpenAPI 在文檔、SDK等場(chǎng)景下,無(wú)法使用上 Swagger 這樣強(qiáng)大的生態(tài)工具鏈。
為了解決這些問(wèn)題,我們需要進(jìn)行兩步操作:
- 設(shè)立一套新的標(biāo)準(zhǔn),來(lái)包容不同風(fēng)格的 OpenAPI
- 以這套新的標(biāo)準(zhǔn),來(lái)建設(shè)生態(tài)工具鏈
如果完成這兩個(gè)步驟,那么現(xiàn)實(shí)世界上的每一個(gè) OpenAPI,RESTful 或者非 RESTful 的,不需要做任何遷移,也能具有強(qiáng)大的工具鏈支持。
新標(biāo)準(zhǔn)的設(shè)計(jì)
通過(guò)我們的研究發(fā)現(xiàn),無(wú)論 OpenAPI 的參數(shù)是如何組成的,傳輸是 JSON,還是 XML,乃至自定義協(xié)議,OpenAPI 都是基于 HTTP 協(xié)議棧進(jìn)行提供的。也就是說(shuō),萬(wàn)變不離其宗的是 HTTP 協(xié)議本身。因此我們確立的基本模型是這樣的:
{
protocol: string, // http or https
port: number, // tcp port
host: string, // domain
request: {
method: string, // http method
pathname: string, // path name
query: map[string]string, // query string
headers: map[string]string, // request headers
body: readable // request body
},
response: {
statusCode: number, // http method
statusMessage: string, // path name
headers: map[string]string, // response headers
body: readable // response body
},
}
對(duì)于不同風(fēng)格的 OpenAPI 而言,就像不同風(fēng)格的建筑,它們的建筑材料都幾乎相同,只是施工手法,組合形式不一樣而已。我們看到的 OpenAPI 風(fēng)格差異,實(shí)質(zhì)則是序列化過(guò)程不同而帶來(lái)的不同。我們序列化過(guò)程和數(shù)據(jù)模型分離,將用戶(hù)更直觀的數(shù)據(jù)結(jié)構(gòu)提取出來(lái)。
比如從用戶(hù)角度出發(fā),一個(gè)數(shù)據(jù)模型是更直觀的事物:
model User {
username: string,
age: number
}
在不同的網(wǎng)關(guān)下,它的傳輸形式可能是 JSON,也可能是 XML,但最終都是 readable,也就是可讀的字節(jié)流。
toJSON(user: User): string
toXML(user: User): string
最終的結(jié)果就是:
__request.body = toJSON(user);
__request.body = toXML(user);
更進(jìn)一步的過(guò)程是,我們會(huì)將一個(gè) OpenAPI 的請(qǐng)求/響應(yīng)包裝為一個(gè)類(lèi)似于編程代碼的方法:
api getUser(username: string): User {
__request.method = 'GET';
__request.pathname = `/users/${username}`;
__request.headers = {
host = 'hostname',
};
} returns {
var body = readAsJSON(__response.body);
return body;
}
盡管上面的代碼不能實(shí)際運(yùn)行,但大致也看出來(lái)我們包容不同的網(wǎng)關(guān)、風(fēng)格的辦法如下:
- 以 request / response 也就是 HTTP 協(xié)議作為核心模型
- 通過(guò)引入一些方法,如 toJSON / toXML / readAsJSON 等方法來(lái)分離數(shù)據(jù)結(jié)構(gòu)和序列化過(guò)程
- 將整個(gè)過(guò)程包裝成方法
這些方法在不同的編程語(yǔ)言下具有不同的實(shí)現(xiàn),但我們只要定義好統(tǒng)一的簽名,就能確保一致性:
function toXML(data: $Model): string;
function toJSON(data: $Model): string;
以上就是 TeaDSL 如何實(shí)現(xiàn)支持任意網(wǎng)關(guān)的方案。整個(gè)過(guò)程相對(duì)抽象,網(wǎng)關(guān)間的那些具有差異化的風(fēng)格,統(tǒng)統(tǒng)交給這些方法去實(shí)現(xiàn),留下來(lái)的就只有數(shù)據(jù)結(jié)構(gòu)。
如何支持不同的編程語(yǔ)言
如果只是能通過(guò)一種描述方式來(lái)描述不同的 OpenAPI 調(diào)用過(guò)程,只是完成了一半的工作。另一半的工作是如何將這種描述語(yǔ)言落地到不同的編程語(yǔ)言下。在過(guò)去,我們支持不同的編程語(yǔ)言,主要是基于模版的形式來(lái)生成不同語(yǔ)言的實(shí)際代碼。但這對(duì)我們來(lái)說(shuō)仍然還有一些不足之處:
- 模版的生成方式相對(duì)生硬,實(shí)現(xiàn)起來(lái)容易,但維護(hù)起來(lái)不那么靈活
- 生成出來(lái)的代碼容易帶來(lái)命名沖突,語(yǔ)法錯(cuò)誤等
從上面的形式也看到,這個(gè)方案,被我們?cè)O(shè)計(jì)成了一種 DSL 代碼。因此它是具有自己的詞法、語(yǔ)法、語(yǔ)義規(guī)則的,在生成目標(biāo)編程語(yǔ)言代碼之前,會(huì)有一套自身的校驗(yàn)。DSL 的這些能力是模版所不具備的。
可能對(duì)于別的場(chǎng)合,采用 DSL 的形式并不多見(jiàn)。但對(duì)于前端工程師而言,這些年已經(jīng)見(jiàn)的較多了:CoffeeScript、Babel、JSX、TypeScript 等等。為此我們參考了諸多編程語(yǔ)言的設(shè)計(jì),最終形成了自己的一套語(yǔ)法。并借鑒編譯器領(lǐng)域的轉(zhuǎn)譯方式,因此我們可以在模型一致的情況,生成到各種不同的編程語(yǔ)言下。
整個(gè) TeaDSL 的處理流程如下:
?
??
??
最終我們支持多種編程語(yǔ)言的場(chǎng)景主要有3個(gè):
- 基本的多種語(yǔ)言的 SDK
- OpenAPI 相關(guān)的多種語(yǔ)言的 Code Sample
- OpenAPI 相關(guān)的多種語(yǔ)言的 Test Case
通過(guò)中間語(yǔ)言的強(qiáng)校驗(yàn),生成到多種目標(biāo)場(chǎng)景,可以解決編程語(yǔ)言支持不全面的問(wèn)題。同時(shí)也大幅節(jié)約 OpenAPI 維護(hù)者的精力成本,不需要反復(fù)手工地編寫(xiě)不同編程語(yǔ)言下的 Code Sample。隨著對(duì)不同編程語(yǔ)言的支持逐步完善,這些中間 TeaDSL 代碼不需要任何操作,即可自動(dòng)支持到新的編程語(yǔ)言下。
總結(jié)
TeaDSL 的主要能力是支持到不同風(fēng)格的 OpenAPI,同時(shí)支持多語(yǔ)言的 SDK、Code Sample 目標(biāo)生成。最終的目的仍然是打通從 OpenAPI 定義到文檔、到 SDK、CLI 等 OpenAPI 使用場(chǎng)景下的一致性。提供給用戶(hù)更統(tǒng)一、專(zhuān)業(yè)、一致的使用體驗(yàn)。同時(shí)也大幅降低 OpenAPI 提供者用來(lái)支持用戶(hù)的成本,通過(guò)自動(dòng)化的方式,節(jié)省精力的同時(shí),還減少人為參與時(shí)導(dǎo)致的錯(cuò)誤。
目前 TeaDSL 在阿里云的一些 SDK 上已經(jīng)有所應(yīng)用,如:https://github.com/aliyun/aliyun-ccp。阿里云開(kāi)放平臺(tái)在持續(xù)努力提升它的整個(gè)工具支持生態(tài),以期望能建成比 Swagger 更適配的生態(tài)體系。
【本文為專(zhuān)欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】
??戳這里,看該作者更多好文??
當(dāng)前名稱(chēng):TeaDSL:支持任意OpenAPI網(wǎng)關(guān)的多語(yǔ)言SDK方案
分享網(wǎng)址:http://www.dlmjj.cn/article/coogcoe.html


咨詢(xún)
建站咨詢(xún)
