iT邦幫忙

2024 iThome 鐵人賽

DAY 18
2
Software Development

微服務奇兵:30天Quarkus特訓營系列 第 18

後端協定基礎知識建置-Google RESTful API設計

  • 分享至 

  • xImage
  •  

在全面了解了REST的概念之後,終於要進入實戰,來嘗試看看怎麼把這些原則應用到Web API的設計中。過程中,「資源命名」是一個很重要的環節,另外就是設計流程脈絡是什麼。這邊我們直接站在Google的肩膀上,直接遵循Google的RESTful API規範,來達到如何有效地命名資源、合理使用HTTP方法,還有怎麼處理版本控制、錯誤信息等等。

設計流程脈絡

一開始在設計RESTful API時,我們要如何起步? Google設計指南中,在設計RESTful API會具體採用以下思考步驟

  1. 確定API提供的資源類型 : 先了解你的API需要處理哪些資源,例如用戶、商品或訂單。這些資源是API的核心,也是設計的基礎。
  2. 確定資源之間的關係:明確資源之間的層次關係。例如,訂單屬於用戶,所以會有「用戶」與「訂單」之間的關聯。
  3. 命名資源:根據資源的類型和它們的關係來定義資源名稱。資源名稱應該清楚、簡潔,並遵循一致的命名規範,例如使用複數形式來表示集合資源,如 /users/orders
  4. 定義資源資料架構:決定每個資源的資料結構,包括資源的屬性(例如「用戶」可能有 idnameemail 等屬性)
  5. 附加基本操作方法:為資源設計一組基本的操作方法,這些方法通常使用標準的HTTP動詞來處理,例如:
    • GET 用來讀取資源
    • POST 用來新增資源
    • PUTPATCH 用來更新資源
    • DELETE 用來刪除資源

資源名稱

在理解設計流程和思維脈絡之後,我們接下來要討論如何定義資源集合與資源的結構規範,這在 API 設計中扮演著關鍵角色。資源名稱 是每個資源在系統中的唯一識別符,因此其設計直接影響 API 的可讀性、可擴展性以及一致性。

如果資源名稱的設計不夠清晰或不一致,將會大大增加開發者使用 API 的難度,並且對系統的維護和擴展造成障礙。因此,清楚地定義資源名稱 是確保 API 正確運作的基礎。為此,Google 制定了一套嚴謹的資源名稱規範,以確保開發者在設計 API 時能夠保持結構的一致性,從而提升系統的可維護性和可操作性。

集合與資源名稱結構

「集合」 是一種特殊的資源,它不是單獨的一個物件或實體,而是包含同類型的子資源列表。舉例來說,目錄是一種集合資源,而目錄中的文件則是目錄的子資源。集合的唯一識別符被稱為 集合 ID

資源名稱是由 集合 ID 和 **資源 ID** 組成,並以分層方式排列,透過正斜槓(/)分隔。如果一個資源包含其他子資源,那麼子資源的名稱則由父資源名稱與子資源 ID 組成,同樣使用正斜槓進行分隔。

範例 1:儲存服務中的資源名稱

//storage.googleapis.com/buckets/bucket-id/objects/object-id

  • API 服務名稱: //storage.googleapis.com
  • 集合 ID: /buckets
  • 資源 ID: /bucket-id
  • 子集合 ID: /objects
  • 子資源 ID: /object-id

範例 2:電子郵件服務中的資源名稱

//mail.googleapis.com/users/name@example.com/settings/customFrom

  • API 服務名稱: //mail.googleapis.com
  • 集合 ID: /users
  • 資源 ID: /name@example.com
  • 子資源 ID: /settings/customFrom

API 提供者可以根據需求,為資源和集合 ID 自行定義值,只要這些值在資源層次結構中保持唯一性。當我們需要提取集合 ID 或資源 ID 時,可以透過簡單的字串分割操作(如 name.split("/"))來取得各個段落。

完整的資源名稱

