Google API 設計指南-設計模式

翻譯自 API Design Guide - Design Patternshtml

空響應體

標準的 Delete 方法 必須(must) 返回 google.protobuf.Empty 來實現全局一致性。它還能夠防止客戶端依賴於在重試期間不可用的附加元數據。由於隨着時間推移對於自定義方法, 對於自定義方法,它們 必須(must) 具備本身的 XxxResponse 消息,即便它們是空的,由於功能極可能隨着時間的推移而增長,而且須要返回附加數據。node

範圍字段

表示範圍的字段 應該(should) 使用符合命名約定的半開半閉區間 [start_xxx, end_xxx),例如 [start_key, end_key)[start_time, end_time)。C++ STL 和 Java 標準庫常用半開半閉的語義。API 應該(should) 避免使用表示區間的其餘方法,例如 (index, count)[first, last]git

資源標籤

在面向資源的 API 中,資源結構由 API 定義。爲了容許客戶端向資源附加少許且簡單的元數據(例如將一臺虛擬機資源標記爲數據庫服務器),API 應該(should) 使用在 google.api.LabelDescriptor 中描述的資源標籤設計模式。github

API 應該(should) 在資源定義中添加字段 map<string, string> labelsweb

message Book {
  string name = 1;
  map<string, string> labels = 2;
}

耗時操做

若是一個 API 方法須要花費較長時間運行,能夠將其設計成向客戶端返回一個表示長時間運行的資源,客戶端可經過這個資源來獲取操做的執行進度並取得執行結果。Operation 定義了耗時操做的標準接口。不能(must not) 爲 API 使用自定義的耗時操做接口以避免打破一致性。數據庫

資源 必須(must) 做爲響應消息直接返回,而且對資源操做的結果 應該(should) 反應在 API 中。例如:當建立資源時這個資源 應該(should) 顯示在 LIST 和 GET 方法中,而且 應該(should) 指示出這個資源尚未準備好。若是方法不須要長期執行,當操做完成時 Operation.response 字段應該包含直接返回的消息。編程

列表分頁

即便結果集很小,可 LIST 的集合也 應該(should) 支持分頁。設計模式

理由:儘管向已有的 API 添加分頁功能從 API 的視角來看是純粹的增長功能,但它實際會改變行爲。不知道有分頁功能的已有客戶端會錯誤地將取到的第一頁數據當成所有數據。api

爲了在 List 方法中支持分頁, API 應該(shall)緩存

  • List 方法的請求信息中定義一個 string 字段 page_token。客戶端經過這個字段來請求指定的某一頁。

  • List 方法的請求信息中定義一個 int32 字段 page_size。客戶端經過這個字段來指定返回結果的最大數量。服務端能夠進一步限制在單個頁面中返回的最大結果數量。page_size 是 0 時,將由服務端決定返回結果的數量。

  • List 方法的響應信息中定義一個 string 字段 next_page_token。這個字段表示取得下一頁的頁碼。空字符串表示沒有更多數據了。

爲了取得下一頁的結果,客戶端 應該(shall) 將響應中的 next_page_token 傳入下次的請求:

rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);

