大多數 RESTful API 其實並不真正符合 REST 原則
文章核心觀點
重點摘要:大多數號稱「RESTful」的 API 實際上並不符合 REST 架構風格的核心原則,特別是缺乏超媒體控制(HATEOAS)的實現。
REST 的真正定義
Roy Thomas Fielding 在其 2000 年的博士論文中定義了 REST(Representational State Transfer)作為一個架構風格,而不是簡單的 CRUD 操作或 HTTP 動詞的使用規範。
"如果應用程式狀態的引擎(因此 API)不是由超文本驅動的,那麼它就不能稱為 RESTful,也不能稱為 REST API。就是這樣。"
— Roy Fielding (2008)
HATEOAS:被忽視的核心原則
HATEOAS(Hypermedia as the Engine of Application State) 是 REST 的基本原則,要求用戶端透過伺服器回應中嵌入的超媒體連結動態發現操作和互動,而不是依賴外部文件。
HATEOAS 範例:
{
"orderId": 123,
"status": "pending",
"amount": 99.99,
"_links": {
"self": {
"href": "/orders/123",
"method": "GET"
},
"cancel": {
"href": "/orders/123/cancel",
"method": "POST"
},
"payment": {
"href": "/orders/123/payment",
"method": "POST"
}
}
}
HATEOAS 的核心價值:
- 解決用戶端-伺服器耦合問題 — 用戶端不需要硬編碼 URL 路徑
- 提升系統的可進化性 — 伺服器可以自由更改 URI 結構
- 減少 URI 結構變更對用戶端的影響 — 動態發現可用操作
- 自我描述的 API — 用戶端可以理解目前狀態下可執行的操作
什麼是「資源」?
根據 Fielding 的定義和 RFC 3986,資源的概念遠比大多數人理解的更廣泛:
"任何可以被命名的信息都可以是資源:文檔、圖像、時間性服務(如「洛杉磯今天的天氣」)、其他資源的集合、非虛擬對象(如一個人)等等。資源是概念映射到一組實體,而不是在任何特定時間點對應於映射的實體。"
重要概念:資源不等同於資料庫實體或持久化物件,它是一個概念性的對應,可以是任何能被 URI 唯一識別的東西。
資源的範例:
// 靜態文件
https://api.example.com/documentation
// 動態資料
https://api.example.com/weather/taipei/today
// 集合資源
https://api.example.com/users
// 特定實例
https://api.example.com/users/123
// 關聯資源
https://api.example.com/users/123/orders
// 時間相關資源
https://api.example.com/reports/monthly/2025/01
常見的誤解
對 REST 的常見誤解:
- REST 就是 CRUD 操作 — REST 不限於基本的新增刪除修改查詢
- 資源就是資料庫實體 — 資源是概念性的,可以跨越多個實體
- RESTful API 不能使用動詞 — 這是設計決策,不是 REST 要求
- 使用 HTTP 動詞就是 RESTful — 僅使用 HTTP 方法遠遠不夠
- JSON 格式就代表 RESTful — 媒體類型不決定是否符合 REST
Fielding 的六大 RESTful API 規則
-
不依賴單一通信協議
API 應該能使用任何 URI 方案,不僅限於 HTTP -
不改變通信協議
遵循現有標準如 HTTP,不要重新定義其行為 -
專注於媒體類型,而非 URI
定義數據格式和超媒體控制,而不是 URI 結構 -
不要硬編碼 URI 結構
用戶端應該透過伺服器提供的連結動態發現 URI -
避免資源「類型」
伺服器內部的資源分類對用戶端應該是不可見的 -
從書籤開始,跟隨連結
用戶端只需要知道起始 URI 和標準媒體類型
實際應用範例:
// 用戶端只知道入口點
GET https://api.example.com/
// 伺服器回應包含可用的操作連結
{
"welcome": "Welcome to Our API",
"_links": {
"users": { "href": "/users" },
"orders": { "href": "/orders" },
"products": { "href": "/products" }
}
}
// 用戶端跟隨連結,而不是建構 URL
GET https://api.example.com/users
// 每個資源都包含其可用操作
{
"users": [...],
"_links": {
"self": { "href": "/users" },
"create": { "href": "/users", "method": "POST" },
"search": { "href": "/users/search{?q}", "templated": true }
}
}
為什麼大多數 API 不是真正的 RESTful?
實際考量:
- 工具生態系統 — OpenAPI 等規範提供了立即可用的好處,如自動產生文件和用戶端程式碼
- 開發體驗 — 靜態合約比動態超媒體更容易理解和實作
- 團隊耦合 — 同一團隊開發前後端時,解耦的必要性不明顯
- 學習曲線 — 建構真正的超媒體驅動用戶端需要更多的認知負擔
- 效能考量 — 額外的連結資訊可能增加回應大小
- 缺乏標準化工具 — 相比 OpenAPI,HATEOAS 缺乏成熟的工具支援
現實中的權衡:
// 典型的「RESTful」API(實際上是 HTTP-based RPC)
GET /api/users/123
POST /api/users/123/activate
DELETE /api/users/123
// 真正的 RESTful API 會是
GET /api/users/123
{
"id": 123,
"name": "John Doe",
"status": "inactive",
"_links": {
"self": { "href": "/users/123" },
"activate": { "href": "/users/123/activate", "method": "POST" },
"delete": { "href": "/users/123", "method": "DELETE" }
}
}
結論與建議
實用主義的建議:
- 誠實命名 — 避免使用「RESTful」這個術語,改稱「基於 HTTP 的 API」
- 需求導向 — 建構對專案和使用者最有意義的 API
- 易用性優先 — 專注於 API 的易學性和不易誤用性
- 情境決策 — 根據具體使用情境選擇合適的設計
何時使用不同的方法:
- HATEOAS 適用於:
- 公開 API 給外部開發者使用
- 需要長期進化的系統
- 多個獨立團隊維護的用戶端
- 複雜的狀態機和工作流程
- 簡單 HTTP API 適用於:
- 同一團隊維護的前後端
- 內部微服務通訊
- 簡單的 CRUD 操作
- 需要快速開發和部署的專案
關鍵問題:誰是你的 API 消費者?學習和使用你的 API 有多容易?它是否直觀?有什麼限制?如何進行版本控制?如何處理廢棄和日落策略?如何有效地向消費者傳達 API 的變更?這些問題比資源識別符的實際格式更有價值。
本摘要基於原文製作,建議閱讀原文以獲得完整資訊。
原文提供了更深入的技術細節和更多實例,值得完整閱讀。