淺析 Open API 設計規範
背景
最近由於業務需求,我參與研發的雲產品 CSB 需要對外開放 Open API,原本不是什麼難事,因爲阿里雲內部的 Open API 開放機制已經非常成熟了,根本不需要我去設計,但這次的需求主要是針對一些獨立部署的場景,需要自行設計一套規範,那就意味着,需要對 Open API 進行一些規範約束了,遂有此文。
Open API 和前端頁面一樣,一直都是產品的門面, Open API 不規範,會拉低產品的專業性。在雲場景下,很多用戶會選擇自建門戶,對接雲產品的 Open API,這對我們提出的訴求便是構建一套成熟的 Open API 機制。
站在業務角度,有一些指導原則,指導我們完善 Open API 機制:
-
前端頁面使用的接口和 Open API 提供的接口是同一套接口
-
任意的前端頁面接口都應該有對應的 Open API
站在技術角度,有很多的 API 開放標準可供我們參考,一些開源產品的 Open API 文檔也都非常完善。一方面,我會取其精華,另一方面,要考慮自身產品輸出形態的特殊性。本文將圍繞諸多因素,嘗試探討出一份合適的 Open API 開放規範。
Open API 設計考慮因素
一個完善的 Open API 規範到底應該規範哪些東西?
站在設計角度,需要考慮:命名規範,構成規範,路徑規範,出入參規範,數據類型規範,統一返回值規範,錯誤碼規範,分頁規範。
站在團隊角度,團隊中的後端初級中級開發以及前端研發是否有足夠的經驗,領悟並落地好制定的 API 規範。同時,伴隨着人員流動,這份 Open API 規範是否可以很好地被傳承下去。
站在行業角度,需要考慮提供 Open API 的產品所在的市場是否已經成熟,API 風格可能已經有了對應的規範。
站在產品角度,每個產品適合的 API 風格是不同的,下文會着重探討這一角度。
總之,Open API 的設計是很難形成定論的一個東西,我在介紹自身產品最終採用的 Open API 規範之前,會先來聊一下大家耳熟能詳的一些概念,例如 restful。
restful 規範之爭
有人的地方就會有江湖。
有代碼的地方也是如此。
如果你在碼圈混,一定聽說過 restful 規範:
-
增刪改查應分別聲明爲:POST、DELETE、PUT、PATCH、GET
-
不應該出現動詞,動詞統一由 HTTP Method 表示
-
體現出 “資源” 的抽象
-
利用 pathVariable,queryParam,header,statusCode 表達很多業務語義
restful 規範看似美好,但如果你真正嘗試過落地,一定會遇到一些類似的問題:
-
以用戶登錄接口爲例,此類接口難以映射到資源的增刪改查
-
以查詢最近 7 個小時內的接口請求錯誤率爲例,衍生到諸如 graphQL 這類複雜的查詢場景,往往需要 json 結構,GET 是無法實現這一點的,只有 POST 纔可以傳遞
基於此,restful 規範逐漸有了反對的聲音:
-
強行讓所有的事物都 “資源” 化一下,有悖於開發常識,接口不一定都能夠通過簡單的增刪改查來映射
-
複雜的查詢語義不一定能夠用 GET 表達
restful 風格的擁躉者,不乏對這些反對言論進行抨擊,社區中不免有 “拒絕 restful 風格的主要是低水平不思進取的架構師和前後端程序員們,不會設計是人的問題,不是規範的問題” 此類的言論。同時對 restful 進行了昇華:複雜參數的檢索問題,在 restful 語義中本就應當歸類爲 post,因爲該行爲並不是對資源的定位(GET),而是對資源的檢索(POST)
這顯然刺激了 restful 風格反對者的神經,不屑道:呵,愚蠢的 restful 原教旨主義者呀。
不知道你是 restful 的擁躉者還是反對者?亦或是,中立者。
restful 之爭暫時到此爲止,這番爭論純屬虛構,看官不必計較。無論你如何看待 restful,下面我的論述,你都可以作爲一箇中立者,否則效果減半。
ROA 與 RPC
API 設計並不只有 restful 一種規範,在更大的視角中,主流的 API 設計風格其實可以分爲
-
面向資源的設計,即 ROA(Resource oriented architecture)
-
面向過程的設計,即 RPC(Remote Procedure Call)
restful 便是 ROA 風格的典型例子,而 RPC 風格則相對而言不太容易被大家熟知,但實際上可能大多數的系統的接口是 RPC 風格的,只不過 RPC 風格這個概念不太爲人所知。
以用戶模塊的 CRUD 爲例,對比下兩個風格:
ROA 風格
創建用戶(POST)
Request:
POST /users
{"name": "kirito", "age": 18}
Response:
HTTP 201 Created
{"id": 1, "name": "kirito", "age": 18}
查詢用戶(GET)
Request:
GET /users/1
Response:
HTTP 200 OK
{"id": 1, "name": "kirito", "age": 18}
查詢用戶列表(GET)
Request:
GET /users
Response:
HTTP 200 OK
{[{"id": 1, "name": "kirito", "age": 18}], "next": "/users?offset=1"}
創建 / 修改用戶 (PUT)
Request:
PUT /users/1
{"name": "kirito", "age": 19}
Response:
HTTP 200 OK
{"id": 1, "name": "kirito", "age": 19}
修改用戶(PATCH)
Request:
PATCH /users/1
{"age": 20}
Response:
HTTP 200 OK
{"id": 1, "name": "kirito", "age": 20}
刪除用戶(DELETE)
Request:
DELETE /users/1
Response:
HTTP 204 No Content
ROA 風格和 restful 規範說明的是一回事,爲方便其與 RPC 風格接口的對比,特此說明上面示例的一些值得關注的點:
-
使用 HTTP 響應碼(200,201,204),完成 HTTP 語義與業務語義的映射,異常流也出現 404,401 等情況(出於篇幅考慮,本文未做異常流的介紹)
-
PATCH 部分修改資源,請求體是修改部分的內容;PUT 創建 / 修改資源,請求體是新資源全部的內容
-
id 是資源定位符,而 age、name 則爲屬性
RPC 風格
創建用戶(POST)
Request:
POST /user/createUser
{"name": "kirito", "age": 18}
Response:
HTTP 200 OK
{"code": 0, "message": "", "data": {"id": 1, "name": "kirito", "age": 18}}
查詢用戶(POST)
Request:
POST /user/getUser
{"id": 1}
Response:
HTTP 200 OK
{"code": 0, "message": "", "data": {"id": 1, "name": "kirito", "age": 18}}
查詢用戶列表(POST)
Request:
POST /user/listUsers
Response:
HTTP 200 OK
{"code": 0, "message": "", "data": {"user": [{"id": 1, "name": "kirito", "age": 18}], "next": "/user/listUsers?offset=1"}}
修改用戶 (POST)
Request:
POST /user/modifyUser
{"id": 1, "name": "kirito", "age": 19}
Response:
HTTP 200 OK
{"code": 0, "message": "", "data": {"id": 1, "name": "kirito", "age": 19}}
修改用戶名稱 (POST)
Request:
POST /user/modifyUserAge
{"id": 1, "age": 20}
Response:
HTTP 200 OK
{"code": 0, "message": "", "data": {"id": 1, "name": "kirito", "age": 20}}
刪除用戶(DELETE)
Request:
POST /user/deleteUser
{"id": 1}
Response:
{"code": 0, "message": ""}
RPC 風格不像 restful 一類的 ROA 風格存在一些約定俗成的規範,每個業務系統在落地時,都存在差異,故此處只是筆者個人的經驗之談,但願讀者能夠求同存異:
-
user 爲模塊名,不需要像 ROA 風格使用複數形式
-
使用明確的動賓結構,而不是將 CRUD 映射到 HTTP Method,HTTP Method 統一使用 POST,查詢場景也可以使用 GET
-
返回值中攜帶 code、message 和 data,來映射響應狀態及響應信息,一般可以自行定義 code 的狀態碼,本文使用 0 標識請求成功,message 僅在業務響應失敗時有意義,data 代表業務響應結果
如何選擇 RPC 和 ROA,則需要根據產品自身的業務情況進行決策。有如下的指導原則:
-
有複雜業務邏輯的 API ,無法使用簡單的增、刪、改、查描述時宜使用 RPC 風格。
-
如果業務所屬行業標準要求 restful 風格 API 或 ROA 能夠滿足業務需求,宜使用 ROA 風格。
AWS 主要採用 RPC 風格,Azure、Google 主要採用 ROA(restful)風格,阿里雲 OpenAPI 同時支持 RPC 和 ROA,以 RPC 爲主。
儘管規範是無罪的,但在 ROA 風格在實踐過程中,我還是見識過不少 “坑” 的:
-
要求資源先行,即先設計資源,後設計接口,對軟件開發流程要求較高
-
錯誤的 ROA 設計案例 1:tomcat 等應用服務器在處理 DELETE 方法的 HTTP 請求時,默認不允許攜帶 request body,需要顯式開啓,導致刪除失敗。(此案例爲設計者的問題,複雜的刪除場景,不應當映射成 DELELE,而應改成 POST,DELETE 不應當攜帶 request body)
-
錯誤的 ROA 設計案例 2:restful 路徑中攜帶的參數,可能會引發正則匹配的問題,例如誤將郵箱作爲路徑參數,或者多級路徑匹配的衝突問題(此案例爲設計者的問題,複雜的查詢場景,不應當映射成 GET,而應改成 POST,path 中只應該出現資源定位符,而不應當攜帶屬性)
-
響應碼爲 404 時,較難區分是真的 path 不存在,還是資源不存在
-
不利於對接網關等需要配置路由轉發的場景
CSB 的 Open API 規範希望滿足以下的需求:
-
後端開發設計接口時,有明確的設計思路,不至於因爲一個接口到底用 POST 還是 GET 實現而糾結,不用花費太多時間在資源的抽象上(這並不是說明資源是不需要被設計的)
-
前端開發對接接口時,能夠較快地與後端協同,並且利於前端接口的封裝
-
用戶對接 Open API 時,整體風格一致,模塊清晰
綜上,在設計風格選擇上,我計劃採取 RPC 的設計規範。總結一下 RPC 風格的優勢:
-
API 設計難度較低,容易落地
-
阿里雲大多數成熟的 IAAS 層產品使用 RPC 規範
-
適合複雜業務場景
一個詳細的 RPC 接口文檔示例
創建服務
請求參數
返回參數
請求示例
POST /service/createService
Request:
{
"name": "httpbin",
"protocol": "http",
"lb": "random",
"upstreamType": "fixed",
"nodes": [
{
"host": "httpbin.org",
"port": "80",
"weight": "1"
}
],
"gatewayId": "gw-1qw2e3e4"
}
Response:
{
"code": 0,
"message": "",
"serviceId": "s-1qw2e3e4"
}
API 命名規範
-
API 應使用拼寫正確的英文,符合語法規範,包括單複數、時態和語言習慣
-
不能出現多個含義相近但功能無實際差別的 API,如同時存在 /user/getUser 和 /user/describeUser
-
語言習慣:禁止使用拼音
-
如下常見場景的命名規則是固定的
-
日期時間類型的參數應命名爲 XxxxTime。例如:CreateTime
-
常用操作名稱規範
-
create:創建
-
modify:變更
-
delete:刪除
-
get:獲取單個資源詳情
-
list:獲取資源列表
-
establishRelation:建立資源關係
-
destroyRelation:銷燬資源關係
總結
以本文推崇的一條規範爲例:"所有接口全部使用 POST",這不是爲了遷就低水平不思進取的架構師和前後端程序員們(我在社區論壇上看到的言論),而是爲了提高開發效率,降低溝通成本,降低運維和錯誤定位成本,把瞎折騰的成本,投入到了其他比如業務架構設計,測試體系,線上監控,容災降級等領域上。
接口規範也並非我總結的那樣,只有 RPC 和 ROA,也有一些言論將 GraphQL 單獨歸爲一類 API 設計風格,用於複雜查詢場景,有興趣的同學可以參考 es 的 API 文檔。
綜上,我計劃採用 RPC 的 API 設計風格。
參考資料
kong:https://docs.konghq.com/gateway/2.8.x/admin-api/
google restful api design:https://cloud.google.com/apis/design?hl=zh-cn
https://www.zhihu.com/question/336797348
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/NYDNOKl7RgH71njqcHueoA