message ListBooksRequest {
  string name = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message ListBooksResponse {
  repeated Book books = 1;
  string next_page_token = 2;
}

當客戶端在 query 參數傳入除 page token 以外的參數時,若是 query 參數與 page token 不一致,服務 必須(must) 拒絕此請求。

page token 的內容 應該(should) 是對 web 安全的 BASE64 編碼後的 protocol buffer,這樣就不會有兼容性問題。page token 中存在敏感信息時,應該(should) 將其加密。服務端 必須(must) 經過如下方法來防止經過篡改 page token 來獲取敏感信息的問題:

  • 根據後續請求指定 query 參數

  • 在 page token 中僅引用服務端的狀態

  • 在 page token 中加密並簽名 query 參數,而且在每次調用中對這些參數進行驗證和鑑權

分頁功能也 能夠(may) 在響應中經過名爲 total_size 類型爲 int32 的字段來提供查詢資源的總數量。

列出子集合

API 有時須要客戶端對子集合進行 List/Search 操做。例如一個 API 有書架集合,每一個書架有的集合,客戶端想要在全部書架中搜索一本書。這種狀況下推薦在子集合上使用標準的 List,而且爲父集合指定通配符 "-"。例如:

GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx

注意:使用 "-" 而非 "*" 是爲了不 URL 轉義。

從子集合中取得惟一資源

有時子集合中的資源具備在其父集合內惟一的標識符,在這種狀況下經過 Get 來取得某資源而不須要知道它的父集合多是有用的。在這種狀況下,建議使用標準 Get,併爲資源惟一的全部父集合指定通配符 "-"。例如:

GET https://library.googleapis.com/v1/shelves/-/books/{id}

響應 必須(must) 使用資源的帶有父集合標識符的規範名稱。例如上面的請求應該返回名稱相似 shelves/shelf713/books/book8141 的資源,而不是 shelves/-/books/book8141

排序

若是 API 方法容許客戶端指定列表結果的排序順序,請求消息中 應該(should) 包含以下字段:

string order_by = ...;

這個值 應該(should) 遵循 SQL 語法:用逗號分隔的字段列表。例如:"foo,bar"。默認升序排列。應該(should) 給字段添加後綴 " desc" 來表示降序。例如:"foo desc,bar"

多餘的空格能夠忽略,"foo,bar desc"" foo , bar desc "是相等的。

請求校驗

若是 API 方法有反作用,而且須要僅驗證請求而不產生反作用,請求消息 應該(should) 包含一個字段:

bool validate_only = ...;

當此字段設置爲 true 時,服務端 必定不要(must not) 執行任何有反作用的操做,而是對請求進行校驗。

校驗成功時 必定(must) 要返回google.rpc.Code.OK,而且使用相同請求信息的完整請求 不該該(should not) 返回 google.rpc.Code.INVALID_ARGUMENT。注意,可能由於其餘錯誤(好比 google.rpc.Code.ALREADY_EXISTS 或競態條件)此請求仍是會失敗。

請求重入

對於網絡 API,冪等是很重要的,由於當有網絡異常時它們可以安全地進行重試。然而一些 API 並不容易實現冪等性,例如須要避免沒必要要重複的建立資源操做。對於這類狀況,請求信息 應該(should) 包含一個惟一 ID(例如 UUID),這樣服務端可以經過此 ID 來檢測重複,保證請求只被處理一次。

// 服務端用於檢測重複請求的惟一 ID
// 此字段應該命名爲 `request_id`
string request_id = ...;

由於客戶端極可能沒有接收到以前的響應,因此當檢測到重複請求後,服務端 應該(should) 返回以前成功的響應。

枚舉默認值

每一個枚舉定義 必須(must)0 值開始,用於當枚舉值沒有明確指定時。API 必須(must) 在文檔中說明如何處理 0 值。

若是有通用的默認行爲,應該(should) 使用枚舉值 0。API 應該在文檔中說明期待的行爲。

若是沒有通用的默認行爲,枚舉值 0 應該(should) 命名爲 ENUM_TYPE_UNSPECIFIED 而且和錯誤 INVALID_ARGUMENT 一塊兒使用。

enum Isolation {
  // 未指定
  ISOLATION_UNSPECIFIED = 0;
  // 快照讀。若是全部讀寫都不能在併發事務中邏輯地序列化,則會發生衝突
  SERIALIZABLE = 1;
  // 快照讀。併發事務向同一行寫入時致使衝突
  SNAPSHOT = 2;
  ...
}

// 當未指定時,服務器將使用 SNAPSHOT 或更高的隔離級別
Isolation level = 1;

一個慣用名稱 能夠(may) 用於 0 值,例如,google.rpc.Code.OK 是指定不存在錯誤的慣用方法。在這種狀況下,OK 與枚舉類型中的 UNSPECIFIED 在語義上是相等的。

在存在本質上合理和安全的默認狀況下,能夠(may) 使用 0 值。例如,在[資源視圖]()枚舉中 BASIC0 值。

語法句法

在某些 API 設計中,有必要爲某些數據格式定義簡單的語法,例如可接受的文本輸入。爲了在不一樣 API 中提供一致的開發體驗和減小學習曲線,API 設計者 必須(must) 使用 ISO 14977 擴展的 Backus-Naur 表格(EBNF)句法來定義這些語法。

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

注意:TOKEN 表示在語法以外定義的終端。

整數類型

在API 設計中,不該該(should not) 使用像 uint32fixed32 這種無符號整型,這是由於一些重要的編程語言和系統(例如 Java, JavaScript 和 OpenAPI)不能很好地支持它們而且更容易致使溢出的問題。另外一個問題是,不一樣的 API 極可能對同一個資源使用不匹配的有符號和無符號類型。

在大小和時間這種負數沒有意義的類型中 能夠(may) 使用且僅使用 -1 來表示特定的意義,例如到在文件結尾(EOF)、無窮的時間、無資源限額或未知的年紀。當這樣使用負數時,必須(must) 在文檔中明確說明以防止混淆。API 生成器也應該在文檔中記錄隱式默認值 0 表示的行爲。

部分響應

客戶端有時只須要響應信息中的特定子集。一些 API 平臺提供了對部分響應的原生支持。Google API 平臺經過響應字段掩碼來提供支持。對於任一 REST API 調用,有一個隱式的系統 query 參數 $fields,它是 google.protobuf.FieldMask 的 JSON 表示。在返回給客戶端以前,響應消息會被 $fields 字段過濾。此行爲是在 API 平臺自動執行的。

GET https://library.googleapis.com/v1/shelves?$fields=name

資源視圖

爲了減小網絡流量,容許客戶端限制服務器在其響應中返回的資源的哪些部分是有用的,返回資源的視圖而不是所有資源表示。API 中的資源視圖是經過向請求添加參數來實現的,該參數容許客戶端在響應中指定要接收資源的哪一個視圖。

此參數:

  • 應該(should) 是枚舉類型

  • 必須(must) 命名爲 view

枚舉中的每一個值定義了資源的哪部分(字段)在響應中會被返回。文檔中 應該(should) 明確記錄每一個 view 值會返回什麼。

package google.example.library.v1;

service Library {
  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*}/books"
    }
  };
}