完整的資源名稱不包含傳輸協議,通常由 DNS 兼容的 API 服務名稱 和 **資源路徑** 組成。資源路徑也被稱為 相對資源名稱。例如:

"//library.googleapis.com/shelves/shelf1/books/book2"
  • API 服務名稱: //library.googleapis.com
  • 資源路徑: /shelves/shelf1/books/book2

資源 ID

資源 ID 是用來唯一標識一個特定資源的識別符,通常由一個或多個非空的 URI 段 組成。這些 URI 段彼此之間用正斜槓(/)分隔,並且每個段都代表資源層次結構中的一部分。每個資源必須有唯一的資源 ID,這樣 API 才能準確識別和操作特定資源。

  • 資源 ID組成

    • 資源 ID 可以由多個段組成,且每一段通常對應一個具體的子資源或更深層的結構。例如,一個檔案系統中的檔案路徑可以視為資源 ID,因為它能唯一標識該檔案在系統中的位置。範例:

      集合 ID 資源 ID

      | 檔案 | source/py/parser.py |

      source/py/parser.py 是資源 ID,這個路徑中的每個段(sourcepyparser.py)都對應檔案系統的不同層次。

  • 資源 ID 的設計要求

    • 符合 URL 規範 : 資源 ID 需要能夠無縫地嵌入到 URL 中,不應包含特殊字元(如空格、引號等)
    • 唯一性:資源 ID 必須在整個系統中唯一。

集合 ID 的命名規範

集合 ID 用來標識其父資源中的集合,並且必須遵循以下規範:

  • 必須是有效的 C/C++ 識別符 (兼容性考量)
  • 必須使用小駝峰命名法的複數形式。例如,如果詞語無法形成合適的複數(如 evidence 或 weather),則使用單數形式。
  • 應使用簡短明確的英文單詞。
  • 應避免使用過於籠統的詞語,或加以限定。例如,rowValuesvalues 更具體。

標準方法

在了解了資源名稱與結構規範後,我們進一步探討如何有效設計 API 的操作方式。為了提升 API 的一致性和降低設計複雜度,Google 針對常見的操作方法制定了統一的標準,這些方法涵蓋了最常見的 CRUD(Create、Read、Update、Delete)操作。透過這些標準方法,開發者能以更簡潔且統一的方式與 API 進行互動,同時也確保了系統設計的可預測性與維護性。接下來,我們將深入介紹這些 標準方法

標準方法與HTTP行為Mappinng

以下是常見的標準方法及其對應的HTTP動詞與行為:

