溝通創造價值,分享帶來快樂。這裏是程序員閱讀時間,每天和你分享讀書心得,歡迎您每天和我一起精進。今天和大家一起討論的話題是如何設計一個良好的API接口?
作者:梁桂釗
解讀:張飛洪
挑戰
API是軟件系統的核心,而我們在設計API接口的時候會面臨着非常多的挑戰:
-
場景上來看,它是多樣的,如何設計一個隨處適用的API?
-
我們所參與的業務不斷演進的,如何設計一個有兼容性的API?
-
我們的軟件流程是協同開發的,那我們如何實現對API的統一認知?
今天我想和大家探討一下如何設計一個良好的API接口,我覺得好的API設計需要同時考慮到這幾個要素:標準化、兼容性、抽象性、簡單性、高性能,可以說這幾個要素缺一不可。
標準化
對於Web API標準化而言,一個非常好的案例就是Restful API。目前業界的Open API多數是基於Restful API規範設計的。
1、等級模型
需要注意的是Restful API它具有成熟度的模型。
-
其中Level 0是普通的請求響應模式。
-
Level 1引入了資源的概念,各個資源可以單獨創建URI,與Level 0相比,它通過資源分而治之的方法來處理複雜問題。
-
Level 2引入了一套標準的HTTP協議,它通過遵守HTTP協議定義的動詞並配合HTTP響應狀態碼來規範化Web API的標準。
-
Level 3中,使用超媒體可以使協議擁有自我描述的能力。
通常情況下,成熟度模型中達到Level 2就已經非常好了。
2、URI
在Restful API中,每一個URI代表着一種資源,是每一個資源的唯一定位符。所謂資源,它可以是服務器上的一段文本、一個文件、一張圖片、一首歌曲,或者是一種服務。
Restful API呢,規定了通過get/post/put/patch/delete等方式對服務端的資源進行操作。
因此,我們在定義一個Web API的時候,需要明確定義出它的請求方式、版本、資源名稱和資源ID。
舉個例子,要查看用戶編碼是101的用戶信息,我可以定義get的請求方式,而他的版本是V1,資源名稱是users,資源ID是1001。
這裏可以思考一下,如果存在多個資源組合的情況呢?
事實上還可引入子資源的概念,需要明確定義出它的請求方式、版本、資源名稱與資源ID,以及子資源名稱與此資源ID。
舉個例子,要查看用戶編碼是101的用戶的權限信息,我可以定義get的請求方式。而他的版本是V1,主資源名稱是Users,主資源ID是1001子資源名稱是Roles,資源ID是101。
有時候,當一個自然變化難以使用標準的Restful API來命名時,就可以考慮使用一些特殊的actions命名。
比如密碼修改接口,我可以定義Put的請求方式,而他的版本是V,主資源名稱是users,主資源ID是101資源字段是password。然後定義一個action的操作是modify。
3、錯誤碼和返回機制
與此同時啊,建議不要試圖創建自己的錯誤碼和返回錯誤機制。
很多時候呢,我們覺得提供更多的自定義的錯誤碼有助於傳遞信息,但其實,如果只是傳遞信息的話,錯誤信息字段可以達到同樣的效果。
此外,對於客戶端來說,很難關注到那麼多錯誤的細節,這樣的設計只會讓API的處理變得更加複雜,難於理解。
因此,我的建議是遵守Restful API的規範,使用HTTP規範的錯誤碼。例如,我們用200表示請求成功,用400表示錯誤的請求,而500則表示服務器內部的錯誤。
當Restful API接口出現非200的HTTP錯誤碼響應時,可以採用全局的異常結構響應信息。
4、返回體結構
這裏列出了最爲常用的幾個字段,講一下它們各自表示的含義。
-
其中code字段用來表示某類錯誤的錯誤碼,例如前面介紹的無效請求、缺少參數、未授權資源、未找到資源、已存在的錯誤。
-
而message字段用來表示錯誤的摘要信息,它的作用是讓開發人員能快速識別錯誤。
-
server_time字段,用來記錄發送錯誤時的服務器時間,他可以明確的告訴開發人員發生錯誤時的具體時間,便於在日誌系統中根據時間範圍來快速定位錯誤信息。
此外,不常用字段會根據不同的情況做出有不同的響應。
如果是單條數據,則返回一個對象的json字符串;如果是列表數據,則返回一個封裝的結構體,其中涵蓋count字段和item字段。
count字段表示返回數據的總數據量。需要注意的是,如果接口沒有分頁的需求,儘量不要返回這個count字段,因爲查詢總數據量是耗性能的操作。
此外,item字段表示返回數據列表,他是一個json字符串的數組。
5、小結
總結一下,怎麼來理解規範呢?可以說他就是大家約定俗成的標準,如果都遵守這套標準,自然溝通成本也就大大降低了。
兼容性
接着我們再來探討一下API接口的兼容性。由於我們參與的業務是不斷演進的,設計一個有兼容性的API就顯得尤爲重要了。如果接口不能夠向下兼容,業務就會受到很大影響。
例如:
-
我們的產品是涵蓋android、ios、pc端的,都運行在用戶的機器上,這種情況下,用戶必須升級產品到最新的版本才能夠更好的使用。
-
同時,我們還可能遇到服務端不停機升級,由於API不兼容而遇到短暫的服務故障。
爲了實現API的兼容性,我們引入了版本的概念,前面的案例URI中通過保留版本號實現了兼容多個版本。
舉個例子,針對要查看用戶編碼是1001的用戶信息,可以分別定義V1和V2兩個版本的API接口,然後分別讓他們對應兩套不完全兼容的業務邏輯特性。
抽象性
通常情況下,我們的接口抽象都是基於業務需求的,因此我們一方面要定義出清晰的業務問題域模型,例如數據模型和領域模型等,並建立起某個問題的現實映射,這樣有利於不同的角色對API設計認知的統一。
另一方面,API設計如果可以實現抽象,就可以很好的屏蔽具體的業務實現細節,爲我們提供更好的可擴展性。
簡單性
簡單性的主要宗旨是遵守最少的知識原則。
怎麼來理解呢?其實就是客戶端不需要知道那麼多服務的API接口,以及這些API接口的調用細節,比如設計模式的外觀模式和中介者模式都是它的應用案例。
如圖所示,外觀接口將多個服務進行業務封裝與整合,並提供了一個簡單的API調用給客戶端使用,這樣設計的好處是什麼呢?就在於客戶端只需要調用這個外觀接口就行了,省去了一些繁雜的步驟。
性能
同時,我們還需要關注性能,就比如說外觀接口,雖然保證了簡單性,但是增加了服務端的業務複雜度,同時,由於多服務之間的聚合,導致他們的接口性能也不是太好。
此外,我們還需要考慮字段的各種組合會不會導致數據庫的性能問題。有時,我們可能暴露了太多字段給外部使用,導致數據庫沒有相應的索引而發生全表掃描。這種情況在查詢的場景下非常常見,因此我們可以只提供存在索引的字段組合給外部調用。
Result<Void> agree(Long taskId,Long caseId,Configger configger)
在上面這個代碼案例中,要求調用方必填taskId和caseId來保證數據庫索引的使用,以進一步保證服務提供方的服務性能。
總結
今天給大家側重探討的是如何設計一個良好的API接口。
好的API設計需要我們同時考慮到標準化、兼容性、抽象性、簡單性和高性能。
其中,標準化的關鍵在於儘可能少的創建自定義規範和機制,而是共同遵守業內標準,例如HTTP規範和Restful API規範。
通常情況下,我們會採取版本號來解決多版本的兼容性的問題。
抽象性需要確保能夠定義出清晰的問題域模型,儘可能屏蔽具體的業務實現細節。
簡單性是相對的,需要遵守最少知識原則,讓調用方儘可能少的知道內部的調用細節,性能注意的細節就多了,這裏主要強調了業務組合和參數組合場景。