小聊 API
參考 How NOT to design APIs 進行總結
作者的朋友的專案正在使用 Beds24 這套系統,這套系統主要就是用來做預定的,連線的是BookingAirBnB上的房源資訊。而這個專案的功能就是從一些訂房平臺上獲取可供預定的房間和日期。
但是這個提供的介面服務存在著很多問題,所以作者就拿他作為反面教材愉快地吐槽了一番。
我們先來看一下一個典型的getAvailabilities介面,介面連線 在這裡 。
我們暫且就稱為介面A。介面 A 通過引數獲取可用房間和時間,所有可選引數如下所示:
{ "checkIn": "20151001", "lastNight": "20151002", "checkOut": "20151003", "roomId": "12345", "propId": "1234", "ownerId": "123", "numAdult": "2", "numChild": "0", "offerId": "1", "voucherCode": "", "referer": "", "agent": "", "ignoreAvail": false, "propIds": [ 1235, 1236 ], "roomIds": [ 12347, 12348, 12349 ] }
我們就來細細評鑑一下這個引數中有哪些不合理的地方。
1.日期
我們可以看到,在請求報文中, checkIn
, lastnight
, checkOut
,都使用了黏在一起的年月日形式,YYYYMMDD,雖然這種形式的可讀性也不差,但是對於跨語言解析的便利性上就不好說了。
其實作為時間,用 ISO8601 (YYYY-MM-DD),這樣的通用標準形式會更加便捷。不論是從 encode 還是 decode 來說,比如 javascript 就能用 Date.parse(YYYY-MM-DD)
直接解析出所代表的 unix timestamp。
2.ID與數字
如果一個數字代表是 ID,即用來描述物件唯一的數字。那麼最好是用 string 型別的,比如請求結構體
roomIds
, propIds
。
如果一個數字是用來描述數量或者值的,那不要使用 string,否則會有歧義並且不好計算。如上頭的 numAdult
, numChild
。
雖然上面數字與 ID 的區分並不是強制要求,但是注意,不論你使用哪一種形式,保持統一。你可以看到: roomId
和 roomIds
,明明只是一個是另一個的集合,缺使用了兩種表示形式,這不免讓人誤解。
在說完了 Request 部分,我們看下返回的 Response。
我們按照
{ "checkIn": "20190501", "checkOut": "20190503", "ownerId": "25748", "numAdult": "2", "numChild": "0" }
作為請求,得到了如下的 Response:
{ "10328": { "roomId": "10328", "propId": "4478", "roomsavail": "0" }, "13219": { "roomId": "13219", "propId": "5729", "roomsavail": "0" }, "14900": { "roomId": "14900", "propId": "6779", "roomsavail": 1 }, "checkIn": "20190501", "lastNight": "20190502", "checkOut": "20190503", "ownerId": 25748, "numAdult": 2 }
3.資料結構
我們用心體會一下這個 response 是幹嘛的...
返回的內容包括了兩塊:
- 客戶的房間資產列表
- 我們請求的引數以及相應的檢索返回
如果你是前端的話,肯定心裡已經開始 mmp 了。
這個 response 把所有資料都捏在了頂層資料結構中。如果我們只想獲取房間列表資訊的話,我還必須遍歷所有資料,並自己做一個篩選??這顯然十分不合理,我們再來看看房間列表。
"14900": { "roomId": "14900", "propId": "6779", "roomsavail": 1 },
他把 roomId
作為了map的 key。這看上去有點重複,我們很少會在 json 傳輸過程中使用把關鍵結果資料作為key的字典型別(dictionary)。對於資料處理方來講,只對 value
中的資料進行處理是最方便的。你額外來一個關鍵資訊,還是作為 key,要不是 value 中包含了 roomId
,真的很 cd。
另外,我們看到 roomsavail
, roomsavail
在值為0,和值為1的時候的資料型別是不一樣的(應保持一致),這個也需要注意。
既然我們吐槽完畢,那就看看推薦的資料結構應該是怎麼樣的:
{ "properties": [ { "id": 4478, "rooms": [ { "id": 12328, "available": false } ] }, { "id": 5729, "rooms": [ { "id": 13219, "available": false } ] }, { "id": 6779, "rooms": [ { "id": 14900, "available": true } ] } ], "checkIn": "2019-05-01", "lastNight": "2019-05-02", "checkOut": "2019-05-03", "ownerId": 25748, "numAdult": 2 }
增加了 properties
資料塊,很明確區分了資料內容,方便取用。
4.錯誤處理
萬能的 200
如果你知道我在說什麼的話,你應該也經歷過這樣的問題~
我們看看 Beds24 介面的錯誤返回碼:
他們定義了一套自己的錯誤碼和錯誤返回資訊。但是這些都是封裝在200返回碼的 Response 中的。
這裡我想說一下,我不太同意原作者的觀點,這也是國內和國外的風格差異。
遵循開發效率優先的原則,我認為錯誤處理的返回應該
對外 API,統一返回200,並註明錯誤在自定義 errorCode 中
。這樣的好處是,API對接方可以很容易區分是連線層的問題還是業務層的問題。
而 內部介面竟可能使用 HttpStatusCode
,前端同學可以更方便獲取異常。
4.呼叫須知
原文中,作者對介面呼叫方給到的呼叫建議有些意見,並不涉及太多技術細節,只是提出了介面設計原則,設計要求和思路。
我們就來簡單過一下...
呼叫須知是介面提供方給到的一些呼叫意見和規範,我們看下 Beds24的呼叫須知:
- Calls should be designed to send and receive only the minimum required data.
- Only one API call at a time is allowed, You must wait for the first call to complete before starting the next API call.
- Multiple calls should be spaced with a few seconds delay between each call.
- API calls should be used sparingly and kept to the minimum required for reasonable business usage.
- Excessive usage within a 5 minute period will cause your account to be blocked without warning.
- We reserve the right to disable any access we consider to be making excessive use of the API functions at our complete discretion and without warning.
我們大概分析下,1和4的意見還是挺在理的,儘可能保證請求資料的最小化,合理化。
第二條:每次只允許單個 api 的請求,只有當上一個請求結束後才能開始下一個請求。
我覺得應該是文件好久沒更新了...在當下併發與並行橫行的年代,這樣的介面限制有點過時。如果你是在搭建REST API,那記住,這個API是無狀態的。這也是 REST API 在雲應用中被廣泛使用的原因,呼叫者無需在意是否一次一請求,一旦程式出現問題,或者上一個請求超時、異常,完全可以立即重新請求。要求呼叫者每次只能請求一次也是一個無理要求。
第三條:複合請求每次要排隊,每次間隔幾秒鐘。
第五條:五分鐘內過高的請求頻率會導致賬戶凍結。
第六條:介面提供方保留凍結介面使用者的賬戶的權利。
有點霸王條約,而且條件說明不清晰,包括凍結條件,同時在介面請求限制上有點嚴格。
5.文件展現
推薦使用介面展示的工具, Swagger 或者 Apiary 。
Beds24的介面文件可謂是十分原汁原味了,沒有主要的 index 引導,很容易讓開發者看得雲裡霧裡。
6.安全
這裡原作者主要吐槽了 Beds24 介面不需要鑑權也能夠呼叫的 BUG。
既然說到了安全,我們就稍微延展一下。
如今常見的 API 呼叫鑑權方式主要有以下幾種:
- Basic Authorization
- Oauth Token
- Session
Basic Authorization
鑑權的方式相對簡單,就是在 Http Header 增加經過base64運算後的 username + password。相信你能夠發現,這樣的鑑權方式沒有過期時間,傳輸內容安全驗證的措施,所以如果你的 api 需要上述兩個方面的保證的話,不建議使用這種鑑權方式。
Session
通過一個唯一的 key,驗證在共享記憶體中的使用者狀態。單例項應用常用的解決方案。唯一的問題在於,共享記憶體中的隔離機制,因為是共享 session 的,所以 session 的讀取安全性很重要。
Oauth Token
不同與 Session, Oauth Token(下面簡稱 token)不需要在共享記憶體中記錄使用者狀態。token 本身就會包含使用者的基本資訊、許可權範圍、有效時間,當然這些都是經過加密的。呼叫者將 token 放在 http header 中進行請求。一般提供 Oauth 驗證方式的介面,都會附加一個類似重新整理 refresh token 的介面,用來解決 token 過期的問題。
*關於詳細的鑑權方式和實現大家可以 Google一下,或者 github 一下。
提供一個優質、可用、易讀的 API 也是每個介面提供方的責任。記得前段時間Ant Design的彩蛋事件,所引出的“開源即責任”,更何況是收費模式下的介面服務呢?
文章末尾,作者好心勸告了大家,如果遇到類似Beds24的介面,請遠離,除非這個介面提供的服務是不可替代的,因為這樣的介面會讓你投入更多的理解、糾錯成本。當然,我們作為開發者,自己也要謹記在設計 API 的時候不要出現上述的一些問題。