Google Cloud 的 API 設計
最近(很久前)在設計 API 接口的時候發現了一些很難取舍的地方,就看了下業界領先的企業都是怎么設計類似 API 的,發現了很多之前設計的缺陷和一些新的思路。主要參考了 AWS 和 Google Cloud 的 API 設計,兩家的設計可以說是把兩個不同的風格發揮到了極致,做同樣的事情 API 的各個方面都可以設計的完全不一樣。Google 有一個自己的 API Design 規范可以在網上找到,不過真實的 GCE API 規范略有一些出入。而 AWS 的 API 設計是沒有什么文檔的,只能通過現有的 API 進行逆向工程來反推設計理念。這篇文章會比較瑣碎,更像是一個讀書筆記,細節的東西會比較多。
Google Cloud API 由于 Google 的文檔比較詳細了,就先照著 Google 的文檔來講,而 Google 的 API 是按照 REST 風格來的,而 REST 只是一種紙面的模式,每家的實現可能都不一樣,我們就來看下 Google 眼里的 REST 是什么樣的。然后介紹一下 REST API 的一些局限和缺點以及 Google 的解決方式。 最權威的 REST 當然還是得去看論文了,不過對于普通開發者來講 HTTP 的 REST 就是一個 URL 指向一個資源,然后這個 URL 上通過不同的 HTTP 方法來實現對這個資源的不同操作。簡單來說就是指向資源的 URL + HTTP 方法就構成了常見的 REST API。這里面每一個部分都有很多門道。 URL URL 可以分成好幾個組成部分 1. 域名,不同的域名可以區分這個網站提供的不同 API 服務。比如一個圖書,一個電影,域名就應該是 library.oilbeater.cn 和 moive.oilbeater.cn 2. 版本號,對應不同的歷史版本,可以是 v1,v2,也可以是 alpha,beta 這樣 3. 資源集合,書店里可能會有多種資源的集合,比如圖書,報紙,影碟,這就需要用資源集合來區分 /books, /newspapers, /cds 等等 4. 資源名稱,這時候才真正到具體的資源,一個圖書可能就是 library.oilbeater.com/v1/books/book_1 5. 子資源,有時候一層的資源模型不能滿足現實的需求,比如圖書館的檢索是按照書架來的,需要先知道在哪個書架才能找到書,就需要在中間插一層資源。最后的 url 可能就變成了 library.oilbeater.com/v1/shelves/shelf_1/books/book_1 6. 重復 3 - 4 資源命名 1. 資源名必須是合法的 C 語言變量名,這個規范主要是為了代碼和 SDK 的一致性,不然有個減號這樣的字符很多語言的 SDK 里這個資源對象你就不得不換個名字或者寫法了,很容易造成不一致,一些自動生成 SDK 的工具也會失敗 2. 資源集合必須是復數 3. 不要用縮寫避免不必要的歧義 4. 不要用過于泛化的資源類型,像 type, object,element,resource 這樣的命名一來不是很清楚,二來很容易和編程語言的關鍵字撞名字,人為增加編程難度。 方法 常用的 HTTP 方法有 GET, POST, PUT, DELETE 稍微常見的還有 HEAD,PATCH, 不太常見的還有 COPY, LINK, LOCK, VIEW 等等。由于 HTTP 是純文本的協議,并沒有規定只能用哪幾個方法,只要服務端做處理就可以自己再自定義其他方法的,比如 Google 自己就實現了一個 BATCH 來做批量處理。 每個 HTTP 方法和以對應不同的語義,而且這些語義像 GET 是獲取, POST 創建 PUT 更新 DELETE 刪除基本上大家都是形成共識的,API 最后的統一性會很好。REST 的初衷也是讓資源盡可能的多,每個資源的方法盡可能的只有標準的幾個方法,這樣所有的 API 看起來長得都很像,學習和實現的成本都會很低。下面說一下具體的每個方法需要注意的一些事情 List 1. 需要用 GET 方法 url 指向資源集合,并返回資源的列表 2. List 返回的內容最好有分頁信息,而不是簡單的一個資源列表,便于前端的展示并減少每次拿所有數據的性能消耗 3. 資源列表需要是有序的,如果兩次請求同樣資源順序完全不一樣前端再不處理就會有奇奇怪怪的現象 4. List 需要提供簡單的按照某個字段排序,filter 方法,更復雜的過濾查詢需要單獨的方法 5. 可以返回一些 metadata,比如資源數量,資源集合的信息等 Create 1. POST 方法 URL 指向資源集合,創建成功需要返回這個資源,而不是只有一個 201 的狀態碼 2. 需要允許 client 自己指定 resource_id,而不是全部由系統自動生成。這樣第三方系統可以根據情況進行重試和重復檢查。不然一個 create 請求中間路徑上有重試就會生成多個 id 不同的資源,后續處理會很麻煩。 Update 1. PUT 或者 PATCH 方法 URL 指向具體資源,更新成功需要返回資源實體 2. PUT 一般用于整個資源實體的更新,而 PATCH 只更新某個字段。對于一個復雜的有大量字段的資源,最好兩種 API 都提供,而不是只提供一個 PUT。只提供一個 PUT 即使只更新一個字段也需要傳遞完整的資源實體,很多情況下是沒必要的 用戶自定義方法 由于 HTTP 的標準方法是有限的,而很多語義是標準方法不能表示的,比如需要把書從一個書架移動到另一個書架就很難用標準方法表示,這就需要用戶自定義方法。定義 HTTP 方法理論上沒問題,但很多第三方庫并不支持這種方法,這種 API 給別人看也會比較奇怪,所以通常會把方法加在 url 里,通常是加在資源名稱后面。比如移動一本書就可以是 /books/book_1:move 1. 通常的做法是用『 /』 來做 URL 的分隔,但是在 Google 的規范里用的是用『 :』 因為 / 分隔會有歧義,不知道后面的到底是一個子資源還是一個用戶自定義方法。聽起來很有道理的樣子,但是在 GCE 的 API 里并沒這么做也是用的『 /』 2. 盡管 URL 里有了自定義方法,HTTP 方法還是盡量用符合語義的,比如更新類的操作都用 PUT 3. 現實中很多操作都是標準 HTTP 方法表示起來很苦難的,比如重啟機器,發送郵件,賬號登錄這些,可以想一下這些操作如何用 HTTP 標準方法來設計 API 標準字段 不同的資源有許多共同的屬性字段,由于每個資源對應的 API 可能是由不同人在不同時間完成的,所以需要有個約定好的公共字段的命名,不然到最后會出現混亂的情況。比如資源需要有個 id 表示,就會出現不同資源有的叫 uuid,有的叫 id,有的叫 resource_id;創建時間又有 created_at,created_datetime,created_time,分頁又有 page_token, next_page, page_num 諸如此類不一致的情況。同樣一些字段的類型也需要統一,比如時間使用時間戳還是標準時間,帶不帶時區。 除了正常的返回就是錯誤返回也要有統一的格式。錯誤信息需要包含 error_code, error_message 和 error_detail。其中 error_message 是用來返回給最終用戶的,而 error_detail 則用于內部人源進行錯誤排查。 REST 的缺陷 盡管 REST API 的設計原則在現實中使用的很廣,但是這種設計也是有很多局限性的。 1. 層級設計很容易過多,比如在公有 IAAS 中需要獲得一 private_ip 的信息,這個 ip 可能是屬于某塊網卡的,網卡屬于某個機器,機器屬于某個 subnet,subnet 屬于某個 vpc,vpc 屬于某個 region。設計出來的 api 就變成了 /regions/region_id/vpcs/vpc_id/subnets/subnet_id/instances/instance_id/nics/nics_id/private_ips/private_ip_id 這種 API 直接上就會覺得不合理。而且仔細考慮機器是不是應該屬于 subnet, private_ip 是不是應該直接是 subnet 的子資源,會有很多需要考慮的地方。 2. 標準 HTTP 方法大多是操縱整個資源,而這個粒度在一些情況下太粗了。比如同樣是更新一個主機的操作,更新主機的一個 tag 和更新主機的 name 都是 PUT 一個資源實例 url,而更改安全組將主機狀態設置成停止也是同樣的 PUT 方法和 url 就會有些不合理。盡管都是更新操作,但是更新的字段不同,功能不同,重要性也不同,用同樣的大 PUT 就會不合適。而且這些字段可能會對應著不同的權限操作,如果任何更新都是同樣的方法,之后細粒度的權限控制也會變得十分痛苦。 3. 涉及多資源的操作會很難設計。REST 的思想是對資源進行操作,但現實中一個操作可能會對應多個資源,這時候對于多個不同資源關系的操作 REST 設計起來就比較頭疼了。比如給一個主機加一個 eip,那么這個操作肯定要用到自定義方法了,接下來的問題就是這個方法到底是屬于主機的,還是屬于 eip 的還是兩個資源都需要加一個同樣的方法?而且 API 的設計也會影響實現的思路,在設計時 API 是把主機和 eip 當成獨立的資源,那么實現的時候很可能就忽略了他們倆之間是有關聯關系的,很可能到最后就會出問題。 4. 批量操作變得困難。由于 REST API 的更新和刪除 url 路徑都是指向具體一個資源的,批量的更新刪除就需要額外進行設計。比如批量關閉幾個主機,API 的樣子就會和其他的完全不一樣。如果不設計批量操作的 API 只是組合使用單個資源的 API,當需要操作的實例多的時候很容易出現性能問題,而且如果操作時間過長,那么中間各種沖突,不一致的幾率就會大幅上升。 Google 的應對方案 1. 層級多的問題,翻一下 google cloud 的 api 可以發現大部分的 api url 只有三層,前兩層還都是 project 和 zone,一個實例的 url 就是 /v1/projects/project/zones/zone/instances/instance 換句話說就是真正到了云里面的資源就沒有再分層了,所有的資源層級都是扁平的在同等地位,所有的資源都是關聯關系而不是層級關系。這樣就避免了層級過深和考慮如何設計層級的問題,也保證了將來每個資源操作的靈活性。 2. 大量的自定義方法。盡管 REST 本來的目的是大量的資源少量的標準操作,很顯然這個顯示環境中是有困難的。Google 在 instance 這個資源的自定義方法就有 20 多種,最后的情況就是大量的資源加上幾個標準方法再加上大量的自定義方法。通過自定義方法,每個方法對應一個操作,權限控制也可以對應到每一個方法上,細粒度的權限控制也就可以實現。 3. HTTP BATCH 方法。至于批量操作的問題 Google 是通過自定義 HTTP 方法實現的,之前說過了不要用 自定義 HTTP 方法因為大家不支持,可是 Google 這種能對 Web 標準起到影響的企業就任性了。BATCH 方法其實就是在 HTTP 的 body 里再封裝多個標準的 HTTP 方法求情。這樣批量操作還是一個個的操作,不過可以一次性打包發過去不用一個個發送了。API 也不用單獨再設計一套批量的,還能組合不同類型的操作,不過一般企業就不要學這種作風了。 盡管 Google 很詳細的做了 API 設計的文檔,但是如果你去看國內 IaaS 廠商的 API 文檔會發現沒有一家是這么做的,甚至連一點 REST 的模樣都沒有。Google 的 API 設計看上去最符合開發者的常規思維,在這個領域反而顯得和一個另類一樣,為啥子呢?如果你看過 AWS 的 API 就會發現國內這些廠商大概都是從 AWS 那邊借(chao)鑒(xi)過來的。至于 AWS 是如何應對 REST 世界中的類似問題以及他到底長什么樣子,可能下篇會寫。 短網址api的設計就是借鑒了這篇文章。
掃描二維碼推送至手機訪問。
版權聲明:本文由短鏈接發布,如需轉載請注明出處。
本文鏈接:http://www.virginiabusinesslawupdate.com/article_243.html