自動生成Android,iOS的REST API訪問代碼
上個月,介紹了生成Java JDBC訪問代碼,從SQL文件,后面應用到我負責的幾個實際的項目,效果不錯, 程序清晰了很多,也更好維護。
用類似的思路,我做了一個新的工具: api-kit,移動開發從中受益。
移動開發:Android,iOS,RESTful API
移動開發,下面幾個環節免不了:
- 負責Server端開發的同學,寫API,寫文檔
- 負責Android的同學,根據文檔,以及和server端同學的交流,實現和服務器的通信代碼
- 負責iOS的同學,根據文檔,以及和server端同學的交流,實現和服務器的通信代碼
這里面有一些重復: 負責server的同學開發API后,寫文檔是把同樣的信息,表達了兩次,一次是在code里面,一次在文檔。 客戶端同學實現各自平臺的訪問代碼,是重復,因為這部分信息是server端的code(或者文檔)的一次翻譯(跨語言,跨平臺)。
- 信息在這個過程中是無創造的復制(或者翻譯,即沒有引入更多的信息)
- 不能保證復制的正確性,比如文檔錯誤,客戶端寫code引入bug,等
- 復制的成本較高:溝通成本,寫code的成本
- 如果API重構,這個改動,需要在其它幾個地方replay
這幾天,我寫了一個程序,api-kit,通過自動生成代碼的方式,解決這里面的大部分重復,使信息的流動更高效和快捷。
例子
假設API描述是這樣的:
// 這是api-kit的輸入,輸出是各個平臺的code:Android, iOS, Server端 struct Book { i32 id string title string isbn float32 price string description } @url(/books/newest) @get func list<Book> getNewest(i32 limit, i32 offset); @url(/books/search) @get func list<Book> searchBook(string q, i32 limit, i32 offset);
API描述是api-kit的輸入,輸出是各個平臺(Server,Android, iOS)的code。
生成的Server端code
- IHandler interface
- Book類
- hook這個interface到servlet的支持代碼(參數解析,綁定,dispatch)
public interface IHandler { // Called before every function. Use cases: setup context, authentication return false to abort further execution. public boolean before(Context context); // Called after every function. One use case is logging public void after(Context context); // GET /api/books/newest public List<Book> GetNewest(Context context, int limit, int offset); // GET /api/books/search public List<Book> SearchBook(Context context, String q, int limit, int offset); } 服務器端的工作簡化為實現這個Interface
生成的Android端code
生成的code,處理url拼接,返回值解析,暴露給程序員的是函數:
零依賴,能有效的減少apk的體積。在compiler的幫助下,做到類型安全,服務器重構后,這邊會編譯出錯,refactor會方便一些。
生成的iOS的code
iOS顯然是需要支持的。先花了一天時間,學習swift,并完成swift的code生成。在和公司的iOS工程師溝通時,他們在用objective-c, 于是又多花了一天時間看oc,并生成oc的code,語法有點不太習慣,但還是搞定了。
也是零依賴的,能有效的減少app的體積。由于oc支持類型安全的dict和arr挺困難,于是,通過注釋幫助一點。
Batch,打包多個請求
由于移動的特殊性(latency),一個頁面一般需要請求多次,才能render。請求多用異步,多個異步的嵌套挺不方便, 如果能合并這些請求,移動端的開發會更方便,也會提高性能。api-kit透明的實現了這一點。
ns com.kanzhun.api struct Book { i32 id string title string isbn float32 price string description } struct NewestReq { i32 limit i32 offset } struct SearchBookReq { string q i32 limit i32 offset } @url(/books/newest) @get func list<Book> GetNewest(NewestReq req); // batch要求參數的個數僅為一個 @url(/books/search) @get func list<Book> SearchBook(SearchBookReq req); // `batch` 為關鍵字 // GetNewest, SearchBook 都是函數名 // batch請求,和服務端是一次交互,客戶端發送一次請求給服務端 @url(/batch) batch Batch(GetNewest, SearchBook)
生成的code
- 在服務器端透明的處理掉Batch請求
- 在客戶端生成名為Batch的函數,接受GetNewest的req,SearchBook的req,返回BatchResp
現在的狀態
- Android,iOS,服務端(servlet)已經完成,已測試。生成的code和手寫的代碼一樣,高效簡潔。
- 除去Batch,沒有任何的私有協議。這里采用的是大家熟悉的HTTP,JSON,RESTful。 事實上,api-kit也可以用來生成已有的restful api的客戶端code。
- Batch用了一點私有協議: 請求打包,post給服務器,服務器拆開,取出一個一個的請求,挨個調用,打包每個函數的返回值,返回客戶端。Batch的服務端邏輯,是自動生成的
- Api的定義文件,是個很好的文檔。api-kit把文檔,翻譯成了可以執行的代碼。代碼也是文檔。這會節約團隊之間的交流成本, 節約出來的時間,可以干更有意思的事情,比如曬曬太陽,喝喝咖啡,和奶奶聊聊天,聽她講故事。
- Rest API的url endpoint是什么,對于客戶端來說,變成了實現細節。客戶端變成僅關心 函數名,參數,返回值,而這些,IDE會給我們很好的幫助。
- 整個流程簡化為:定義api規范,服務器端實interface。客戶端的代碼已經生成好。大家思考的方式,變成函數,返回值,參數。 交流也就變為該調用哪個函數,某個參數是什么意思,參數名,函數名,在common sense的幫助下,不少也是self explained,這部分溝通也會省掉一些。
接下來的工作
- 可能我會想辦法做到透明的cache(生成cache的code),
- 支持其它語言,比如生成ajax調用的js code,用于支持網站開發,Python的客戶端(開發時的黑盒子測試)
- 生成go的服務器端代碼
我們正在招聘,需要Android和iOS工程師的加入,和我一組,開發看準網的App,為四億職場人服務。 請發簡歷 到 shenfeng at kanzhun dot com,請加入我們