標準方法 HTTP 映射 HTTP 請求內容 HTTP 回應內容
List GET <集合 URL> 資源列表
Get GET <資源 URL> 資源
Create POST <集合 URL> 資源 資源
Update PUT 或 PATCH <資源 URL> 資源 資源
Delete DELETE <資源 URL> 不適用 google.protobuf.Empty
  • List 方法:使用 GET 動詞,用於查詢集合資源,列出集合內的所有資源。

    # 請求格式
    GET /v1/{parent=shelves/*}/books
    # 範例
    GET /v1/shelves/shelf1/books?page_size=10
    # 回應格式
    {
      "books": [
        { "id": "book1", "title": "大亨小傳" },
        { "id": "book2", "title": "一九八四" }
      ],
      "next_page_token": "..."
    }
    
  • Get 方法:用來查詢單一資源的詳細資料。它需要指定資源名稱,並返回該資源的完整數據。

    # 請求格式
    GET /v1/{name=shelves/*/books/*}
    #範例
    GET /v1/shelves/shelf1/books/book1
    #回應格式
    {
      "id": "book1",
      "title": "大亨小傳",
      "author": "F. Scott Fitzgerald"
    }
    
  • Create 方法:使用 POST,在父資源下建立一個新的資源,並返回新創建的資源。

    # 請求格式
    POST /v1/{parent=shelves/*}/books
    #範例
    POST /v1/shelves/shelf1/books
    #請求內容
    {
      "title": "麥田捕手",
      "author": "J.D. Salinger"
    }
    #回應格式
    {
      "id": "book1",
      "title": "大亨小傳",
      "author": "F. Scott Fitzgerald"
    }
    
  • Update 方法:用來更新已存在的資源。通常分為兩種:部分更新(PATCH)和完整更新(PUT)。

    # 請求格式
    PATCH /v1/{book.name=shelves/*/books/*}
    #範例
    PATCH /v1/shelves/shelf1/books/book1
    #請求內容
    {
      "title": "大亨小傳(更新版)"
    }
    #回應格式
    {
      "id": "book1",
      "title": "大亨小傳(更新版)",
      "author": "F. Scott Fitzgerald"
    }
    
  • Delete 方法: 用來刪除資源,並返回空回應。如果該方法是非同步的,它可能會返回長時間執行的操作狀態。

    # 請求格式
    DELETE /v1/{name=shelves/*/books/*}
    #範例
    DELETE /v1/shelves/shelf1/books/book1
    #回應格式
    {
    }
    

如果沒有遵循以上標準方法,我們可能會看到這樣的 API 設計:

  • 取得書籍列表GET /v1/shelves/books_list
  • 取得單一本書GET /v1/book_detail/{book_id}
  • 新增書籍NEW /v1/add_book
  • 更新書籍MODIFY /v1/update_book/{book_id}
  • 刪除書籍REMOVE /v1/remove_book/{book_id}

這樣的設計對每一個操作都有不同的動詞和 URL,使用者在學習和使用這個 API 時,需要記住每個操作的動詞和路徑細節,並且這種不一致性會增加錯誤發生的可能。所以在設計 RESTful API 時,Google 強烈建議使用這些標準方法,因為它們能顯著降低 API 的複雜度,並提高 API 的一致性與可維護性。

錯誤處理

在設計RESTful API時,錯誤處理是一個關鍵環節,它不僅能夠幫助開發者識別問題,也能為最終用戶提供友好的錯誤資訊。Google RESTful API的錯誤模型基於google.rpc.Status,這個模型是一個與協議無關的錯誤回應格式,適用於各種API協議(如REST和gRPC)。它的目的是提供一致的錯誤處理方式,無論是RESTful API還是其他API協議,這個錯誤模型都能提供簡單易懂且可擴展的錯誤資訊。

錯誤模型結構

  • code(錯誤代碼):
    • 為一個整數,用來表示錯誤類型,定義於google.rpc.Code中。這些錯誤代碼與HTTP狀態碼類似,用於標示錯誤類別,例如INVALID_ARGUMENT(對應400 Bad Request)或NOT_FOUND(對應404 Not Found)。
  • message(錯誤訊息):
    • 開發者面向的可讀錯誤訊息,應該用英語撰寫。訊息應該簡單明瞭地解釋錯誤,並且盡量提供可操作的解決方案。這段訊息可以直接顯示給開發者。
  • details(錯誤詳情):
    • 這是一個可選的欄位,允許提供額外的錯誤資訊,如重試建議、幫助連結等。這些資訊可以幫助客戶端更好地處理錯誤。

錯誤模型範例,當API出現錯誤時,會返回類似以下的JSON錯誤回應:

  • code404表示該資源(用戶)未找到。
  • message:未找到使用者。
  • details:提供了更多具體的錯誤資訊,包括資源的類型、名稱,以及更詳細的錯誤描述。
{
  "error": {
    "code": 404,
    "message": "User not found.",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ResourceInfo",
        "resourceType": "User",
        "resourceName": "users/12345",
        "description": "The specified user was not found."
      }
    ]
  }
}

標準錯誤代碼

Google RESTful API使用一組統一的錯誤代碼來表示不同類型的錯誤,這些代碼被定義在

google.rpc.Code中,常見的有:

  • INVALID_ARGUMENT (400):請求中的參數無效,通常用於資料格式錯誤或不符合API規範。
  • NOT_FOUND (404):客戶端請求的資源不存在。
  • ALREADY_EXISTS (409):創建的資源已經存在。
  • PERMISSION_DENIED (403):客戶端沒有執行該操作的權限。
  • RESOURCE_EXHAUSTED (429):超過了資源的配額或速率限制。
  • INTERNAL (500):伺服器內部錯誤,通常由伺服器端問題引起。

Google RESTful API的錯誤模型提供了一個一致的框架來處理錯誤,無論是錯誤代碼、錯誤訊息還是詳細信息,這個模型都能幫助API使用者快速定位和解決問題。

版本控制策略

在上述聊完後,接著進入在設計和維護API的過程中最重要環節之一,API版本控制。Google的RESTful API採用版本控制策略來確保重大更新不會影響現有的用戶程式碼,並使API的開發和使用更加穩定和可預測。

版本控制的基本原則

  • 主要版本號:Google API必須提供一個主要版本號,這個版本號通常放置在REST API的URI路徑的第一部分。例如:/v1/users。當API引入了不向後兼容的重大變更(例如刪除或重命名字段),必須增加API的主要版本號。

    • 主要版本升級的必要性:例如,從v1升級到v2意味著API的結構或功能發生了重大改變,並且與v1版本不兼容。
  • 穩定性級別:除了主要版本號,API可能會有不同的穩定性級別,如Alpha版、Beta版和穩定版。這些版本標示了API的穩定性和功能成熟度。

    • Alpha版和Beta版:這些版本通常用來測試新功能,並附加版本標籤,如v1alphav1beta。Alpha版和Beta版功能可能會隨時變動,最終會合併到穩定版本中。
  • 穩定版:當API達到穩定狀態時,主要版本號可以是如v1這樣的穩定版號,代表此版本不會再進行重大變更。

語義版本控制與Google的版本策略

Google的版本控制與傳統的語義版本控制(Semantic Versioning)不同。語義版本控制通常包括三個部分:主要版本號(Major)、次要版本號(Minor)、補丁版本號(Patch)。然而,Google API只公開主要版本號,如v1,並不使用次要或補丁版本號(如v1.0v1.1)。這樣的做法確保了版本控制的簡潔性和穩定性,並避免了頻繁的版本更新影響使用者。

版本共存議題

在一段過渡期內,API的不同版本應該能夠同時存在,讓開發者能夠逐步過渡到新版本。例如,v1v2可以在同一個應用中同時運行,這樣用戶可以有時間更新其程式碼來適應新版本。當一個版本即將被棄用時,應提供足夠的通知(例如180天),以讓使用者有時間完成過渡。

API棄用與移除議題

當API中的某個功能被標記為已棄用時,開發者應避免在新程式碼中使用該功能,並逐步將其移除。API提供方會根據一定的通知期,逐步停止棄用功能的支持。通常:

  • Beta版的功能可以在棄用180天後移除。
  • Alpha版功能可能會隨時移除,甚至不需要提前通知。

API棄用時,應使用清晰的註釋來標記相關功能。例如:

message Scroll {
  option deprecated = true;
  // Deprecated: Use the `Book` resource instead.
}

商品管理系統API

我們以一個簡單的商品管理系統為例,來嘗試Google設置指南的步驟思考

Step 1 : 確定資源類型

首先,需要確定API中需要管理的資源。在商品管理系統中,最核心的資源就是商品(Product)。除此之外,商品通常會隸屬於某個分類,因此我們還需要一個**分類(Category)**資源。

主要資源:

  • 商品(Product)
  • 分類(Category)

Step 2 : 資源關係和命名

接下來,定義資源之間的關係和命名。根據RESTful API設計的原則,我們會使用複數形式來表示資源集合,並且讓每個資源擁有唯一的標識符(ID)。

  • 商品集合/products
    • 單個商品:/products/{productId}
  • 分類集合/categories
    • 單個分類:/categories/{categoryId}
    • 某分類下的商品:/categories/{categoryId}/products

這樣的命名方式簡單、直觀,並且反映了資源之間的層次關係,例如某個分類下的所有商品可以通過/categories/{categoryId}/products來訪問。

Step 3: 定義資源結構

我們需要為每個資源定義具體的資料結構。以商品(Product)為例,常見的屬性可能包括:

  • id:商品的唯一標識符
  • name:商品名稱
  • price:商品價格
  • categoryId:商品所屬分類的ID

以下是JSON格式

{
  "id": "12345",
  "name": "Laptop",
  "price": 1200.00,
  "categoryId": "1"
}

**分類(Category)**的資料結構則可以包含:

  • id:分類的唯一標識符
  • name:分類名稱

以下是JSON格式

{
  "id": "1",
  "name": "Electronics"
}

Step 4: 設計HTTP操作方法

接下來,為每個資源附加一組基本的HTTP操作方法(GET、POST、PUT、DELETE),來定義資源的各種操作。

商品資源的操作

  • GET /products:取得所有商品
  • GET /products/{productId}:取得特定商品的詳情
  • POST /products:新增商品
  • PUT /products/{productId}:更新商品資訊
  • DELETE /products/{productId}:刪除商品

範例 : 新增商品, POST /products

  • Request Body:

    {
      "name": "Tablet",
      "price": 600.00,
      "categoryId": "1"
    }
    
    
  • Response:

    {
      "id": "98765",
      "name": "Tablet",
      "price": 600.00,
      "categoryId": "1"
    }
    
    

範例 : 取得所有商品,GET /products

  • Response

    [
      {
        "id": "12345",
        "name": "Laptop",
        "price": 1200.00,
        "categoryId": "1"
      },
      {
        "id": "67890",
        "name": "Smartphone",
        "price": 800.00,
        "categoryId": "1"
      }
    ]
    
    

分類資源的操作

  • GET /categories:取得所有分類
  • GET /categories/{categoryId}:取得特定分類的詳情
  • POST /categories:新增分類
  • PUT /categories/{categoryId}:更新分類資訊
  • DELETE /categories/{categoryId}:刪除分類
  • GET /categories/{categoryId}/products:取得某分類下的所有商品

範例 : 取得所有商品,GET /categories/1/products

  • Response
[
  {
    "id": "12345",
    "name": "Laptop",
    "price": 1200.00,
    "categoryId": "1"
  }
]

Step 5: 錯誤處理

在設計API時,正確的錯誤處理對開發者至關重要。根據Google API的錯誤處理模型,使用標準的HTTP狀態碼來表示錯誤,並返回清晰的錯誤訊息,幫助用戶快速定位問題。

常見的錯誤狀態碼:

  • 400 Bad Request:請求無效,通常是參數錯誤或格式不正確。
  • 401 Unauthorized:未經授權,客戶端需要提供有效的憑證。
  • 403 Forbidden:客戶端無權執行該操作。
  • 404 Not Found:資源不存在。
  • 500 Internal Server Error:伺服器端發生未知錯誤。

錯誤回應範例:

當請求的商品ID不存在時,應返回404 Not Found錯誤。

{
  "error": {
    "code": 404,
    "message": "Product not found.",
    "details": [
      {
        "field": "productId",
        "issue": "Product with ID '99999' does not exist."
      }
    ]
  }
}

錯誤處理邏輯:

  1. 參數錯誤處理(400 Bad Request):當客戶端傳遞了無效的參數,例如price字段超出範圍時,應返回400錯誤並指出具體問題。
{
  "error": {
    "code": 400,
    "message": "Invalid parameter: 'price' must be a positive number.",
    "details": [
      {
        "field": "price",
        "issue": "Price must be greater than zero."
      }
    ]
  }
}

  1. 未經授權的請求(401 Unauthorized):如果客戶端未提供授權憑證或憑證無效,應返回401 Unauthorized。

    {
      "error": {
        "code": 401,
        "message": "Missing or invalid API key."
      }
    }
    
  2. 資源未找到(404 Not Found):當客戶端請求的資源(例如商品ID)不存在時,應返回404 Not Found。

    {
      "error": {
        "code": 404,
        "message": "Product not found."
      }
    }
    
    

Step 6: 版本控制

v1版本開始。隨著需求的變更,我們可能需要對API進行擴展或調整,例如:

  • v1:API初始穩定版。
  • v2:新增了商品批量更新功能,並對部分字段做了變更(不向後兼容)。

假設我們的商品管理系統API從v1版本開始。隨著需求的變更,我們需要新增一個功能,這個功能涉及到API的字段結構變更,並且不向後兼容,因此我們需要升級到v2版本。

v1 版本(初始穩定版) :v1版本中,商品的資源結構簡單,只包括商品的nameprice字段。

  • 取得商品資訊範例回應

    • EndpointGET /v1/products/{productId}
    {
      "id": "12345",
      "name": "Laptop",
      "price": 1200.00
    }
    

v2 版本(新增不兼容的字段變更):v2版本中,我們對商品結構進行了調整,將price字段改為unitPrice,這是一個不兼容的變更,因此我們升級到v2

  • 取得商品資訊範例回應

    • EndpointGET /v2/products/{productId}
    {
      "id": "12345",
      "name": "Laptop",
      "unitPrice": 1200.00
    }
    

範例示意兩個重點

  • v1v2版本可以同時存在,讓使用者有時間逐步遷移到新的版本。
  • 當API中有不向後兼容的變更(如字段名稱修改)時,升級主要版本號(如從v1到v2)是最佳實踐。

從這個範例中,將上述Gooogle提到的規範整個過一遍:保持資源命名清晰、正確處理錯誤以提高可用性,以及通過版本控制來管理不兼容的變更,從而確保API的穩定性與擴展性。這些原則讓我們能夠設計出易於使用且可持續發展的API系統。

雖然我只挑出了API設計中的核心重點來探討,例如資源命名、錯誤處理和版本控制,但Google API規範還涵蓋了標準字段命名規則設計模式文件結構等更多細節。我稍微列一下Google提到的每個項目講的重點核心

  • 基於資源的設計:如何通過API來表示資源,並設計資源與操作的關係。
  • 資源名稱:資源的命名方式,應簡單、易懂且具備一致性。
  • 標準方法與自訂方法:標準HTTP方法(如GET、POST、PUT、DELETE)以及根據需求定義的自訂方法。
  • 標準字段:通用的字段定義,讓API具有更高的一致性和可讀性。
  • 錯誤處理:如何統一返回錯誤資訊,確保開發者能理解並解決問題。
  • 命名規則:資源、操作與字段的命名慣例,確保統一與可讀性。
  • 設計模式:API設計時常見的架構模式與最佳實踐。
  • 文件:API文檔的撰寫方式,確保API使用者能夠清晰理解和使用。
  • 使用proto3:使用Proto3(Protocol Buffers)作為資料定義語言,簡化數據的序列化與傳輸。
  • 版本控制:管理API變更,確保舊版本與新版本的兼容性。
  • 兼容字符:定義API中允許使用的字符集,確保資料的正確傳輸。
  • 目錄結構與文件結構:API源碼與文檔的組織方式,便於維護和擴展。
  • 術語庫:統一的術語定義,讓API中的詞彙具備一致性和可理解性。

這些部分同樣重要,只是由於時間限制無法逐一深入。如果你有時間,建議可以自行查閱相關規範,這將幫助你更全面地掌握API設計的最佳實踐,並打造出更健全的系統。


上一篇
後端協定基礎知識建置-認識RESTful API設計原理
下一篇
開發概念建置-阻塞式 vs 非阻塞式 vs 響應式API
系列文
微服務奇兵:30天Quarkus特訓營30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言