前言
如今 OpenAPI 已經成為完成系統之間內建的重要橋梁,OpenAPI 的可用性以及客戶在使用時的體驗就變得越來越重要,阿裡雲前架構師曾說過:"阿裡雲的本質是一家賣 API 的公司。API 有沒有做好,是關乎生死的大事"。但是從日常來自客戶的回報中我們總結了以下比較通用的幾點OpenAPI體驗問題 :
• 雲産品 OpenAPI 沒有提供 SDK 或者 SDK 語言不全;
• 部分雲産品的 SDK 使用風格差異過大,導緻使用成本增加;
• API 文檔缺失或者不夠清晰,不具備指導意義;
• 沒有場景化 CodeSample 或提供 CodeSample 無法運作。
那麼阿裡雲又該如何解決以上問題呢?本文就将通過客戶調用阿裡雲 OpenAPI 服務所遇到的重點問題來介紹開源技術
Darabonba(原名TeaDSL)是如何逐漸解決的。
多語言 SDK 生成
說起生成多語言 SDK,大家第一時間想起的一定是目前業界内生成多語言 SDK 的通用方案——Swagger,開發者通過 Swagger 定義的 OpenAPI 标準并配合模闆的方式來生成多語言 SDK。不過問題并沒有是以而得到完美的解決,首先模版的生成方式相對生硬,雖然實作起來容易,但維護起來卻不那麼靈活;其次,大量 OpenAPI 并不是 RESTful 風格的,這就導緻很多的産品現存的 OpenAPI 在文檔、SDK等場景下,無法使用上 Swagger 這樣強大的生态工具鍊。既然無法沿用 Swagger 規範中繼資料的方法來解決這個問題,我們就需要對我們的工作重新進行抽象。在筆者看來,之是以沒有一套中繼資料可以适用于所有的網關主要還是因為每個網關所對應的後端情況不同,就像機器語言或者彙編語言會因為架構的不同而有所不同,但是其本質還是描述如何通過操作寄存器、記憶體裡的資料來完成一個程式,進階語言就是通過 AST 相容了各平台的這些不同最後解決了這些問題。而對于 OpenAPI 來說也是同樣的道理,是以我們通過重新定義一門 DSL 語言 Darabonba 來描述各種各樣的 OpenAPI。
通過 Darabonba 對 OpenAPI 進行描述,其本質就是統一了中繼資料,隻是這個中繼資料并不是 JSON 或者 Yaml 這樣的方式來描述的,而是通過 DSL 代碼來描述。Darabonba 的編譯器則會将 Darabonba 的 DSL 代碼轉化為 AST,通過 OpenAPI 描述轉化而來的 AST 不僅包含了 OpenAPI 的資訊,而且還包含整個 OpenAPI 的流程性描述,是以我們隻需要通過 AST 開發對應的各語言SDK就可以生成多語言的 SDK了。Darabonba 具體的設計思路和理念可參考文章:
《Darabonba:支援任意 OpenAPI 網關的多語言 SDK 方案》這裡就不再贅述。
子產品化設計
在通過中繼資料向生成 SDK 的過程中,僅僅通過對 OpenAPI 的資料模型和請求/響應描述是不夠的,還需要各種參數處理,簽名生成,檔案上傳,流操作等各種複雜的方法,以往通過模闆生成 SDK 的時會選擇維護一個各語言的核心子產品來封裝這些方法,但是随着支援的 OpenAPI 越來越多核心庫中的方法也是越來越多,就會産生以下的問題:
• 客戶使用或者開發者接入核心庫的 SDK 開發者來說成本會越來越大;
• 核心庫的維護人員的維護成本也會越來越高,随着方法越來越多也需要不斷的對核心庫進行重構,遇到不相容性改動的可能性也會越來越高;
• 所有方法雜糅在一個核心庫中,在修改時容易牽一發動全身,需要大量的測試用例保障。
在通過 Darabonba 生成的 SDK 時也會遇到同樣的問題,Darabonba 作為一門 DSL 語言主要能力在于描述 OpenAPI ,為了保障生成的 SDK 具備完整的功能同樣需要很多實作很多核心方法,而在總結以往維護核心庫的中遇到的問題以後,我們選擇了現在在進階語言中非常常見的子產品化開發理念,并提供了相應的指令行工具
Darabonba CLI和
Darabonba 子產品倉庫。
接口子產品
Darabonba 其核心能力是描述 OpenAPI,缺少複雜邏輯實作的能力,為了彌補這個能力 Darabonba 設計了接口子產品的概念。與 Java 中的 interface 接口類型定義類似,Darabonba 的接口子產品即是隻在 Darabonba 編寫的DSL 代碼中隻定義方法體的集合而并不實作其具體邏輯,真正的邏輯則是由各語言分别實作。例如 Darabonba 中常用的
Console子產品:
* Console val with log level into stdout
* @param val the printing string
* @return void
* @example \[LOG\] tea console example
*/
static function log(val: string): void;
我們隻需要在子產品中申明子產品包含 log 方法并描述它的出參入參即可,而各語言則通過自身語言的特性來實作該方法即可,其具體實作可參考
Console 子產品源碼。在編寫好生成接口子產品以後可以通過 Darabonba 提供的 Darabonba CLI 執行
dara publish
将子產品釋出到 Darabonba 子產品倉庫,就可以在 Darabonba 代碼中使用了。下面就是我們通過引入 Console 子產品來列印字元串的一段代碼:
import Console;
static async function main(args: [ string ]) throws : void {
Console.log("hello world!");
}
通過接口子產品的設計理念将以往核心庫中的方法根據功能拆分成一個個包含了特定功能的基礎子產品,不僅使得生成的 SDK 更好用邏輯更清晰,同時也做到了足夠的抽象避免很多在生成 SDK 過程中重複造輪子的工作。目前 Darabonba 官方提供了包含了常用方法的
Util子產品、檔案上傳所使用的
FileForm以及
XML子產品等,同時開發者也可以編寫與自己業務邏輯相關的接口子產品并釋出到 Darabonba 子產品倉庫。
OpenAPI 子產品
在 Darabonba 的子產品化設計中不止有接口子產品,事實上每一個 Darabonba 的項目都是一個子產品,是以基于一組 OpenAPI 描述編寫的 Darabonba 代碼就是一個 OpenAPI 子產品。開發者在完成了 OpenAPI 描述的 Darabonba 代碼編寫以後,同樣可以通過 Darabonba CLI 将描述 OpenAPI 的 Darabonba 子產品釋出到子產品倉庫中,這樣使用 SDK 的使用者就可以通過子產品的詳情頁面檢視 SDK 中包含的方法及各語言 SDK 的安裝說明等資訊了。
基于一組 OpenAPI 釋出的 Darabonba 子產品不僅可以幫助使用者更好的了解這組 OpenAPI,更可以在這個基礎上實作 OpenAPI 接口的 CodeSample 編寫,進而實作多語言的 CodeSample 統一生成。
CodeSample 自動生成
可以說對于簡單的 API 調用普通的 API 文檔就足夠了,但是随着現在 OpenAPI 在系統與系統內建之間使用的越來越廣泛,其複雜度也随之提高,以往單純使用 API 文檔的方式已經不足以讓客戶順利的使用 OpenAPI 了。從阿裡雲目前的工單情況來看,SDK 相關的客戶咨詢至少有一半是因為沒有 CodeSample 造成的,其中更是有1/4的客戶是直接要求為 SDK 提供 CodeSample。
這種情況下,能夠提供給使用者可運作、可調試的 CodeSample 示例就成了文檔中必不可少的一部分,但是如何能夠編寫全語言的 CodeSample 并且保障其可運作,卻是一個極大的問題。如果通過人力來維護,很容易就出現語言不全,或是編寫的代碼沒有維護的問題,阿裡雲中遇到最多的問題就是 OpenAPI 在疊代,而 CodeSample 卻忘記疊代了造成了提供出去的 CodeSample 無法使用進而被使用者诟病。
通過引用子產品倉庫中 Darabonba 子產品編寫的 CodeSample 則可以避免這樣的問題,首先多語言的自動生成,節約了大量的維護成本,而且風格統一利于使用者了解;同時 Darabonba 編譯時采用類型的強校驗,一旦 OpenAPI 的參數或者傳回結果出現了不相容的更新,CodeSample 則會生成失敗進而通知到開發者更新相關示例來解決這個問題。下面是阿裡雲語音服務 SDK 相關的 CodeSample 示例,大家也可以點選
示例連結嘗試生成:
import Dyvmsapi;
import RPC;
import Console;
/**
* 使用AK&SK初始化賬号Client
* @param accessKeyId
* @param accessKeySecret
* @param regionId
* @return Client
* @throws Exception
*/
static function createClient (accessKeyId : string , accessKeySecret : string) throws : Dyvmsapi{
var config = new RPC.Config{};
// 您的AccessKey ID
config.accessKeyId = accessKeyId;
// 您的可用區ID
config.accessKeySecret = accessKeySecret;
return new Dyvmsapi(config);
}
/**
* @param args
* @throws Exception
*/
static async function main(args: [string]) throws : void {
var client = createClient("accessKeyId","accessKeySecret");
var request = new Dyvmsapi.QueryCallDetailByCallIdRequest{
// 通話的唯一識别ID。
callId = "100625930001^10019107xx",
// 産品ID。
// 11000000300006:語音通知。
// 11010000138001:語音驗證碼。
// 11000000300005:語音IVR。
// 11000000300004:語音雙呼。
// 11000000300009:語音SIP。
// 11030000180001:智能外呼。
prodId = 11000000300004L,
// 指定通話發生的時間,格式為Unix時間戳,機關毫秒。會查詢這個時間點對應的一整天的記錄。
queryDate = 1577255564
};
var response = client.queryCallDetailByCallId(request);
Console.log(response.code);
}
Test Cases 自動生成
在 OpenAPI 公布上線以後,如何能夠保障 OpenAPI 持續可用就是一個非常重要的問題,如果沒有一個保障機制,很可能會出現 OpenAPI 出了問題就無法及時發現,客戶的投訴也就随之而來。而且 OpenAPI 和 SDK 也會遇到更新更新等情況,如何能保障這些更新更新不會給正在使用 OpenAPI 的客戶造成問問題也是 OpenAPI 提供方遇到的一個非常大的挑戰。為了解決這些挑戰, 就需要 OpenAPI 提供方編寫 Test Cases 作為日常的持續內建來檢驗 OpenAPI 的可用性,但是目前多語言的 SDK 的 Test Cases 大多存在以下的問題:
• 需要大量的人力去維護 Test Cases ,而且無法保障所有語言都擁有 Test Cases;
• OpenAPI 的 Test Cases 少而且更新頻率低,對 OpenAPI 覆寫面低無法起到有效的保障作用;
• 各語言 SDK 的由各語言的開發同學分别維護,是以用例不同步導緻不同語言之間的測試結果有所差異。
而Darabonba 的多語言生成能力則可以解決以上所有問題,隻需要引用 SDK 在子產品倉庫中對應的 Darabonba 子產品與 Darabonba 官方提供的斷言子產品
Assert子產品編寫對應的 Darabonba Test Cases 即可為各語言 SDK 生成其對應的 Test Cases。通過 Darabonba 自動化生成 Test Cases 不僅可以解決人力不足 Test Cases 很難覆寫各語言 SDK 的情況,而且生成的各語言 Test Cases 标準一緻,也可以解決各語言 Test Cases用例不同步造成的測試差異問題。下面是一段通過 Darabonba 編寫的測試用例:
import Assert;
import Dyvmsapi;
import RPC;
static function createClient (accessKeyId : string , accessKeySecret : string) throws : Dyvmsapi{
var config = new RPC.Config{};
config.accessKeyId = accessKeyId;
config.accessKeySecret = accessKeySecret;
return new Dyvmsapi(config);
}
static async function TestNumberEqual() throws : void {
var client = createClient("accessKeyId","accessKeySecret");
var request = new Dyvmsapi.QueryCallDetailByCallIdRequest{
callId = "100625930001^10019107xx",
prodId = 11000000300004L,
queryDate = 1577255564
};
var response = client.queryCallDetailByCallId(request);
Assert.equal(response.code, 'OK', 'queryCallDetailByCallId is failed!');
}
總結
Darabonba 的主要能力是支援到不同風格的 OpenAPI,同時支援多語言的 SDK、CodeSample 目标生成。最終的目的仍然是打通從 OpenAPI 定義到文檔、到 SDK、CLI 等 OpenAPI 使用場景下的一緻性。提供給使用者更統一、專業、一緻的使用體驗。同時也大幅降低 OpenAPI 提供者用來支援使用者的成本,通過自動化的方式,節省精力的同時,還減少人為參與時導緻的錯誤。
貢獻指南
Darabonba 的目标是讓使用者得到極緻的 OpenAPI 體驗,這樣的目标僅靠我們一個團隊是很難完成的,是以我們也需要更多的人參與到我們的開源項目來,大家可以按以前方式參與進 Darabonba 貢獻中來:
• 參加阿裡雲組織的
CodeSample 全民賽碼開發者挑戰賽,不僅能通過 Darabonba 編寫現有阿裡雲雲産品 SDK 的CodeSample 熟悉 Darabonba 并在使用過程中為 Darabonba 提出自己的建議還能赢取大疆無人機。
• 參與其他語言生成器的生成,目前 Darabonba 隻支援了比較常用的六門語言,還需要更多生成器的支援。
• 編寫更多底層工具子產品,使 Darabonba 能夠生成更便捷的 SDK。
• 參與 Darabonba 編譯器及 CLI 工具的建設中來。
• 編寫更多 OpenAPI 中繼資料轉換到 Darabonba 的工具。