在全面了解了REST的概念之後,終於要進入實戰,來嘗試看看怎麼把這些原則應用到Web API的設計中。過程中,「資源命名」是一個很重要的環節,另外就是設計流程脈絡是什麼。這邊我們直接站在Google的肩膀上,直接遵循Google的RESTful API規範,來達到如何有效地命名資源、合理使用HTTP方法,還有怎麼處理版本控制、錯誤信息等等。
一開始在設計RESTful API時,我們要如何起步? Google設計指南中,在設計RESTful API會具體採用以下思考步驟
/users
或 /orders
。id
、name
和 email
等屬性)在理解設計流程和思維脈絡之後,我們接下來要討論如何定義資源集合與資源的結構規範,這在 API 設計中扮演著關鍵角色。資源名稱 是每個資源在系統中的唯一識別符,因此其設計直接影響 API 的可讀性、可擴展性以及一致性。
如果資源名稱的設計不夠清晰或不一致,將會大大增加開發者使用 API 的難度,並且對系統的維護和擴展造成障礙。因此,清楚地定義資源名稱 是確保 API 正確運作的基礎。為此,Google 制定了一套嚴謹的資源名稱規範,以確保開發者在設計 API 時能夠保持結構的一致性,從而提升系統的可維護性和可操作性。
「集合」 是一種特殊的資源,它不是單獨的一個物件或實體,而是包含同類型的子資源列表。舉例來說,目錄是一種集合資源,而目錄中的文件則是目錄的子資源。集合的唯一識別符被稱為 集合 ID。
資源名稱是由 集合 ID
和 **資源 ID**
組成,並以分層方式排列,透過正斜槓(/
)分隔。如果一個資源包含其他子資源,那麼子資源的名稱則由父資源名稱與子資源 ID 組成,同樣使用正斜槓進行分隔。
範例 1:儲存服務中的資源名稱
//storage.googleapis.com/buckets/bucket-id/objects/object-id
//storage.googleapis.com
/buckets
/bucket-id
/objects
/object-id
範例 2:電子郵件服務中的資源名稱
//mail.googleapis.com/users/name@example.com/settings/customFrom
//mail.googleapis.com
/users
/name@example.com
/settings/customFrom
API 提供者可以根據需求,為資源和集合 ID 自行定義值,只要這些值在資源層次結構中保持唯一性。當我們需要提取集合 ID 或資源 ID 時,可以透過簡單的字串分割操作(如 name.split("/")
)來取得各個段落。
完整的資源名稱不包含傳輸協議,通常由 DNS 兼容的 API 服務名稱
和 **資源路徑**
組成。資源路徑也被稱為 相對資源名稱
。例如:
"//library.googleapis.com/shelves/shelf1/books/book2"
//library.googleapis.com
/shelves/shelf1/books/book2
資源 ID 是用來唯一標識一個特定資源的識別符,通常由一個或多個非空的 URI 段 組成。這些 URI 段彼此之間用正斜槓(/
)分隔,並且每個段都代表資源層次結構中的一部分。每個資源必須有唯一的資源 ID,這樣 API 才能準確識別和操作特定資源。
資源 ID組成
資源 ID 可以由多個段組成,且每一段通常對應一個具體的子資源或更深層的結構。例如,一個檔案系統中的檔案路徑可以視為資源 ID,因為它能唯一標識該檔案在系統中的位置。範例:
集合 ID | 資源 ID |
---|
| 檔案 | source/py/parser.py |
source/py/parser.py
是資源 ID,這個路徑中的每個段(source
、py
、parser.py
)都對應檔案系統的不同層次。
資源 ID 的設計要求
集合 ID 用來標識其父資源中的集合,並且必須遵循以下規範:
rowValues
比 values
更具體。在了解了資源名稱與結構規範後,我們進一步探討如何有效設計 API 的操作方式。為了提升 API 的一致性和降低設計複雜度,Google 針對常見的操作方法制定了統一的標準,這些方法涵蓋了最常見的 CRUD(Create、Read、Update、Delete)操作。透過這些標準方法,開發者能以更簡潔且統一的方式與 API 進行互動,同時也確保了系統設計的可預測性與維護性。接下來,我們將深入介紹這些 標準方法。
以下是常見的標準方法及其對應的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協議,這個錯誤模型都能提供簡單易懂且可擴展的錯誤資訊。
google.rpc.Code
中。這些錯誤代碼與HTTP狀態碼類似,用於標示錯誤類別,例如INVALID_ARGUMENT
(對應400 Bad Request)或NOT_FOUND
(對應404 Not Found)。錯誤模型範例,當API出現錯誤時,會返回類似以下的JSON錯誤回應:
404
表示該資源(用戶)未找到。{
"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
中,常見的有:
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的穩定性和功能成熟度。
v1alpha
或v1beta
。Alpha版和Beta版功能可能會隨時變動,最終會合併到穩定版本中。穩定版:當API達到穩定狀態時,主要版本號可以是如v1
這樣的穩定版號,代表此版本不會再進行重大變更。
Google的版本控制與傳統的語義版本控制(Semantic Versioning)不同。語義版本控制通常包括三個部分:主要版本號(Major)、次要版本號(Minor)、補丁版本號(Patch)。然而,Google API只公開主要版本號,如v1
,並不使用次要或補丁版本號(如v1.0
或v1.1
)。這樣的做法確保了版本控制的簡潔性和穩定性,並避免了頻繁的版本更新影響使用者。
在一段過渡期內,API的不同版本應該能夠同時存在,讓開發者能夠逐步過渡到新版本。例如,v1
和v2
可以在同一個應用中同時運行,這樣用戶可以有時間更新其程式碼來適應新版本。當一個版本即將被棄用時,應提供足夠的通知(例如180天),以讓使用者有時間完成過渡。
當API中的某個功能被標記為已棄用時,開發者應避免在新程式碼中使用該功能,並逐步將其移除。API提供方會根據一定的通知期,逐步停止棄用功能的支持。通常:
API棄用時,應使用清晰的註釋來標記相關功能。例如:
message Scroll {
option deprecated = true;
// Deprecated: Use the `Book` resource instead.
}
我們以一個簡單的商品管理系統為例,來嘗試Google設置指南的步驟思考
首先,需要確定API中需要管理的資源。在商品管理系統中,最核心的資源就是商品(Product)。除此之外,商品通常會隸屬於某個分類,因此我們還需要一個**分類(Category)**資源。
主要資源:
接下來,定義資源之間的關係和命名。根據RESTful API設計的原則,我們會使用複數形式來表示資源集合,並且讓每個資源擁有唯一的標識符(ID)。
/products
/products/{productId}
/categories
/categories/{categoryId}
/categories/{categoryId}/products
這樣的命名方式簡單、直觀,並且反映了資源之間的層次關係,例如某個分類下的所有商品可以通過/categories/{categoryId}/products
來訪問。
我們需要為每個資源定義具體的資料結構。以商品(Product)為例,常見的屬性可能包括:
id
:商品的唯一標識符name
:商品名稱price
:商品價格categoryId
:商品所屬分類的ID以下是JSON格式
{
"id": "12345",
"name": "Laptop",
"price": 1200.00,
"categoryId": "1"
}
**分類(Category)**的資料結構則可以包含:
id
:分類的唯一標識符name
:分類名稱以下是JSON格式
{
"id": "1",
"name": "Electronics"
}
接下來,為每個資源附加一組基本的HTTP操作方法(GET、POST、PUT、DELETE),來定義資源的各種操作。
商品資源的操作
範例 : 新增商品, 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/1/products
[
{
"id": "12345",
"name": "Laptop",
"price": 1200.00,
"categoryId": "1"
}
]
在設計API時,正確的錯誤處理對開發者至關重要。根據Google API的錯誤處理模型,使用標準的HTTP狀態碼來表示錯誤,並返回清晰的錯誤訊息,幫助用戶快速定位問題。
常見的錯誤狀態碼:
錯誤回應範例:
當請求的商品ID不存在時,應返回404 Not Found錯誤。
{
"error": {
"code": 404,
"message": "Product not found.",
"details": [
{
"field": "productId",
"issue": "Product with ID '99999' does not exist."
}
]
}
}
錯誤處理邏輯:
price
字段超出範圍時,應返回400錯誤並指出具體問題。{
"error": {
"code": 400,
"message": "Invalid parameter: 'price' must be a positive number.",
"details": [
{
"field": "price",
"issue": "Price must be greater than zero."
}
]
}
}
未經授權的請求(401 Unauthorized):如果客戶端未提供授權憑證或憑證無效,應返回401 Unauthorized。
{
"error": {
"code": 401,
"message": "Missing or invalid API key."
}
}
資源未找到(404 Not Found):當客戶端請求的資源(例如商品ID)不存在時,應返回404 Not Found。
{
"error": {
"code": 404,
"message": "Product not found."
}
}
從v1版本開始。隨著需求的變更,我們可能需要對API進行擴展或調整,例如:
假設我們的商品管理系統API從v1版本開始。隨著需求的變更,我們需要新增一個功能,這個功能涉及到API的字段結構變更,並且不向後兼容,因此我們需要升級到v2版本。
v1 版本(初始穩定版) : 在v1版本中,商品的資源結構簡單,只包括商品的name
和price
字段。
取得商品資訊:範例回應:
GET /v1/products/{productId}
{
"id": "12345",
"name": "Laptop",
"price": 1200.00
}
v2 版本(新增不兼容的字段變更): 在v2版本中,我們對商品結構進行了調整,將price
字段改為unitPrice
,這是一個不兼容的變更,因此我們升級到v2。
取得商品資訊:範例回應:
GET /v2/products/{productId}
{
"id": "12345",
"name": "Laptop",
"unitPrice": 1200.00
}
範例示意兩個重點
從這個範例中,將上述Gooogle提到的規範整個過一遍:保持資源命名清晰、正確處理錯誤以提高可用性,以及通過版本控制來管理不兼容的變更,從而確保API的穩定性與擴展性。這些原則讓我們能夠設計出易於使用且可持續發展的API系統。
雖然我只挑出了API設計中的核心重點來探討,例如資源命名、錯誤處理和版本控制,但Google API規範還涵蓋了標準字段、命名規則、設計模式、文件結構等更多細節。我稍微列一下Google提到的每個項目講的重點核心
這些部分同樣重要,只是由於時間限制無法逐一深入。如果你有時間,建議可以自行查閱相關規範,這將幫助你更全面地掌握API設計的最佳實踐,並打造出更健全的系統。