自動化代碼生成工具 Snips 開發實踐

前言

在開發工做中,常常會遇到新產品、服務上線後,須要將其 API 編寫不一樣語言的 SDK。但不一樣語言 SDK 中都有很大一部份內容是用來進行 API 的描述,並且這部分代碼量是最大的,手寫起來也枯燥易錯。因此咱們須要一種 Data-Driven 的開發方式,經過工具(Snips)來自動化的生成準確、優雅的代碼,讓開發者減小重複無心義的工做,將更多精力放在產品以及業務上。ios

本文共 4420 字,閱讀大概須要 18 分鐘。git

今天的內容包括:
github

•    QingStor SDK 簡介
•    QingStor SDK 開發流程的改變
•    API Specification
•    SDK 生成工具 Snips
•    場景化測試
•    使用 Snips 開發 QingStor Go SDK


正文

你們好,我是青雲 QingCloud 系統工程師 Aspire 。今天我來和你們分享一下 QingStor SDK 以及自動化 SDK 生成工具 Snips 的開發經驗。編程

今天交流的內容包括:json

  • QingStor SDK 簡介swift

  • QingStor SDK 的開發流程api

  • API Specification數組

  • SDK 生成工具 Snips網絡

  • 場景化測試app

  • 使用 Snips 開發 QingStor Go SDK

1. QingStor SDK 簡介

QingStor™ 對象存儲爲用戶提供可無限擴展的通用數據存儲服務,在 QingCloud Console (青雲控制檯)中能夠直接建立、使用和管理對象存儲 Bucket ,能夠方便的上傳下載文件。咱們也提供了命令行工具(如 qingcloud-cli, qsctl) 來在各類場景下進行數據的存取。可是面對海量數據的操做時,圖形化的界面和命令行工具是不夠的。另外,咱們的用戶也須要在代碼層面使用 SDK 或者直接請求 API 來接入 QingStor 對象存儲。自上線以來咱們就開放了一套標準、規範且簡潔的 RESTful API ,以及一個 Python 的 SDK 。雖然說 Python 的用戶量很是大,可是顯然只有這一種的 SDK 是沒法知足用戶需求的,再加上 QingStor 的 API 是遵循 RESTful 標準的,直接使用 API 來接入 QingStor 的成本也會高一些。(不過好在咱們已在今年上半年兼容了 AWS S3 的 API ,因此用戶也可使用 S3 的 SDK 來接入 QingStor 。)

這裏將 QingStor API 和 QingCloud IaaS 的 API 作個簡單比較:

0_1481807543034_屏幕快照 2016-12-15 下午 6.17.58.png

相比之下用戶使用 API 來接入 QingStor 的難度會高一些,對 SDK 的需求也就更強烈。

目前 QingStor 提供了包括 Go 、 JavaScript 、 Ruby 、 PHP 、 Swift 、 Java 、 Python 在內 7 種語言的 SDK ,已經能夠作到覆蓋主流編程語言,並且有了 Snips 的幫助,開發者也可以在短期內開發出另外一種語言的 SDK 。

同時,這次咱們將 QingCloud IaaS 和 QingStor 的 SDK 進行了拆分,例如 qingcloud-sdk-swift (還沒有發佈) 和 qingstor-sdk-swift 。這樣作主要是考慮到移動端對空間比較敏感,所引入的第三方庫越小越好,因爲 QingCloud IaaS 目前開放的 API 數量是 QingStor 的三倍,將二者合併爲一個包會形成空間的浪費,對於一個僅須要 QingStor 作爲存儲的 App 來說,只引入 QingStor 的 SDK 就足夠了。

QingStor SDK 的中文使用文檔能夠參考 https://docs.qingcloud.com/qi... ;另外這些 SDK 也已開源在 GitHub ,能夠訪問 https://github.com/yunify 來獲取,也歡迎你們給咱們提 Issue 和 Pull Request 。

2. QingStor SDK 的開發流程

首先能夠回顧一下咱們 Python SDK 的開發方式,就是在 API 發生改變以後,手動增長 SDK 中與之對應部分的代碼,這種作法效率不高,維護起來也讓人頭疼。

再加上 QingCloud IaaS 和 QingStor 共有兩百多個開放 API ,而且不斷有新的 API 伴隨產品或功能上線,要作到 SDK 的實時跟進比較困難。並且如今只有一個 Python 的 SDK ,若是再加上其餘語言的,每種語言都手動維護,會耗費工程師不少沒必要要的精力。還有一個問題比較麻煩,若是有用戶對一些小衆語言的 SDK 有需求,咱們也無法當即進行支持,這點行業內基本都有相似的狀況。

