在上一篇文章中,我們講解了什麼是 api,什麼是 sdk:
https://www.cnblogs.com/tanshaoshenghao/p/16217608.html
今天將來到我們萬丈高樓平地起系列文章的第二篇:如何編寫 api 文件?
咳咳,其實寫 api 文件這個事情也沒有一個統一的標準,寫這篇文章更多地是分享與記錄自己的一些心得體會。
曾經有大佬和我說,通過一個人的 api 設計大概率能看出他的工程水平,並且推薦我去看一些優秀的 api 設計,比如 aws 的 api。
後來我感覺,學 api 有點像研習四書五經,入門後誰都能吟兩句,至於能否真正消化理解及能發揮多大價值,不同人有不同的造化。
所以,或許寫 api 就像比武功吧,一招一式耍出來後,花架子或許能唬住大眾,高手之間的交流則只可意會不可言傳......
呃不過阿菌可能連花架子也算不上,所以就不在大家面前賣弄如何設計 api 了。
但關於怎麼寫 api 文件這樣的業務小常識,感覺還是可以分享一下的!
讓人又愛又恨的 api 文件
首先我想說的是,脫離現實談理想都是耍流氓,據我自己的切身體會,這世界上大概率是沒有人喜歡寫 api 文件的。
畢竟工作本來就累,這程式碼一寫完,測試不找碴已經是萬幸了,要順便改上幾個 bug,誰還記得維護 api 文件嘛。
但是咧,一旦我們需要去維護別人留下的程式碼,又或者需要翻看自己幾個月前堆的屎山,立馬就會哭爹喊娘:這兔崽子為啥不留 api 文件...... T_T
所以,為了方便自己,同時也能方便他人,我們還是要把編寫 api 文件這件事情放在心上。
放在心上則意味著放在首位,也就是說,當我們開始一項開發任務的時候,首先要把 api 定義好,並且把 api 文件完善細化好,讓文件先行!
api 文件一般是由服務端同學定義的,但最好能由前後端同學一起定初稿;一個人設計容易有疏漏與盲區,兩個人一起設計往往雙方都能得到進步。我自己在定義 api 的時候也常常會和前端同學交流一下想法,前端同學往往能給出一些建議,大家磨合後開發就會比較順暢。(一開始可能會在開發的過程中改動初始設計,慢慢設計多了,返工的概率就減少了)
api 文件需要說明的內容
下面就和大家介紹一下為一個 api 編寫文件需要包含哪些資訊,我們以上一篇文章中的介面為例,也就是我們的雲你好 api:
我在想有沒有一種可能,等我的系列文章更新完後,雲你好服務就正式上線商用了...... 別小瞧它只是雲你好哦,阿菌會在這系列文章裡分享所有自己在當前工作中的心得體會與思考,由淺入深,希望能給大家帶來一點點啟發,也歡迎各路大佬前來指點交流。
# 雲你好服務 API 介面回顧:
@app.get("/api/v1/hello")
def hello():
# 看使用者是否傳遞了引數,引數為打招呼的目標
name = request.args.get("name", "")
# 如果傳了引數就向目標物件打招呼,輸出 Hello XXX,否則輸出 Hello World
return f"Hello {name}" if name else "Hello World"
首先我們要給出描述資訊:
描述資訊:雲你好 api,大家可以通過雲你好,獲得一次來自雲端的問候
然後要給出 api 的請求方式:
請求方式:GET
接著要給出請求地址:
http://127.0.0.1:5000/api/v1/hello
然後要給出請求引數:
然後我們要給出返回引數:
為了讓 api 的呼叫者直接看懂,一定要記得給出響應示例,這裡的響應示例不能只包括成功的請求響應,也要包括請求失敗的響應:
狀態碼:200
"hello 阿菌"
狀態碼:400
"您的餘額不足了,不能打招呼了"
狀態碼:500
"sorry~伺服器開小差了"
簡單來說就是要把呼叫 api 各種可能出現的情況告訴使用者,我們順便完善一下雲你好的程式碼,讓雲你好服務可以容納以上述三種情況:
# 雲你好服務 API 介面
@app.get("/api/v1/hello")
def hello():
"""
首先判斷使用者的餘額夠不夠
假設我們已經寫好了判斷邏輯,
後面我們講 cookie,session,header 等知識的時候會補充
"""
try:
if not enough_money():
return "您的餘額不足了,不能打招呼了", 400
# 看使用者是否傳遞了引數,引數為打招呼的目標
name = request.args.get("name", "")
# 如果傳了引數就向目標物件打招呼,輸出 Hello XXX,否則輸出 Hello World
return f"Hello {name}" if name else "Hello World", 200
except Exception:
# 假設在使用雲你好服務的過程中出現了異常
return "sorry~伺服器開小差了", 500
請求響應設計小加餐
當然,雲你好只是個極簡的例子,企業開發中是不會直接返回字串的,一般會約定一個資料格式,比如狀態碼為 200 返回:
{
"items": ["xxx", "yyy", "zzz", ...],
"total": 10
}
狀態碼非 200 的響應,也就是請求發生錯誤的響應資料格式會是這樣的:
{
"msg": "sorry~ something went wrong..."
"msg_cn": "sorry~伺服器開小差了..."
"error_code": "20000"
"detail": {
"xxx": "...."
}
}
剛開始學程式設計的時候我不太明白,既然已經有了響應碼,比如 400,500,為什麼還要弄一個 error_code 呢?
其實,error_code 一般是用來判斷客戶端錯誤的,比如說我們設定找不到資源的狀態碼為 404,但我們的邏輯中很可能會有好幾段找資料的邏輯,比如使用者傳了使用者名稱,地址,手機,郵箱,我們逐個到資料庫中判斷這些資料是否存在,如果都用 404 作為響應,而且提示資訊都是"資源不存在",那麼定位錯誤就比較麻煩了。
有了錯誤碼,我們則可以根據每一種情況定義一個錯誤碼,比如:
以下均為 404 錯誤
* 使用者名稱不存在 - error_code: 10000
* 收貨地址不存在 - error_code: 10001
* 手機號不存在 - error_code: 10002
* 郵箱不存在 - error_code: 10003
這樣在開發聯調定位錯誤的時候是非常方便的,而且我們還可以根據不同的狀態碼,設定不同的報錯資訊。
不過,寫程式碼就像寫文章,其實形式多種多樣,沒有對錯,不必拘泥於一種形式。比如我見過只通過 error_msg 區分不同不同錯誤的,也挺好的。
如何讓 api 文件看起來更舒服?
上面我們介紹了為一個 api 編寫文件需要對外提供哪些資訊,雖然我們知道了要寫什麼,但是離寫出一個好看的 api 文件還是有一定距離的。
這裡的距離一共包括以下幾點:
- 我們每次寫 api 文件時的心情不一樣,有的時候我們心情特別好,寫 api 文件就會特別認真,會詳細且準確地描述每一個引數;但我們總會有心情不好的時候,為了在心情不好的時候也能對自己的行為做出約束,我們得想一些辦法。
- api 文件的樣式。如果僅僅用一個線上文件(甚至用word/pdf)寫 api,我們自己定義的格式通常是比較普通的,為了讓 api 文件的閱讀者(比如我們自己)有一個好心情,我們得想辦法弄個好看點的樣式。
- api 文件給出的示例要是能夠執行除錯就更好了。
這個時候我們可以使用一些外部的 api 文件軟體,幫助我們構建出一份漂亮的 api 文件。而 api 文件工具軟體,業內首推的就是 swagger 了,免費又好用。
下面我分享一下我是如何給雲你好服務接入 swagger api 文件的。
善用 Github 上的開源工具
由於雲你好是用 python 的 flask 框架搭建的(選擇 python 講解是因為 python 程式碼就像說大白話,配合註釋容易讓大家看懂),當我們想要使用某項主流技術的時候,首先可以去 Github 上搜一下有沒一些好用的腳手架,這樣可以避免重複造輪子。
比如我們可以在 github 上搜尋 flask swagger,然後選擇按星星的數量排序。
可以看到,第一名的是一個基於 flask 二次開發的框架,雖然這個框架已經直接整合了 swagger,但是不太符合我們的需求,我們是希望能直接基於現有的雲你好程式碼接入 swagger,而不是換一個新框架。
接著我們看第二個快 3k 星星的 flasgger 框架,點進去後直接看示例,看看如何能把軟體跑起來。
看完後發現 flasgger 貌似非常符合我們的需求,只要額外新增幾行程式碼就能跑起 swagger 了,於是我們把 swagger 接入我們的雲你好服務:
from flask import Flask, request
from flasgger import Swagger, swag_from
app = Flask(__name__)
swagger = Swagger(app)
# 雲你好服務 API 介面
@app.get("/api/v1/hello")
@swag_from('hello.yml')
def hello():
try:
if not enough_money():
return "您的餘額不足了,不能打招呼了", 400
# 看使用者是否傳遞了引數,引數為打招呼的目標
name = request.args.get("name", "")
# 如果傳了引數就向目標物件打招呼,輸出 Hello XXX,否則輸出 Hello World
return f"Hello {name}" if name else "Hello World", 200
except Exception:
# 假設在使用雲你好服務的過程中出現了異常
return "sorry~伺服器開小差了", 500
if __name__ == '__main__':
app.run()
swagger 可以通過 yml 格式描述一個 api 的詳細資訊,可以描述的 api 資訊包括且不限於我們上文提到的 api 文件需要說明的內容:
雲你好 api
---
parameters:
- name: name
in: query
type: string
responses:
200:
description: "請求成功"
schema:
examples: "Hello 阿菌"
type: string
400:
description: "客戶端錯誤"
schema:
examples: "您的餘額不足了,不能打招呼了"
type: string
500:
description: "服務端錯誤"
schema:
examples: "sorry~伺服器開小差了"
type: string
然後我們可以把雲你好服務執行起來,訪問 http://localhost:5000/apidocs
後能得到一個這樣的介面:
我們甚至可以在 swagger 介面上執行除錯雲你好的 api 介面:
那有了這份 api 文件之後,內部的開發協作就會變得非常方便,大家按照約定呼叫介面就能使用相應的服務了。
api 文件部署小加餐
api 文件其實是語言無關的,不管我們用的是 java、python、golang 什麼語言都好,想要得到這樣的一份 api 文件,關鍵在於編寫好 yml 檔案,描述好我們每一個 api 的用途,請求引數以及響應。
由於我們的雲你好服務只是一個非常簡單的服務端程式,為了演示方便,阿菌就直接把 swagger 跑在服務端了。
事實上,企業級應用是很少會這樣部署 api 文件的,我先帶大家看一下 flasgger 大概是怎麼把 swagger 整合到我們的後端伺服器的,我們可以從執行情況進行反推。
首先我們重啟一次伺服器,訪問一次 http://localhost:5000/apidocs
,並且開啟控制檯,看一下後端發生了什麼:
/Users/game-netease/Desktop/flaskProject/venv/bin/python -m flask run
* Serving Flask app 'app.py' (lazy loading)
* Environment: development
* Debug mode: off
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
127.0.0.1 - - [13/May/2022 08:48:33] "GET /apidocs/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/lib/jquery.min.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui-bundle.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui.css HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui-standalone-preset.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:34] "GET /apispec_1.json HTTP/1.1" 200 -
從 flask 為我們列印的請求路徑可以看出,flasgger 其實是把一系列 swagger 的靜態資源整合到了我們的後端服務中,並且把對應的路由註冊到了後端框架的路由表裡,當我們訪問 /apidocs/
這樣的路徑時,它返回了相應的靜態資源給瀏覽器渲染。
然而,大量的靜態資源傳輸是會佔用網路頻寬的,在企業中,如果有大量的靜態資源業務一般會配備有專門的靜態資源服務,處理這類請求的服務一般對網路頻寬要求比較高,對算力的要求相對會低一些。市面上也有不少和靜態資源相關的技術服務,比如 CDN 內容分發網路,一些具備冷熱儲存功能的資料庫服務等等。
所以,對於雲你好服務來說,假設我們架設了靜態資源伺服器,更合理的 api 文件部署方案或許該是這樣的:
好了,"雲你好"服務第二集暫時就更新到這裡了,這個系列的文章會比較長,希望在一步步搭建雲你好服務的過程中,繼續和大家分享我在寫程式碼過程中的一些思考,下一期更精彩!