統一資源識別符 (URI) 是 Web 架構中最簡單的元素,也是最重要的 !
良好的 URI 設計,並非只有『好看』而已,
往往牽涉到開發的彈性、可擴充性。
目錄
特性
URI 對映的是一個 資源,資源 可以是任何東西,
而 資源 (resource),是對一實體集合的『概念』映射 (而非特定檔案)。
這種對於 資源 (resource) 的 抽象定義 (abstract definition),
是實現 Web 架構的 關鍵特性 (Key Features):
靜態 (static)
許多 Web 頁面、應用 依賴此特性,
使 URI 的引用,能保持 靜態 (static),
且訪問的結果,能 隨時間而變化。
例如,此 button 將導向至 https://crazy.ck101.com/news/
而非 每個星期,都要 更改 一次 URI (不計重新導向):
又例如,隨著使用者人數成長,某公司欲部署/搬移 新 Server,
過程中可能涉及 儲存硬碟、Web Server 的更換 (e.g., Apache -> Nginx),
甚至是全新的 作業系統 (Windows -> Linux)…。
盡可能少更改
然而 HTTP 語意並不會變,
倘若當初 URI 的設計良好,新環境僅需保持映射語意,
Client 仍藉由相同的方式與之互動,
除了 Server 提升健壯性、效能…,沒有其他變化發生。
再次,實現的關鍵在於 — (1-3) 統一介面 (Uniform Interface):
HTTP 透過向 客戶端 (Client) 提供 與資源種類無關的 統一介面 (uniform interface),
藉此 隱藏服務實現的細節。
由於 URI 的 映射語意 (semantics of the mapping) 是 區別資源的關鍵 !
於是,一個簡單的 URI 設計原則:
URI 應 盡可能少地更改。
(儘管 URI 是對映靜態文件)
通用性 (generality)
資源 (resource) 與 表示 (representation) 以 元資料 (metadata),包含了許多資訊來源,
而不需人為地以 種類 或 實作方式 加以區分,
進而提供 資源 的 通用性 (generality)。
延遲綁定 (late binding)
代表允許 資源 延遲綁定 (late binding) 到 表示 (representation) 的參考,
並能基於請求訊息的性質,進行 內容協商 (Content Negotiation)。
基於概念 (Based on Concept)
因為背後的資源可以是 任意東西 (e.g., 服務、運算、檔案),
若 部署、快取 的 安全性 與 彈性 允許,
應以 基於概念 (Based on Concept) 的 URI 映射 為主。
例如,對可能有多種 表示 (representation) 的資料 「使用者 9487」,
應使用 表頭欄位 (Header) 或 其他方式 進行 內容協商 尤佳,而非特定檔案 :
https://example.com/users/9487/ (O)
https://example.com/users/9487.json (X)
然而,這仍須取決所使用的 快取策略。
若為不易改變的靜態資源 (某些 css、js),則較無影響 :
https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js (O)
名詞 vs. 動詞
HTTP 其中一個設計目標是:
分離『 資源辨識 (resource identification) 』 與 『 請求語意 (request semantics) 』
兩者的關係,就像是:
- 目標 與 (目標的) 用途
- 名詞 與 動詞
- 工具 與 使用方式
- …
BUT 現實是殘酷的…😢
過去的十幾年,這個理念被忘的一乾二淨,
許多應用將一坨拉庫 『動詞』的 請求語意,
塞到『名詞』的 URI 之中。
以新增一個 id 為 9487 的使用者為範例:
錯誤示範
我要資源:『新增使用者』(X)
編號為: 9487 (O)
https://example.org/createUser?id=9487
沒道理…
若這樣改呢 ?
我要資源: 『動作』是『新增』的使用者 (X)
編號為: 9487 (O)
https://example.org/user?action=create&id=9487
也沒好到哪去 😐
正確示範
我要資源:『使用者』(O)
編號為: 9487 (O)
https://example.org/user?id=9487
或者將 query 置於路徑中:
我要資源:『使用者』(O)
編號為: 9487 (O)
https://example.org/user/9487/
可預見性
URI 時常具有 階層 (hierachy) 的觀念,
設計時,應優先考慮『可預見性』,
提供良好的邏輯,讓人們能直覺的猜到資源的位置與內容。
在 URL 中,即是以「 / 」(slash) 表達階層概念,例如:
https://example.org/users/Jason/ 使用者 Jason
https://example.org/users/Jason/articles/20 使用者 Jason 的 20 號文章
善用子網域
如 (2-1) 統一資源識別符 (URI) 所述:
網域名稱 中,也包含了能依 網域擁有者 自行設定的 子網域 (Subdomain),
如最常見的 www (唸法: triple w)、本站的 blog、
內容傳遞網路 常使用的 cdn、或其他的 api、mail、tw …等等:
Ex:
https://api.github.com
https://www.github.com
與 『路徑』相比,兩者皆能實作負載平衡,並沒有絕對的好壞,
然而,個人認為 子網域 (Subdomain) 獨立部署、組織調校 上相對便利,故偏好之,
實務上仍應考量自身需求為主:
https://blog.jason.party
https://jason.party/blog
總結
幾個常見的 URI 設計原則:
- 使用名詞 而非動詞 (如上述範例)
- 依 集合 與 個體,使用 複數 與 單數 單字
- 根據資源的關聯性,區分先後 順序
- 視需求 使用『/』或『?』
- 單字間空白以 『–』或『_』連接 (ex: 訂單列表 order-list)
- 相同的資源可能有多個 URI
Ex:
https://example.org/users 使用者列表
https://example.org/users/Jason/ 使用者 Jason
https://example.org/users?name=Jason 使用者 Jason
https://example.org/users/Jason/articles 使用者 Jason 的 文章列表
https://example.org/users/Jason/articles/20 使用者 Jason 的 20 號文章
https://example.org/users/Jason/articles?id=20 使用者 Jason 的 20 號文章
API
URI 實務上有非常多種風格,可多參考知名 API 實作。
例如:
GitHub API: https://api.github.com/
Facebook 的 Graph API: https://developers.facebook.com/docs/graph-api