要解決這些問題,就須要換一種思路。咱們能夠看到,不一樣語言的 SDK 中都有很大一部份內容是用來進行 API 的描述(或者叫定義),並且這部分代碼量是最大的,手寫起來枯燥易錯。因此咱們採用了一種新的 SDK 的開發流程,使用標準的數據來生成代碼,以後經過場景化的測試來進行驗證,其中用到了咱們本身寫的一個代碼生成工具━━ Snips 。

Snips 使用 API 的標準化描述和代碼模版來生成各類語言 API 調用的那部分代碼,除了生成出來的代碼,還須要手動編寫的代碼,每種語言都不同,不適合統一輩子成代碼,好比錯誤處理,文件讀寫,網絡請求等。這樣作比起純手工打造一個 SDK ,須要開發的代碼量會小不少,開發效率可以獲得很大的提高。

上面是利用 Snips 開發一種新的語言的 SDK 的示意圖。下面具體說明一下。

新增一種語言的 SDK :

  • 手寫哪些錯誤處理、網絡請求等相關的的代碼

  • 編寫代碼模版

  • 使用 Snips 生成代碼

  • 經過場景化測試

  • 發佈

更新 SDK :

  • 更新 API 描述

  • 從新生成代碼

  • 經過場景化測試

  • 發佈

這裏的 API 的描述咱們是經過 Git Submodule 的形式引入到各個 SDK 項目中,這樣若是是 API 的變動,徹底不須要手動編寫 SDK 的代碼就能夠作到 SDK 的更新。

下面會逐個講一下 API 描述規範 ( API Specification )、 Snips 和 場景化測試( Scenario Based Testing )這幾個部分,咱們是怎麼作的。

3. API Specification

要實現上述的流程,得先有 API 的描述,咱們花了很長時間來肯定使用怎樣的 API 描述規範,也走了一些彎路。

起初咱們本身制定了一個 API Specification 的 Schema :

  • 這個 Schema 的目標是描述 HTTP API

  • 每個 API Specification 文件呈現一個 Service (如 QingCloud IaaS 或者 QingStor )

  • Service 中能夠有 SubService

  • 同時 Service 和 SubService 中都包含 Metadata 、 Properties 、 Operations 、 Endpoint 等信息

  • Operation 是具體的 API 請求,其中包含 Request 和 Response 的描述以及其餘基本信息

  • 定義了 boolean 、 integer 、 timestamp 、 binary 、 list 、 object 等幾種基本的數據類型,及自定義的數據類型 CustomizedType, CustomizedType 能夠出現多級引用

以後使用這套 Schema ,去描述了 QingStor 的 全部 API ,而且寫了解析器,快速實現了從 API 描述生成 Go SDK 的代碼。可是 Review 時咱們發現這個本身定義的 Schema 仍是太簡陋,沒有通過足夠的數據進行驗證,不少狀況都沒有考慮到,還有一些 Corner Case 也難以描述,而且這個 Schema 自己的校驗效果也不理想。並且若是使用這套本身定義的 Schema 來描述 QingStor 和 QingCloud API ,不管是在內部使用仍是開放出去,都是讓人比較難以接受的,這種自立門戶的作法也沒有太大意義。

而後咱們對比了幾個目前能夠用到的幾個 API Specification 的規範,最後選擇了 Swagger 。

Swagger 是一個描述 RESTful API 的規範, Swagger 具體的 Specification 你們能夠訪問它的網站來查看: http://swagger.io

今年的一月份 Swagger 改名爲 OpenAPI Specification ,由 Linux 基金會贊助成立了 OpenAPI Initiative 來繼續 OpenAPI Specification 的開發。在 Google 、 Microsoft 等大廠的支持下, Swagger 儼然已經成了業界標準,相關的生態和工具也已比較齊全,用它來做咱們 API 的描述規範再合適不過,因此咱們最終選擇了 Swagger 來從新描述了 QingStor APIs ,而且實現了用 Swagger 描述規範來生成代碼。

使用 Swagger 規範無疑是正確的,由於 Swagger 的工具和生態相對比較完善。以 Swagger Editor 爲例,它是一個 API Specification 的 Web 編輯器,能夠在編輯的同時提供代碼補全、高亮和實時語法驗證功能,感興趣的朋友能夠在 http://editor.swagger.io 體驗一下。

Swagger 雖然發展的比較快,但並非對全部 API 都友好。 QingCloud IaaS 的 API ,請求參數部分裏會有數組( Array )和字典( Map )。例如 statics.n.router_static_name 、 statics.n.router_static_value 這種請求參數,用戶實際提供的是一個由 Static 字典組成的數組,而且這個請求參數是位於 Request URL Query , SDK 會把數組和字典轉換一下格式,構造出 statics.0.router_static_name=name&statics.0.router_static_value=value 這種形式的請求串。這樣就會出現問題,在描述請求的時候須要定義數組和字典參數,因爲 Swagger 的規範比較嚴格, Operation Parameter 不容許自定義類型出現,這時就只能將請求參數的描述放在 Request Body 裏面來定義,這樣就須要解析 Specification 的時候作一些特殊處理。

