翻譯自 API Design Guide - Compatibilitynode
本章提供了有關版本控制部分中給出的破壞和保持兼容性修改的詳細說明。api
並不老是絕對清楚什麼是不兼容的修改,這篇指南 應該(should) 被當成參考性的,而不是覆蓋到全部狀況。安全
下面列出的這些規則只涉及客戶端兼容性,默認 API 做者瞭解部署(包括實現細節的變化)的需求。ide
通常的目標是服務端升級 minor 或 patch 不能影響客戶端的兼容性:測試
代碼兼容:針對 1.0 編寫的代碼在 1.1 上編譯失敗ui
二進制兼容:針對 1.0 編譯的代碼與 1.1 客戶端的庫連接/運行失敗(具體的細節依賴客戶端,不一樣狀況有不一樣變化)google
協議兼容:針對 1.0 構建的程序與 1.1 服務端通訊失敗加密
語義兼容:全部組件都能運行但產生意想不到的結果翻譯
簡而言之:舊的客戶端應該與相同 major 版本的新服務端正常工做,而且可以輕鬆地升級到新的 minor 版本。版本控制
因爲客戶端使用了自動生成和手寫的代碼,除了理論上的基於協議的考慮,還有一些實際的問題。經過生成新版本的客戶端庫來測試你的修改,並保證測試經過。
下面的討論將 proto 信息分爲三類:
請求信息(例如 GetBookRequest
)
響應信息(例如 ListBooksResponse
)
資源信息(例如 Book
,包括在其餘資源消息中使用的任何消息)
這三類有不一樣的規則,例如請求信息只會從客戶端發送到服務端,響應信息只會從服務端發送到客戶端,但資源信息通常會在二者之間互相發送。尤爲是可被修改的資源須要根據讀取/修改/寫入的循環來考慮。
從協議的角度看,這種修改老是安全的。惟一須要考慮的是客戶端庫可能已經經過手寫的代碼使用了新 API 接口的名字。若是新接口與其它徹底正交,這種狀況不太可能發生。若是是已存接口的簡化版本,則極可能引發衝突。
除非添加了一個與現有客戶端庫中方法衝突的方法,這種修改沒有問題。
一個會破壞兼容性的例子:若是有 GetFoo
方法,C# 代碼生成器已經建立了 GetFoo
和 GetFooAsync
方法。所以從客戶端角度來看,在 API 接口中添加 GetFooAsync
方法將會破壞兼容性。
假設綁定沒有引入任何歧義,使服務端響應之前被拒絕的 URL 是安全的。當將現有操做應用於新的資源名稱時,可能(may) 會這樣作。
添加請求字段能夠是兼容的,只要不指定該字段的客戶端在新版本中與舊版本表現相同。
會致使錯誤的最明顯例子是分頁:若是 API 的 v1.0 版本不支持,除非 page_size
默認值是無窮大(這樣是很差的)才能在 v1.1 中加入分頁。不然 v1.0 的客戶端本來但願經過一次請求取得全部結果,但實際只能取到一部分。
只要不改變其餘響應字段的行爲,就能夠擴展不是資源的響應消息(例如ListBooksResponse),而不會破壞兼容性。即便致使冗餘,任何在舊的響應消息中的字段也應該存在於新的響應中並保持它原來的語義。
例如,1.0 中的一個查詢請求的響應有 bool 類型的字段 contained_duplicates
來指示由於重複而忽略掉的結果。在 1.1 中,咱們在 duplicate_count
字段中提供更詳細的信息,儘管從 1.1 版原本看是多餘的,但 contained_duplicates
字段 必須(must) 要保留。
只在請求信息中使用的枚舉類型能夠自由擴展來添加新元素。例如,使用資源視圖時,新的視圖可以添加到新 minor 版本中。客戶端歷來不須要接收此枚舉,因此也不須要關心它。
對於資源消息和響應消息,默認假設客戶端應該處理它意識不到的枚舉值。可是 API 做者應該意識到編寫可以正確處理新枚舉值的代碼多是困難的。應該(should) 在文檔中記錄當遇到未知枚舉值時客戶端的指望行爲。
proto3 容許客戶端接收它們不關心的值而且當執行從新序列化消息時會保持值不變,因此這樣就不會打破讀取/修改/寫入循環的兼容性。JSON 格式容許發送數值,其中該值的「名稱」是未知的,可是服務端一般不會知道客戶端是否真正知道特定值。所以 JSON 客戶端可能知道它們已經收到了之前對他們未知的值,但他們只會看到名稱或數字而不是兩個都有。在讀取/修改/寫入循環中將相同的值返回給服務端不該該修改這個值,由於服務端應該理解這兩種形式。
能夠(may) 添加僅由服務端提供的資源實體中的字段。服務端 能夠(may) 驗證請求中的值是否有效,可是若是該值被省略則 必定不能(must not) 失敗。
從根本上說,若是客戶端代碼使用了某些字段,那麼刪除或重命名它將會破壞兼容性,而且 必須(must) 增長 major 版本號。引用舊名稱的一些語言(如 C# 和 Java)在編譯時會失敗, 另外一些語言會引發運行時異常或數據丟失。協議格式的兼容性在這裏是可有可無的。
這裏的修改
實際指刪除
和添加
。例如,你想要支持 PATCH,但已發佈的版本支持 PUT,或者已經使用了錯誤的自定義動詞,你 能夠(may) 添加新的綁定,可是 必定不要(must not) 移除舊的,由於和刪除服務的方法同樣會破壞兼容性。
儘管新類型是協議兼容的,可以改變客戶端庫自動生成的代碼,所以 必須(must) 要升級 major 版本。會致使須要編譯的靜態類型的語言在編譯期就發生錯誤。
資源 必定不能(must not) 修更名字-這意味着集合名不能被修改。
不像其餘大多數破壞兼容性的修改,這會影響 major 版本號:若是客戶端指望使用 v2.0 訪問在 v1.0 中建立的資源(或反過來),則應該在兩個版本中使用相同的資源名稱。
對資源名的驗證也 不該該(should not) 改變,緣由以下:
若是驗證變嚴格,以前成功能請求如今可能會失敗
若是比以前文檔中記錄的驗證要寬鬆,依據以前文檔的客戶端可能會被破壞。客戶端極可能在其餘地方保存了資源名,而且對字符集和名字的長度敏感。或者,客戶端可能會執行本身的資源名稱驗證來保持與文檔一致。(例如,當開始支持 EC2 資源的長 ID 時,亞馬遜向用戶發出了許多警告並提供了遷移的時間)
請注意這樣的修改只能在 proto 的文檔中可見。所以當評審 CL 時審查除註釋外的修改是不夠的。
客戶端老是依賴 API 的行爲和語義,即便沒有明確支持或記錄此行爲。由於在大多數狀況下修改 API 的行爲和語義在客戶端看來是破壞性的。若是某行爲不是加密隱藏的,你 應該(should) 假設用戶已經依賴它了。
由於這個緣由加密分頁 token 是個好主意,以防止用戶建立本身的 token,以及防止當 token 行爲發生變化時可能帶來的不兼容性。
除了上面列出的資源名稱的變化,這裏還要考慮兩種類型的修改:
自定義方法名:雖然不是資源名稱的一部分,但自定義方法名稱是 REST 客戶端 POST 請求 URL 的一部分。更改自定義方法名稱不該該破壞 gRPC 客戶端,可是公共 API 必須假定它們具備 REST 客戶端。
資源參數名:從 v1/shelves/{shelf}/books/{book}
到 v1/shelves/{shelf_id}/books/{book_id}
的修改不會影響替代的資源名稱,但可能會影響代碼生成。
客戶端會常常執行讀取/修改/寫入的操做。大多數客戶端不支持它們意識不到的字段值,特別是 proto3 不支持。你能夠指定任意消息類型(而不是原始類型)中缺失的字段表示更新時不會被修改,但這樣使刪除這樣的字段變的困難。原始類型(包括 string
和 bytes
)不能簡單地使用這種方法,由於明確地設置 int32
的值爲 0 和不對它設置值在 proto3 中並無區別。
使用字段掩碼來進行全部更新操做不會有問題,由於客戶端不會隱式覆蓋其不知道的字段。然而這是一個不尋常的決定,由於大部分 API 容許所有資源被更新。