enum BookView {
  // 響應中只包含做者、標題、ISBN 和惟一的圖書 ID。這是默認值。
  BASIC = 0;

  // 返回全部信息,包括書中的內容
  FULL = 1;
}

message ListBooksRequest {
  string name = 1;

  // 指定返回圖書資源的哪些部分
  BookView view = 2;
}

對應的 URL:

GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC

能夠在 標準方法 一章中查看更多關於方法定義、請求和響應的內容。

ETag

ETag 是一個不透明的標識符,容許客戶端進行條件請求。爲了支持 ETag,API 應該(should) 在資源定義中包含一個字符串字段 etag,它的語義 必須(must) 與 ETag的經常使用用法相匹配。一般,etag 包含由服務器計算出的資源指紋。更多詳細信息,請參閱維基百科RFC 7232

ETags 能夠強驗證或弱驗證,其中弱驗證的ETag 以 W / 爲前綴。在這種狀況下,強驗證意味着具備相同 ETag 的兩個資源具備相同的內容和相同的額外字段(Content-Type)。這意味着強驗證的 ETag 容許緩存稍後組裝的部分響應。

相反,具備相同弱驗證 ETag 值的資源意味着這些表示在語義上是等效的,但不必定每字節都相同,所以不適合於字節範圍請求的響應緩存。

// 強驗證的 ETag(包含引號)
"1a2f3e4d5b6c7c"
// 弱驗證的 ETag(包含前綴和引號)
W/"1a2b3c4d5ef"

輸出字段

API 可能但願將由客戶端提供的字段和只由服務端在特定資源上返回的字段進行區分。對於僅輸出的字段,必須(shall)記錄字段屬性。

請注意,若是客戶端在請求中設置了僅輸出(output only)字段,或者客戶端使用僅輸出字段指定了一個 google.protobuf.FieldMask,則服務器 必須(must) 接受該請求而不能出錯。這意味着服務器 必須(must) 忽略僅輸出字段的存在及其任何指示。這個建議的緣由是由於客戶端一般會將服務器返回的資源重用爲另外一個請求的輸入,例如一個獲取到的 Book 將在 UPDATE 方法中被再次使用。若是要驗證僅輸出字段,客戶端須要作清除輸出字段的額外工做。

message Book {
  string name = 1;
  // 只用作輸出
  Timestamp create_time = 2;
}

查看其餘章節

相關文章
相關標籤/搜索