Swagger 標準也考慮到了 API 數量不少致使描述文件過長的狀況,它支持使用 $ref 來引用其餘文件,固然這個引用的功能實際上是 JSON Reference 和 JSON Pointer 規範提供的,可是這裏的 $ref ,只支持同一個文件內的引用,或者是引用某個 URL 連接。咱們測試的解析器,包括 Swagger 官方的 swagger-codegen 都不支持文件間的引用,更不用提 Circle Reference 這種經常使用的狀況了。不過這個問題咱們在 Snips 中也解決掉了,能夠看到咱們的 QingStor API Specs 中的 API 描述是拆分紅了不少文件的,具體內容等下 Snips 的部分會提到。

使用 Swagger API Specification 規範來描述 API ,其做用不只僅能夠用來生成代碼,生成文檔,更重要的是它的約束做用,它反過來能夠規範 API 的開發和交付,進一步保證 QingCloud 的總體服務質量。對於 SDK 開發來說則是一種 Data-Driven 的開發方式,這種思路可讓產出的各個 SDK 在功能上保持很強的一致性,不會出現某種語言的 SDK 缺失功能,或者是更新滯後,這種思路的優點也會隨着更多產品和功能的上線變得愈來愈明顯。

API Specification 文件自己也須要驗證正確性,而使用 Swagger 標準能夠垂手可得的使用 JSON Schema 來實現 Specification 數據的驗證。

QingStor 的 API Specification 也放到了 GitHub ,這裏是地址 https://github.com/yunify/qin...

4. 代碼生成工具 Snips

接着講講代碼生成工具,對於 Swagger 來說,有官方的代碼生成器 swagger-codegen ,還有其餘的同類開源項目好比 go-swagger 。

它們雖然說能夠生成代碼,可是生成出來的代碼可控性和可讀性都不高,並不能知足咱們的需求,定製起來也比較麻煩。

例如採用 swagger-codegen 得 fork 過來,改它的 Java 代碼,並且每增長一種語言的 SDK 基本上都要去增長對應的 Java 代碼,這對於 Java SDK 以外的開發者來說是很是不友好的。

除此以外還有不少其餘細節上的問題,例如咱們 API 的遺留問題,上面說到的 QingCloud IaaS 的請求參數不標準;例如 swagger-codegen 和 go-swagger 不支持文件引用的解析;生成出來的代碼大小寫控制不嚴格( acl 被轉換成了 Acl ,而不是 ACL )等。

現有的代碼生成器沒有能夠開箱即用的,都須要去進行很多的修改。可是去實現一個 Swagger 的解析器又太費時費力了,因此咱們想到了一種折中的方案,使用開源的 Swagger 解析器來構建本身的生成器。

