Lesson 8:API 設計哲學-RESTful API 的資源 概念與動詞運用
RESTful API 的核心哲學
REST(Representational State Transfer)是一種架構風格,它不是一個標準,而是一組設計原則。RESTful API 的設計哲學主要圍繞在兩個核心概念:資源(Resources) 和 狀態轉移(State Transfer)。
REST 提倡以 資源(Resource)為中心,透過 HTTP 的既有語意(HTTP verbs)定義操作方式,而不是把 API 設計成模糊的 endpoints。
這和傳統 action-style 的設計有本質差異。
RESTful的六個限制
1. 客戶端 - 伺服器分離 (Client-Server)
這個限制要求客戶端(前端應用程式、手機 App 等)和伺服器(後端 API)必須彼此分離。
目標: 提高可攜性 (Portability) 和可擴展性 (Scalability)。
原則: 伺服器不關心使用者介面 (UI) 的狀態,只負責提供和管理資料資源。客戶端則不關心資料儲存,只負責處理使用者介面和業務邏輯。
2. 無狀態性 (Stateless)
這是 REST 架構中最核心且最重要的限制之一。
目標: 提高可擴展性、可靠性和可見性。
原則: 伺服器不應該儲存任何客戶端的會話狀態 (Session State)。每一次客戶端請求都必須包含所有必要的資訊,讓伺服器能夠完全理解和處理該請求。
實例: 身份驗證(Authentication)和授權(Authorization)資訊(例如 Token)必須在每次請求中明確傳遞。如果伺服器在處理請求後崩潰,客戶端可以安全地向另一台伺服器重試該請求,因為所有資訊都在請求本身。
3. 可快取性 (Cacheable)
為了優化網路效率和使用者體驗,資料應該可以被快取。
目標: 減少客戶端和伺服器之間的交互次數,提高效率和使用者感受到的性能。
原則: 伺服器響應(Response)必須明確標記其資料是否可以被客戶端或中介伺服器(Proxy)快取,以及快取的有效期。如果響應是可快取的,客戶端在下次請求相同資源時可以直接使用本地快取。
4. 統一介面 (Uniform Interface)
這是 REST 架構的基礎,它要求系統使用一個簡單、標準且一致的方式來與資源互動。它又細分為四個子限制:
識別資源 (Identification of Resources): 每個資源都必須有一個唯一的標識符 (Identifier),即 URI (統一資源標識符)。
透過表示層操作資源 (Manipulation of Resources Through Representations): 客戶端從伺服器獲得資源的「表示層」(通常是 JSON 或 XML)。客戶端可以透過修改這個表示層並將其發送回伺服器來改變資源的狀態。
自我描述訊息 (Self-Descriptive Messages): 每個訊息(請求和響應)都必須包含足夠的資訊來描述如何處理它。例如,使用 HTTP 方法(GET, POST, PUT, DELETE)來描述意圖,使用 MIME 類型(
Content-Type)來描述主體格式。超媒體作為應用程式狀態的引擎 (HATEOAS - Hypermedia As The Engine of Application State): 伺服器透過在資源表示層中提供超連結 (Hyperlinks),來指導客戶端接下來可以執行的操作或可以存取的相關資源。客戶端不應該「猜測」URI。理想原則,實務中少數採用。
5. 分層系統 (Layered System)
這個限制允許您在客戶端和伺服器之間添加多個中間層。
目標: 提高可擴展性和安全性。
原則: 系統可以由多個層次組成,例如負載平衡器 (Load Balancers)、快取伺服器 (Cache Servers) 或安全代理 (Security Proxies)。客戶端通常無需知道它正在與系統的哪一層進行通訊。
6. 程式碼隨選 (Code-On-Demand) (可選限制)
這是六個限制中唯一可選的。
目標: 減少客戶端的預先功能需求。
原則: 伺服器可以臨時提供程式碼(例如 JavaScript 小程式或 Applet)給客戶端執行,以擴展客戶端的功能。雖然這在現代 Web 開發中很常見(例如下載前端 JS 程式碼),但在傳統 REST 架構中並非必須。
資源概念 Resources
在RESTful API的概念之中,資源是透過 API 進行操作的任何可識別的實體、資料或服務。
命名慣例:
資源應該使用 名詞(或名詞片語)來命名,而不是動詞,並且通常使用 複數 形式。
例如:
/users(使用者集合)、/products(產品集合)、/orders/{id}(單個訂單)。
識別:
每個資源都有一個唯一的 統一資源識別符 (URI),就像資源的地址。
層次結構:
可以使用 巢狀結構 來表示資源之間的關係。
例如:
/users/{user_id}/orders表示特定使用者擁有的訂單集合。
HTTP 動詞運用 Methods
一旦我們定義了資源,我們就使用 HTTP Method 來告訴伺服器我們想要對這個資源執行什麼樣的 操作。這是 RESTful API 中實現 狀態轉移 的關鍵。
以下是四個最常用的 HTTP 動詞及其對應的 CRUD(Create, Read, Update, Delete)操作:
Create:POST , 在資源集合中建立一個新的資源。改變伺服器狀態;重複呼叫會建立多個資源(Non-Idempotent)
Read:GET , 從伺服器檢索資源或資源集合的表示。不改變伺服器狀態;重複呼叫結果相同(Safe)
Update:PUT , 完全替換 URI 所識別的整個資源。改變伺服器狀態;重複替換結果仍是該最終狀態(Idempotent)
Delete:Delete , 移除 URI 所識別的資源。改變伺服器狀態;重複刪除資源的結果是資源不存在
設計原則總結
一個好的 RESTful 設計,將操作(動詞)和資源(名詞)分開,使得 API 具有 統一介面 的特性:
專注於資源 (名詞): 您的 URI 應該只包含資源名稱(例如
/products)。使用 HTTP 動詞 (動作): 動作由 HTTP 方法(
GET,POST,PUT,DELETE)來決定。避免在 URI 中使用動詞: 避免使用類似
/getAllUsers或/deleteProduct/456這樣的 URI。錯誤範例:
POST /createNewUserRESTful 範例:
POST /users
這套系統讓 API 的使用者能夠透過查看 URI 和 HTTP 動詞,直觀地 理解 API 將會做什麼操作,大大提高了 API 的可讀性和可維護性。
GraphQL
其他文章都會把RESTful跟GraphQL拿出來一起討論,那我們這邊也來看看什麼是GraphQL吧
GraphQL 跟RESTful本質上是有差異的,因為GraphQL是一種查詢語言,並不是一種架構風格。
GraphQL 是2012年由Facebook(Meta)開發的開源 API 查詢語言與執行環境,開發人員發現REST的架構下會傳送過多”冗餘”的資料,在Response Body中塞太多不必要的資訊導致了回應速度較慢的問題,因此便出現了GraphQL來解決新興社交媒體平台對速度的需求。(後續對RESTful的詬病也有:資料太少)
例如:在RESTful架構下,取得使用者資料 /user 但整個網站中或許有很多頁面只要拿user.nickname 但卻都要請求一大包資料,那這一大包資料中就包含了許多 冗餘資料。


