--- title: Restful api design Principles tags: 網頁後端技術文章 --- # RESTful api design Principles [TOC] --- ## Architectural Constraints REST 定義以下六種約束,只要符合以下六種約束都可以稱為 RESTful api。 * Uniform interface * Client–server * Stateless * Cacheable * Layered system * Code on demand (optional) ### Uniform interface 一個資源在系統中只能有一個`logical URI`,然而他必須提供一個方式去獲取相關的資料,建議將資源的命名與網頁相同名稱。 任何一個單一的資源不應該太大或是說包含各個項目在他的表達中,一個資源應該要包含能夠連結到相關的URIs去抓取相關的資訊。 如果資源的表示橫跨系統的話,必須遵從特殊的命名規範,例如:命名約定、連結格式、資料格式。 所有資源都應該可以通過通用方法(例如 HTTP GET)訪問,並使用一致的方法進行類似的修改。 :::warning 一旦開發人員熟悉了您的 API 之一,他應該能夠對其他 API 遵循類似的方法。 ::: ### Client–server 這種約束本質上意味著客戶端應用程序和服務器應用程序必須能夠獨立地發展而不相互依賴。 客戶端應該只知道資源 URI,僅此而已。 今天,這是 Web 開發的標準做法,因此您不需要任何花俏的東西,把事情簡單化。 :::warning 服務器和客戶端也可以單獨更換和開發,只要不改變它們之間的介面即可。 ::: ### Stateless 確保服務器與客戶端之間的交互是無狀態的,服務器不應該儲存任何客戶端的需求,服務器應該對待所有的需求像新的一樣,no session, no history。 :::warning 請求之間不應在服務器上存儲客戶端上下文。 客戶端負責管理應用程序的狀態。 ::: ### Cacheable 在 REST 中,緩存應在適用時應用於資源,然後這些資源必須聲明自己可緩存。 緩存可以在服務器端或客戶端實現。 :::warning 管理良好的緩存部分或完全消除了一些客戶端-服務器交互,進一步提高了可擴展性和性能。 ::: ### Layered system 例如,REST 允許您使用分層系統架構,在該架構中您在服務器 A 上部署 API,在服務器 B 上存儲資料並在服務器 C 中驗證請求。 客戶端通常無法判斷它是直接連接到終端服務器還是沿途的中介。 ### Code on demand (optional) 大多數情況下,您將以 XML 或 JSON 的形式發送資源的靜態表示。 但是當你需要時,你可以自由地返回可執行的程式碼來輔助應用程式的一部分,例如,客戶端可以調用你的 API 來獲取 UI component 的渲染程式碼。 這是允許的。 ## Resource naming ### What is a resource? 在 REST 中,主要呈現的資料稱為 `resource`,有一個約束和強健的命名策略從長遠來看會是最好的設計決策之一。 通常一個 resource 不會是抽象的物件,例如:人, resource 是到一組實體的概念 mapping。 #### Singleton and Collection Resources 一個資源可以成為單一集合或是 collection ,舉例來說:消費者們是一個 collection ,我們可以辨識消費者們使用URI`/customers`,我們可以辨識單一消費者資源使用`/customers/{customerId}`. #### Collection and Sub-collection Resources 一個資源可能包含許多子資源 一個子資源`accounts`他是位於`customer`下,我們可以使用`/customers/{customerId}/accounts` 簡單來說,一個單一資源`account`,如果他是在`accounts`下依然可以被辨認如下,`/customers/{customerId}/accounts/{accountId}` ## Best Practices ### Use nouns to represent resources api 應該以名詞作為命名而不是動詞,因為名詞具有屬性,而動詞沒有,如下所示: * Users of the system * User Accounts * Network Devices etc. and their resource URIs can be designed as below: ```bash= http://api.example.com/device-management/managed-devices http://api.example.com/device-management/managed-devices/{device-id} http://api.example.com/user-management/users http://api.example.com/user-management/users/{id} ``` ### document 一個 `document` 是單一觀念,有點像物件的實體或是資料庫的紀錄。 你可以看到他像是在單一資源裡面的`resource collection`,一個`documents`的狀態會被表現成以下兩種分別為:有值的欄位 or 連結到其他相關的資源,使用單數來表示`document`的原型。 ```bash= http://api.example.com/device-management/managed-devices/{device-id} http://api.example.com/user-management/users/{id} http://api.example.com/user-management/users/admin ``` ### collection 客戶可能會建議將新資源添加到`collection`中。但是,由集合資源選擇是否創建新資源。 使用“複數”名稱來表示集合資源原型。 ```bash= http://api.example.com/device-management/managed-devices http://api.example.com/user-management/users http://api.example.com/user-management/users/{id}/accounts ``` ### Consistency is the key 使用一致的資源命名約定和 URI 格式,以最大限度地減少歧義和最大的可讀性和可維護性。 您可以實施以下設計提示以實現一致性: #### Use forward slash (/) to indicate hierarchical relationships ```bash= http://api.example.com/device-management http://api.example.com/device-management/managed-devices http://api.example.com/device-management/managed-devices/{id} http://api.example.com/device-management/managed-devices/{id}/scripts http://api.example.com/device-management/managed-devices/{id}/scripts/{id} ``` #### Do not use trailing forward slash (/) in URIs ```bash= http://api.example.com/device-management/managed-devices/ http://api.example.com/device-management/managed-devices /*This is much better version*/ ``` #### Use hyphens (-) to improve the readability of URIs ```bash= http://api.example.com/device-management/managed-devices/ http://api.example.com/device-management/managed-devices /*This is much better version*/ ``` #### Do not use underscores ( _ ) ```bash= http://api.example.com/inventory-management/managed-entities/{id}/install-script-location //More readable http://api.example.com/inventory-management/managedEntities/{id}/installScriptLocation //Less readable ``` #### Use lowercase letters in URIs ```bash= http://api.example.org/my-folder/my-doc //1 HTTP://API.EXAMPLE.ORG/my-folder/my-doc //2 http://api.example.org/My-Folder/my-doc //3 ``` In the above examples, 1 and 2 are the same but 3 is not as it uses My-Folder in capital letters. #### Do not use file extensions ```bash= http://api.example.com/device-management/managed-devices.xml /*Do not use it*/ http://api.example.com/device-management/managed-devices /*This is correct URI*/ ``` #### Never use CRUD function names in URIs ```bash= HTTP GET http://api.example.com/device-management/managed-devices //Get all devices HTTP POST http://api.example.com/device-management/managed-devices //Create new Device HTTP GET http://api.example.com/device-management/managed-devices/{id} //Get device for given Id HTTP PUT http://api.example.com/device-management/managed-devices/{id} //Update device for given Id HTTP DELETE http://api.example.com/device-management/managed-devices/{id} //Delete device for given Id ``` #### Use query component to filter URI collection 有時候我們會遭遇到一些特殊需求,包含說排序、過濾、 pagination 基於特定條件,然而針對這樣的需求,我們不應該創建一個新的api,取而代之的是開啟排序、過濾、pagination在該 resource api,透過傳入的 parameters 去 query ```bash= http://api.example.com/device-management/managed-devices http://api.example.com/device-management/managed-devices?region=USA http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ&sort=installation-date ``` ## 常見QA ### Difference between PUT and POST https://restfulapi.net/rest-put-vs-post/