比對了幾個開源項目以後,咱們採用的解析器是 go-openapi/spec ( https://github.com/go-openapi/), 這個解析器的做者也是 go-swagger 的做者, go-swagger 是在這個解析器之上構建的。遺憾的是 go-openapi 也不支持文件引用,看到將來有支持文件引用功能的計劃,不過不知道何時纔會加上。因而咱們簡單熟悉了一下代碼,以後提交了幾個 PR 把這功能幫他們實現了,做者也欣然接受 「 With this PR go-swagger is the first library on go that fully supports json schema and ref resolving so very happy with it ?」。

隨後就有了代碼生成工具 Snips ,它是一個命令行工具,很好地支持着咱們的 SDK 開發,以 Go 語言 SDK 爲例,包括模版在內,手寫的代碼大約 6 千行左右,而生成出來的代碼已經達到了 2 萬多行,開發效率獲得了不小的提高。

關於模版, Snips 會從指定路徑加載模版文件,模版目錄下須要有一個 manifest 文件,能夠是 JSON 或者 YAML 格式,這個文件指定了一些生成規則,例如指定該目錄下模版文件的格式,輸出文件名的命名風格是 CamelCase 仍是 snake_case ;輸出文件的擴展名和先後綴,能夠參考 example 下的 manifest.yaml 來查看全部支持的規則。模版文件格式目前只支持一種,是 Go 語言的 template 。

與代碼生成有關的簡單邏輯是放在模版裏去實現的,同時生成器也提供了一些內置函數能夠在模版中使用,如大小寫風格的轉換、字符串替換、數據傳遞等,從而作到了生成器與某種語言無關,新增語言不須要去修改生成器的代碼。上文提到的 acl 轉換成 Acl 的問題,使用 Snips 提供的函數就能夠正確轉換,例如 {{snakeCase "acl"}} 會轉換成 「 ACL 」。

關於多版本 API , Snips 也有解決方案。

經過 -n (--service-api-version) 參數來指定使用的 API 版本,而後將代碼生成到不一樣的目錄,例如 latest version 的代碼在 service 目錄,特定版本的代碼能夠在 service-2016-01-06 中,再根據語言的不一樣看是否還須要相應的調整。以 Go 語言爲例,用戶使用的時候 import 不一樣的路徑的 service 便可切換不一樣版本的 API ,如 import "github.com/yunify/qingstor-sdk-go/service-2016-01-06"。

Snips 目前已經開源, GitHub 地址: https://github.com/yunify/snips 。目前是針對 QingCloud IaaS 和 QingStor API 的代碼生成工具, Snips 的思路和其餘的 Swagger 生成器的思路不太同樣,將來也可能會作成一個通用的代碼生成器。

5. 場景化測試

SDK 開發出來了,除了單元測試以外,還須要在線上生產環境進行測試,保證交付的 SDK 可正常工做,咱們稱之爲服務測試( Service Test )。

服務測試中咱們採用 Cucumber ( https://cucumber.io),它是一個 Behaviour-Driven Development (BDD) 工具。 Cucumber 會讀取經過天然語言描述的測試場景和數據,而後結合不一樣的測試實現去驗證是否經過。 Cucumber 能夠被稱做是一種測試方式,基本上每種語言都有它的實現。

舉個例子:
0_1481807807209_屏幕快照 2016-12-15 下午 6.20.22.png

獲取一個 Object , Cucumber 描述是這樣的:
0_1481807824338_屏幕快照 2016-12-15 下午 6.20.28.png

Ruby 中對應的測試實現是這樣的:
0_1481807869956_屏幕快照 2016-12-15 下午 8.45.46.png

Cucumber 會檢查代碼場景執行過程當中的數據是否知足預期,給出一個完成的測試結果

這樣以使用者的角度來真實的測試 SDK ,而且可讓全部 SDK 的測試用例保持一致,在保證 SDK 質量的同時,也能夠作到各類語言 SDK 功能的一致性。

QingStor SDK 的測試場景也放在了 GitHub : https://github.com/yunify/qin...

6. 使用 Snips 開發 QingStor Go SDK

上面講了整套的 QingStor 的 SDK 開發流程,下面用 QingStor Go SDK 來舉例說明一下。

qingstor-sdk-go https://github.com/yunify/qin...

首先須要實現 SDK 最基礎的部分,好比網絡請求和簽名處理、文件讀寫、錯誤處理等等,而後再使用 Snips 生成 API 相關的代碼。

假設基礎部分如今已經完成了,而且通過了單元測試。

接下來安裝 Snips ,可使用 go get -u github.com/yunify/snips 安裝,或者直接訪問 GitHub 下載編譯好的二進制文件。

在代碼倉庫目錄下以 git submodule 的形式引入 API Specification 和 Test Scenarios:

./specs/qingstor 引用 QingStor API specifications
./test/features 引用 QingStor 測試場景

而後即可編寫代碼模版,下圖所示爲 Go SDK 的模版文件:
0_1481807963055_5D34506E-D55F-44B2-9C99-372EF92834B7.png

具體的模板文件內容請見:
https://github.com/yunify/qin...

模板編寫完成後,便可利用模版和 API Specifications 來生成代碼。下圖爲生成代碼的命令,生成完的代碼有可能會難看,能夠格式化一下代碼,固然若是模版控制的嚴格,生成出來的代碼足夠漂亮,能夠跳過格式化的步驟。
0_1481808000037_793462BC-26BB-4D54-9E72-DE2414A51CCA.png

以後即可使用這些生成好的代碼,實現測試場景,具體的代碼請見這裏: https://github.com/yunify/qin...
0_1481808038670_4E9E0DB7-9157-4312-8C58-E3EACB5B0811.png

最後運行測試
0_1481808055907_44D55874-9759-4141-84E4-BE255C4BD2EC.png

7. 開發者激勵活動

目前 QingStor 已經提供了七種語言的 SDK (其中 Python SDK 的新版也會使用 Snips 來從新生成),覆蓋了主流的編程語言,但還有一些編程語言的 SDK 咱們沒有來得及開發,爲激勵更多的開發者參與進來貢獻其它語言的 SDK ,咱們特此發起 QingStor SDK 大賽活動,報名地址請見: https://jinshuju.net/f/0MB6w6

相關文章
相關標籤/搜索