GraphQL帶來的挑戰
前端邏輯外顯化的複雜性
在RESTful的世界中,前端只要fetch/axios 後端給的endpoints,然後將資料拿出還做處理即可,最多就是針對get去做一些query string的條件組裝。
但 GraphQL 的採用會增加前端開發的複雜性,前端需要:
專門的資料獲取層 (Data Fetching Layer):
- 前端需要引入像 Apollo Client、Relay 或 urql 這樣的專門客戶端函式庫。這些函式庫提供了快取管理、狀態管理、查詢狀態追蹤等複雜功能。
查詢管理與 Schema 依賴:
前端必須用 GraphQL 語法撰寫完整的查詢 (Query) 或變動 (Mutation),並確保它們與後端 Schema 完全匹配。
大型應用程式中,管理數十個甚至數百個不同的查詢文件 (例如
.graphql檔案) 會變得複雜。
型別系統與程式碼產生 (Code Generation):
- 雖然 GraphQL 的強型別是優勢,但這意味著前端需要設定工具來從後端 Schema 自動產生 TypeScript 或 Flow 的型別。雖然這能提高安全性,但初次設定需要時間和精力。
錯誤處理的複雜性:
在 GraphQL 中,一個請求可以部分成功(例如,獲取了一個使用者的資料,但其中一個欄位因為授權失敗)並同時返回錯誤。前端需要學習如何解析
data和errors兩個頂層鍵值。GraphQL 的回應通常永遠是 HTTP 200 OK,即使內部發生錯誤(如資料庫連線失敗、權限不足)。錯誤細節是包在 Response Body 的 errors 陣列中。這會導致傳統的 HTTP 監控工具(如依賴 4xx/5xx 報警的系統)失效,後端需要建立針對 GraphQL 的錯誤監控機制。
後端相對應的挑戰
後端以為把資料這塊丟給前端就高枕無憂了嗎?雖然 GraphQL 的實施看起來只是「多了一層解析器 (Resolver)」,但這層抽象帶來了新的性能和安全挑戰:
N+1 查詢問題的風險:
- 這是 GraphQL 後端最著名的挑戰。如果沒有使用 DataLoader 等機制來批量處理關聯查詢,一個簡單的 GraphQL 查詢可能會觸發數百個獨立的資料庫請求。
查詢深度與複雜度限制:
- 後端必須實施強硬的安全措施(如限制最大查詢深度、計算查詢成本),防止前端無意或惡意發送消耗大量資源的複雜查詢,導致 DoS 攻擊。
Resolver 的性能瓶頸:
- 後端解析器需要處理來自各種異構資料源(資料庫、微服務、舊版 REST API)的資料聚合,這要求後端開發者對性能和非同步操作有深刻理解。
Schema 設計的難度:
- GraphQL Schema 一旦發布,就很難更改(因為前端客戶端會依賴它)。設計一個可擴展、可版本控制且符合業務邏輯的 Schema 比設計一組 REST 端點更具挑戰性。
GraphQL 實際上是將部分複雜性從後端轉移到了前端客戶端(例如,處理資料聚合、快取和精準獲取),同時將核心的安全和性能控制權集中到了後端的 Resolver 層。
如何抉擇?
這兩種架構並非互相取代,而是各有適用情境:
選擇 RESTful API:
專案需求簡單,資源結構固定且變化較少。
只需要簡單的 CRUD 操作。
團隊對 REST API 熟悉度高,或需要快速開發小型應用程式。
在微服務架構中,服務之間的通信通常會使用 REST(近年 gRPC 在後端間通訊更受歡迎)。
選擇 GraphQL:
專案資料來源複雜、相互關聯性高。
應用程式需要服務於多個前端介面(如 Web、iOS、Android),且每個介面需要的資料集差異大。
需要頻繁迭代前端介面,需要靈活的資料獲取能力。
遇到 網路環境較差 的行動應用程式,需要極大化資料傳輸效率。
GraphQL 非常適合後端 API gateway 或 BFF 層,讓多個後端資料源合併。
個人實務見解補充
REST 不一定比較慢
很多文章把 GraphQL 推成「更快」,事實上這不一定正確,因為RESTful API的風格定義,並沒有一定會造成under-fetching或multiple calls的狀況。
REST的快取優勢
URL 一致性與資源導向
RESTful API 的設計核心是資源(Resources),每個資源都透過一個固定且唯一的 URL (URI) 來識別。例如,獲取 ID 為 1 的使用者,其 URL 可能是 /users/1。
一致性: 任何客戶端向這個 URL 發出
GET請求,都應該獲得完全相同的資源表述。快取鍵 (Cache Key): 由於 URL 是唯一且一致的,整個 URL 本身就可以作為快取系統中的快取鍵。這是 HTTP 快取機制運作的基礎。
天然支持標準 HTTP 快取語義
HTTP 協定已經內建了強大且標準化的快取機制,REST API 可以直接利用這些機制,而不需要額外的實作

結論
GraphQL:減少來回請求次數
REST:降低單次請求延遲
所以速度比較看場景:
多頁面、多 client、多畫面、多欄位 => GraphQL 可能比較快
API 可 Cache、可 CDN、可 Edge => REST 通常快很多
GraphQL 快的是「資料獲取」
REST 快的是「傳輸與交付」

