Skip to main content

Command Palette

Search for a command to run...

Lesson 8:API 設計哲學-RESTful API 的資源 概念與動詞運用

Updated
3 min read

RESTful API 的核心哲學

REST(Representational State Transfer)是一種架構風格,它不是一個標準,而是一組設計原則。RESTful API 的設計哲學主要圍繞在兩個核心概念:資源(Resources)狀態轉移(State Transfer)

REST 提倡以 資源(Resource)為中心,透過 HTTP 的既有語意(HTTP verbs)定義操作方式,而不是把 API 設計成模糊的 endpoints。

這和傳統 action-style 的設計有本質差異。

💡
action-style 意思是:以function為核心關注於動作。例如:有一個新增使用者的功能 我的path就是:/createUser

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} (單個訂單)。

  • 識別:

  • 層次結構:

    • 可以使用 巢狀結構 來表示資源之間的關係。

    • 例如:/users/{user_id}/orders 表示特定使用者擁有的訂單集合。

HTTP 動詞運用 Methods

一旦我們定義了資源,我們就使用 HTTP Method 來告訴伺服器我們想要對這個資源執行什麼樣的 操作。這是 RESTful API 中實現 狀態轉移 的關鍵。

以下是四個最常用的 HTTP 動詞及其對應的 CRUD(Create, Read, Update, Delete)操作:

  1. Create:POST , 在資源集合中建立一個新的資源。改變伺服器狀態;重複呼叫會建立多個資源(Non-Idempotent)

  2. Read:GET , 從伺服器檢索資源或資源集合的表示。不改變伺服器狀態;重複呼叫結果相同(Safe)

  3. Update:PUT , 完全替換 URI 所識別的整個資源。改變伺服器狀態;重複替換結果仍是該最終狀態(Idempotent)

  4. Delete:Delete , 移除 URI 所識別的資源。改變伺服器狀態;重複刪除資源的結果是資源不存在

設計原則總結

一個好的 RESTful 設計,將操作(動詞)和資源(名詞)分開,使得 API 具有 統一介面 的特性:

  1. 專注於資源 (名詞): 您的 URI 應該只包含資源名稱(例如 /products)。

  2. 使用 HTTP 動詞 (動作): 動作由 HTTP 方法(GET, POST, PUT, DELETE)來決定。

  3. 避免在 URI 中使用動詞: 避免使用類似 /getAllUsers/deleteProduct/456 這樣的 URI。

    • 錯誤範例: POST /createNewUser

    • RESTful 範例: 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 中,一個請求可以部分成功(例如,獲取了一個使用者的資料,但其中一個欄位因為授權失敗)並同時返回錯誤。前端需要學習如何解析 dataerrors 兩個頂層鍵值。

    • 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 快的是「傳輸與交付」

從Rookie到Junior,一個後端成長的30堂課

Part 21 of 31

“從 Rookie 到 Junior:一個後端成長的 30 堂課” 是一套專為後端新手所設計的成長型技術系列文章。內容以實務為導向,逐步拆解後端工程的核心能力,包括程式語言基礎、架構思維、框架運作原理、業務邏輯設計、資料庫操作、以及常見的開發模式。 本系列的目標是協助讀者從零散的學習堆疊,建立成體系的後端知識框架。讀者能夠理解各語言背後不變的工程思維與設計原則。這套內容旨在讓學習者從「會寫程式」進入「能理解系統設計」的階段,逐步具備勝任 Junior Backend Engineer 的能力。

Up next

Lesson 7: 網路共通語-HTTP 協定核心

前面介紹了比較多實務上的內容,內容也相對扎實,接下來我們就進到比較輕鬆的環節,這篇文章主要探討HTTP以及相關延伸的概念。 什麼是Http HTTP(超文字傳輸協定,HyperText Transfer Protocol)是全球資訊網 (World Wide Web) 的基礎,它是一種應用層協定,用於在用戶端和伺服器之間傳輸超媒體文件(例如 HTML 頁面)。HTTP 是網路上的共通語言。 HTTP (HyperText Transfer Protocol,超文字傳輸協定) 是全球資訊網 (Wo...

More from this blog

Lesson 26 : 系統韌性的守護者-限流、熔斷與背壓的設計模式

當這幾個名詞出現後,代表我們進到了一個高併發/大流量的系統了。在這個章節中,我們一起來看看如何透過一些方式來避免高併發導致我們的系統crash掉。 限流(Rate Limiting) 相信大家對這個名詞並不陌生,限流其實就是字面上的含意,限制流量。 限流的目的是保護「接收方」,確保系統不會因為瞬間的高併發請求而癱瘓。 限流通常發生在 API Gateway 或服務的最前端。它像是一個夜店門口的保全

Mar 26, 20262 min read

Lesson 25: 淺談 單體架構、微服務架構與單/多租戶架構

過去我們討論了 要把程式寫在哪、程式要怎麼拆的題目,接下來我們來看看「如何服務不同客戶」,這些架構反映了軟體開發在擴充性與複雜度之間的權衡。 軟體架構深度解析:從系統拆分到商業規模化 在軟體工程的演進中,架構的選擇往往是在「開發效率」、「系統擴充性」與「營運成本」之間尋求平衡。我們可以從兩個核心維度來觀察這些架構:系統如何運行(單體 vs. 分散式) 以及 如何服務客戶(單租戶 vs. 多租戶)。

Mar 26, 20262 min read

Lesson 24: 資料庫擴展術-讀寫分離、複寫機制與快取一致性挑戰

為什麼要讀寫分離? 大多數的 Web 應用都是 「讀多寫少」(例如:看文的人多,發文的人少,Heavy Read System)。當所有的請求都塞給同一台資料庫時,磁碟 I/O 和連線數會成為瓶頸。 Master (主庫): 負責寫入 (Insert/Update/Delete),確保數據一致性。 Slave (從庫): 負責讀取 (Select),可以有多個從庫來分擔讀取壓力。 為什麼讀

Mar 25, 20262 min read

面試經驗談 2025-2026

從2025年3月開始,我陸陸續續參與了從新創到上市櫃公司的Senior - Tech Lead的相關面試,其中有不乏 尊重面試者、展現高度專業的企業(公司),當然也有遇到幾場面試鬼故事,這篇文章主要分享我對於軟體工程師面試的方向分享,以及部分鬼故事,以此警惕自己不要成為這樣的面試官。 AI的洪流,改變了SWE的生態 LLM的發展確確實實的影響到了軟體工程師的生態。 過去受限於算力與資料規模,深度學

Mar 23, 20262 min read

Lesson 23: 系統的緩衝區-Queue 佇列與非同步處理 (Asynchronous)

佇列 佇列的實作工具非常多,舉凡AWS SQS、RabbitMQ、Kafka…等。 佇列的特性,其實是一個非常強大的系統緩衝區,應用層面非常廣。 什麼是佇列? 佇列可以想像成,在既有流程中外,有另一個”水管”,來連接原有的資料流(或邏輯過程),其中 呼叫方將資料 推(Push)到水管中,接受方(監聽) 從水管中將資料拉(Pull)出處理 為什麼佇列是「強大的緩衝區」? 在同步處理中,系統像是一

Mar 23, 20262 min read

Bennett's Tech Blog | 後端架構、系統設計

32 posts

來自台灣的軟體工程師,相信軟體可以改變世界