動態

詳情 返回 返回

2023再談RESTful 和 GraphQL - 動態 詳情

前段時間組內搞代碼檢視,經常能看到一些 “掛着 RESTful 羊頭,賣的卻是 GraphQL 狗肉”的 API 設計。
舉個例子,假如後台有兩種資源用户 User 和 羣組 Group ,按照RESTful的規範,他們設計以下API端點:

# 獲取用户列表
GET /users
# 獲取指定用户
GET /user/{id}
# 創建用户
POST /users
# 修改用户
PUT /user/{id}
# 刪除用户
DELETE /user/{id}

# 獲取羣組列表
GET /groups
# 獲取指定羣組
GET /group/{id}
# 創建羣組
POST /groups
# 修改羣組
PUT /group/{id}
# 解散羣組
DELETE /group/{id}

咋一看沒啥問題,可是到了後面,要實現 “用户加入羣組” 和 “用户退出羣組” 兩個特性時,他們給出的API設計:

# 用户加入羣組
PATCH /group/{id}
{
  "addMembers": ["userID"]
}

# 用户退出羣組
PATCH /group/{id}
{
  "deleteMembers": ["userID"]
}

我當場就蚌埠住了,這哪裏是 RESTful 風格,這不就是他們老大深痛惡絕的 GraphQL ?
眼看我就要當場發飆,有個老手趕緊出來圓場,他給出以下API設計:

# 用户加入羣組
PATCH /user/{id}/group/{groupID}

# 用户退出羣組
DELETE /user/{id}/group/{groupID}

這個設計看起來是解決剛才的問題,但實際上只是掩耳盜鈴。
我接着追問: “管理員邀請用户加入羣組” 和 “管理員將用户踢出羣組” 要怎麼設計呢?
那個老手依瓢畫葫蘆給出以下設計:

# 管理員邀請用户加入羣組
PATCH /group/{id}/user/{userID}

# 管理員將用户踢出羣組
DELETE /group/{id}/user/{userID}

眼看他們還不醒悟,我就接着追問:“用户加入羣組需要管理員同意” 和 “管理員邀請用户加入羣組需要用户同意” 又怎麼實現呢?
這下子老手也沒轍,只能説這塊的代碼需要重新設計。

我讓他們把原來的設計文檔重新拿出來檢視,然後發現很多地方都不符合規範,連基本的實體關係圖ER都沒有!
我忍不住吐槽:怪不得當年我一個人拿着不到一半的工資,連前後端一起搞,效率卻比這幫985/211的高材生/海歸還要高,而他們連一個最基本的CURD都要不斷地返工!

原來招人的HR完全不考慮專業是否對口,只看畢業院校和學歷,結果這幫非科班出身的人連ER圖都不會,更別説更復雜一點的類圖、活動圖以及泳道圖,要知道對科班出身的人來説畫UML是基本功!

其實他們的設計裏面已經有了 User 和 Group 兩張實體表,再增加一張 Relation 關係表:

struct Relation {
id string
user_id string
group_id string
status string # 根據cookie的session獲取當前用户id
# 再判斷當前用户是不是當前group的管理員
# 如果是則管理員邀請用户,把status設置為"wait_for_admin_approve"
# 否則就是用户申請加入羣組,把status設置為"wait_for_user_accept"
}

然後這樣設計API:

# 用户加入羣組
POST /relations
{
  user: "abc",
  group: "ikun"
}

# 用户退出羣組
DELETE /relations/{id}

# 管理員邀請用户加入羣組
POST /relations
{
  user: "abc",
  group: "ikun"
}

# 管理員將用户踢出羣組
DELETE /relations/{id}

回頭總結,不難發現,大多數人在實踐RESTful規範時,最容易犯的錯誤就是處理嵌套資源的時候,容易設計出 /parent/xxx/sub/xxx 這樣的API。

這種設計咋一看起來確實非常容易理解,但其實後期非常難以維護,尤其是遇到 n:m 多對多的關係時,這種嵌套的API設計就是一場災難。

即便是 1:n 一對多的情況下,嵌套的API設計看起來沒有問題,但後期當子資源 n 越來越大的時候,單獨增加/刪除某個子資源需要把所有子資源都獲取一遍,就非常容易形成性能瓶頸,並且沒辦法通過分庫分表的方式進行橫向擴容。

儘管我們可以換成GraphQL的風格,通過 addSubdelSub 的方式單獨新增或刪除某個子資源,來避免每次都需要獲取所有的子資源。

但我們也必須要考慮併發更新資源頻繁引發衝突的風險;尤其越來越多的Java開發轉向基於Go的雲原生開發,為了方便維護不再額外引入SQL數據庫,直接使用etcd,但etcd本身也有mvcc機制來保證數據的一致性,這樣還是會導致併發更新資源的衝突!

經過這次事情,我也徹底明白了,真正掌握RESTful規範,離不開對關係模型的深厚功底;儘管我們從SQL數據庫切換到etcd這類NoSQL數據庫,按道理API設計規範應該相應地換成GraphQL與對象模型緊密相連的規範。

但考慮團隊水平參差不齊,後台API接口設計規範還是要堅持RESTful為主,先把基於關係數據庫的基本功練好!

前端團隊是可以基於GraphQL規範實現Backend for Frontend,搞一個接口聚合模塊,作為性能優化手段,減少首頁白屏時間。

Add a new 評論

Some HTML is okay.