系統設計 - RESTful API 使用問題和建議

雖然 RESTful API 已經成爲業界對於 API 的共識,但是不得不說,但是不得不說它具有很多侷限性。

RESTful API 和很多的技術流傳的原因類似:始於一種非常理想化的願景,但是在落地時卻需要做出權衡和取捨。

它的流行開始於 Roy Fielding 的演講,Roy Fielding 也是 HTTP 協議標準作者之一。

我猜測 Roy Fielding 的想法是,HTTP 協議已經是一個完善的應用層協議了,對於應用開發來說,只需要將所有的網絡數據抽象爲 URI 資源,然後配合可選的 Method 以及狀態碼就夠用了。

但是,在我們實際落地的情況中,HTTP 的表達力完全不夠。其限制主要有這幾個:

基於這些原因,人們爲了遵守 RESFul 不得不絞盡腦汁,而如果使用 RPC 風格的 API 只需要使用合適的動詞作爲 URI 以及合適的 Payload 報文格式即可。

但是,總體來說,在一定程度上,使用 RESTful 風格,可以做到自解釋性,減少了文檔的依賴。而它的缺點,也可以通過一些團隊規約避免。

本文基於技術研討會討論的內容,整理了一些使用 RESTful 的一些建議,包括如何通過團隊契約在一定程度上彌補表達力不夠的問題。

01 使用版本號解決版本不兼容問題

版本化 API 會有很多好處,而版本化 API 有很多種風格,包括:

推薦使用 URL 前綴實現,這樣對後面 API 定義無侵入性且能通過 URL 表達版本。

實例:GET /v1/products/{id}

02 資源路徑參考領域模型

在一般情況下,可以以模塊、聚合根作爲路徑前綴,讓路徑排列更有規律。

參考類似模式: /[模塊]/[版本號]/[聚合根]/{id}/[實體]/{id}/[屬性/動作]

03 謹慎選擇 HTTP Method

HTTP 協議提供了很多的 Method,但是處於團隊理解成本的原因建議只使用下面幾個 HTTP Method:

避免使用 PATCH,原因是無法表達業務含義,往往產生破壞性變更。例如,將保存的出庫單提交,業務上需要修改單據的狀態。應該避免使用 Patch 部分更新單據,建議使用 PUT /xxx/submit 的形式設計,讓團隊更容易理解,保證業務一致性。

04 實體的單複數應具有實際意義

通過 URL 應該能識別出返回的結構類型是否是一個列表或者分頁的包裝對象。

例如,通過 GET /v1/orders/{id}能猜測出返回結果是一個資源對象。

而通過 GET /v1/orders 能猜測出其結果是一個列表。

在某些項目中,複數詞彙的 URL 默認返回分頁對象,而一些項目會給分頁 URL 添加一個 page 後綴,例如 GET /v1/orders/page

05 合理實現冪等性

冪等含義:相同輸入,應得到相同返回。

06 提前設計查詢語言或關鍵字

如果需要實現複雜的查詢,可以提前設計一套基於 Query 參數的查詢規則,儘可能實現通用的數據庫字段查詢。

需要考慮:

這類實現一般需要結合具體數據庫查詢框架,例如 JPA、QueryDsl、Mybatis Plus 的 Wrapper 查詢能力。

例如下面一個基於 Mybatis Plus 的通用 QueryWrapper:

07 狀態碼的選用

狀態碼的選用只用於前端處理一些通用的錯誤,而具體的業務規則錯誤統一使用 409(業務規則衝突)來返回,並返回約定的錯誤碼、報錯信息。

參考如下:

08 批量處理接口

批量處理也是 RESTful 風格 API 不好處理的地方。有一些常見的做法:

  1. 由於 URL 使用複數已經代表資源,因此需要增加 /batch 後綴作爲 URL 區分

  2. 使用類似版本號的處理方式在 URL 前增加 /batch 前綴

  3. URL 上可以不區分,在傳入參數的格式上區分。例如,通過傳入一個列表表達批量創建用戶。

方案 2 看似更符合 RESTful API 風格,實際上非常容易讓人困惑,因爲無法通過 URL 語義區分批量接口;方案 2 的問題是,批量接口並不常見,使用前綴會影響很多接口的語義。

在一些文章中,還會區分 bulk 和 batch 的區別,認爲 bulk 是對多個資源處理同樣的操作,而 batch 是針對多個資源處理不同的操作。

當然在實踐中我們不用如此區分,但在微服務環境下一些基礎服務往往需要提供批量接口,避免循環調用帶來的性能問題。

一些分頁接口的例子:

09 動詞名詞化技巧和場景

有些情況下動詞 API 在充分建模的情況下也可以名詞化。

比如登錄的接口,我們在建模充分後識別到登錄行爲本質上創建了一個會話或者憑證,於是可以將:

POST /v1/users/login

改寫爲:

POST /v1/authorizes

另外一個例子,在金融領域需要對資產進行估值,看似應該使用:

POST /v1/assets/calculate

其實應該改寫爲:

POST /v1/capital-rating-transactions

每次評估後都會產生一次事務 ID。

10 區分成功和錯誤的返回結果

當 API 報錯時,有些做法是使用一個容器包裝錯誤信息和成功的消息體。

例如:

{
  "code": "xxx"
  "data": {}
  "error": {}
}

這種做法相當於再次封裝了 payload 會顯得有些冗餘,主流的 API 設計一般是:當返回 HTTP 代碼爲 2xx 時,返回成功的對象,當使用其他代碼時返回錯誤的對象。

錯誤的對象一般包含:

11 創建和更新只返回必要信息

在討論中,有一個話題是創建和更新操作是否需要返回處理後的對象?

返回的好處是,在編寫 API 測試時方便取數,另外也可以複用詳情的返回模型。

不返回的好處時,可以簡化開發工作,僅僅返回必要的字段和數據,前端需要獲取詳細數據,可以調用查看相關接口。

同時,不返回全量信息也可以在部分場景提高性能,避免組裝、傳輸過多數據。

12 可以參考的 API 規範和示例

除了前面的建議外,我們也經常參考一些真實的 API 設計規範,下面是一些常見可參考模仿的 API 設計示例:

參考資料

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IEE2MFJgbicpSWs2ffLICQ