REST 深度进阶
最近團隊人數在擴大,才發(fā)現(xiàn),REST 這個出來很多年頭的東西,居然還有人用不好。
說起來,REST 出現(xiàn)已經很久了。
從早期的三層架構,到現(xiàn)在的多層、微服務,核心內容之一就是 API --- 從非常簡單的 API,到多設備多用途的 API,包括一些外接的三方,像 BAT 的公共服務,簡單的、麻煩的,都是 API。而這些 API,又基本上都是基于 REST 的。
今天我們不去詳細解釋 REST,只說說 REST 應用中間的一些要點。
REST 應用之多,是有他的原因的。他很容易理解,很靈活,并且可以適用于任何規(guī)模的應用。
當然,REST 并不是唯一的規(guī)范,還有 SOAP、GraphQL。但是,這只是字面上的并列的規(guī)范。所有的規(guī)范用過了,你就會知道:SOAP 很笨重,有時候還很古怪:你需要花大量的心思去想接口的表示,而不是邏輯本身。至于 GraphQL,又延伸的太多了,居然需要調用 API 的客戶端去考慮和設計,這絕不是個好主意。
好吧,這個問題見仁見智,我們不展開討論。
不管怎么說,在我看來,REST 仍然是 API 接口規(guī)范的王者,并且不會在短時間內被取代。
在我的習慣中,使用 REST 會有以下幾個約束。
1. 使用 JSON 數據
別誤解,這是我的習慣,不是 REST 的。
REST 并沒有規(guī)定使用什么樣的格式來傳遞數據,XML 也行,JSON 也行。但是在我的團隊中,JSON 傳遞數據是一個硬性要求。
相比較而言,JSON 比 XML 有太多的優(yōu)勢了:
更易于使用、書寫和閱讀
更快,占用的內存空間更少
不需要特殊的依賴項或包來解析
主流的編程語言對 JSON 都有支持
如果不理解這些優(yōu)勢,沒關系,去網上隨便找一個 XML,試著自己解析一下看看。熟悉大廠的各種開放平臺的同學們也會有直覺的感覺:早期的 SOAP 和 XML,已經被逐步替換為了 REST 和 JSON。
此外,這里說的使用 JSON 數據,不僅僅是響應數據,還包括請求數據。不要使用 form-data 或者 x-www-form-urlencoded 發(fā)送數據,轉成 JSON 發(fā)送,會更容易閱讀、編寫、測試和管理。
真心的,如果你這么做了,我會替所有開發(fā)的同學們感謝你。
2. 認真對待方法
想一下,你有沒有見到過只用 GET 方法來處理一切事情的 API?
這并不是不可以,只不過,這樣的寫法說明沒有深入理解這個工具,以及 HTTP 的準確的工作方式。要知道,HTTP 中每個方法都被設計為處理特定的工作和內容。
這兒我逐個說說:
GET?- 在僅僅用于讀數據時,應該用 GET。不寫入、不更新,只讀取數據。這個概念很簡單。而且,在這個前提下,相同的請求一定會返回相同的結果。
POST?- 看字面的意思就明白,就是存儲一些東西,像是在數據庫中創(chuàng)建一條記錄、在某處寫入一些內容。通常來說,可以選擇很多種方式 POST 數據:multipart/form-data、x-www-form-urlencoded、application/json 或者 text/plain,等等,很多。不過,我們要求只使用 application/json 方式,這樣做可以保持開發(fā)和調用的一致性。
PUT?- 字意就是更新內容。所以當我們需要更新數據時,就需要定義為 PUT 方法。當然,也可以用來創(chuàng)建新數據。
DELETE?- 刪除,很好理解。
PATCH?- 打補丁,對于已經存在的數據進行更新操作。這個跟 PUT 有一點點區(qū)別,通常 PATCH 是有范圍的,更新需要更新的內容,而 PUT 更多時候是更新整個數據。
當然,在某些文章里,還會有 OPTIONS、HEAD、TRACE等等,這些用得少,就不說了。想了解的,可以去查 HTTP 相關的文檔。
說這么多,重要的是 --- 既然 HTTP 提供了這樣的方法定義,我們完全可以把任何 CRUD 的操作對映到這些方法,而不是只用 GET,這決不是一個好習慣。
3. 注意語義
在團隊開發(fā) API 時,有一個嚴格的要求,就是 API 名稱需要有語義感。語義感這個詞是我自己生造的,不是什么高大上的東西,就是要求寫的 API 名稱能使用正確的英文和次序,能夠讓人看得懂。都 9021 年了,居然還有人用拼音首字符,說出來你敢信嗎?
在我看來,所有的 API 都應該可以在不看注釋和說明的情況下被調用方理解,從調用端點,到參數,和 JSON 的鍵。
這兒,我參考了國外的一些規(guī)則。規(guī)則也很簡單:
用名詞,別用動詞。想一下,上面列出的方法,本身就是動詞,比方說:GET /clients,就很好理解,如果換成 GET /getClients,總覺得怪怪的。
一定要準確使用單數和復數,針對一條數據,就用單數;針對多條數據,就一定用復數。感覺一下 GET /client 和 GET /clients 的區(qū)別。當然,對于單個數據來說,通常還需要某種 ID 的存在:GET /client/id。
下面用一些例子來理解一下這個規(guī)則。
//?好的方式 GET?/clients POST?/clients GET?/client/23 PUT?/client/23 DELETE?/client/23 GET?/client/23/comments//?不好的方式 GET?/clients/23 GET?/listAllClients POST?/client/create PUT?/updateClient/23 GET?/clientComments/23這兒要多說兩句:規(guī)則只是規(guī)則,不用那么死板的去記。要把這種規(guī)則理解了,并習慣性地應用在編程的過程中,變成一種類似肌肉記憶的東西。
4. 隨時留心 API 的安全
就算你做得不是公開的 API,也一定要記著,使用某些手段,讓你的 API 安全起來。這是 API 編程一個基本的要求。
這又有幾個方面的要求:
1. 使用 HTTPs
HTTPs 已經出來非常久了,而且,如果你對接過大廠的 API,你會發(fā)現(xiàn)使用 HTTPs 是一個基本的要求。
HTTPs 提供了一種比 HTTP 更安全的方式,可以在基本網絡層面除去中間人攻擊,并加密調用端和 API 的通訊。在編程時,使用 HTTPs 是個成本最低但又確實有效的安全方式。
把使用 HTTPs 當成一個標準和習慣,有一天你會感謝自己的。
2. 從構建 API 開始,就要做到控制訪問
你看得沒錯,是從構建 API 開始。
不需要做得很麻煩,但要有控制,要能控制誰能訪問這個 API。通??梢韵燃尤胍粋€簡單的 JWT Auth,等 API 成形后,再轉為 OAuth。目的很簡單,就是控制訪問。如果真出現(xiàn)了 API 被攻擊什么的,簡單地關閉暴露的密鑰就可以了。當然,我們還可以用密鑰來跟蹤 API 的調用,包括調用量、調用異常等。
3. 小心對待敏感數據
API 代表了網絡,代表了通訊。在網絡和通訊上,傳遞敏感數據一定要小心再小心。我們前邊提到了一定使用 HTTPs,也是因為這個。如果不想面向監(jiān)獄編程,一定要確保這些敏感數據通過正確的方式,給到正確的調用方。
看了一眼數據,就被追了刑責,這是我身邊的真事。
4. 確保運行環(huán)境的安全
網關、防火墻,有就用上,別因為麻煩就關掉。更深的內容,可以扔給運維,但基礎的部分,自己要懂要會。
5. 版本控制
API 疊代升級,是每個開發(fā)的會面對的事。有時候,升級僅僅是邏輯的改變,而更多時候,是會改變輸入輸出結構的。這種情況下,保持和維護 API 的版本很重要。作為后端開發(fā)人員,我們無法保證調用端會隨時同步進行相應的改動。極端情況下,改變內部邏輯,也有可能影響到調用端。
API 版本控制,不用猶豫,馬上開始使用。不要覺得某個 API 比較小,或者調用端少,就不去做。記著,任何的代碼改動,對于不更新應用或其它內容的調用者來說都是有風險的。你不僅需要確保你的代碼不會破壞任何東西或任何人,還需要知道某個應用版本的表現(xiàn)。這件事一點都不好玩。
關于 API 版本控制的詳細實現(xiàn),我前邊一篇推文,可以去看看。傳送門
至于版本的方式,倒是不那么重要,可以看個人的習慣,v1、v2、v3也可以,v1.0、v1.1、v1.2也可以。按照微軟的建議,是采用 Major.Minor.Patch 的方式。不過我自己覺得帶上 Patch 部分有點太長了。
所以,在我的習慣中,應用版本控制后,API 的 URL會是這樣的:
GET?/v1.7/clients POST?/v1.7/clients GET?/v1.7/client/23 PUT?/v1.7/client/23 DELETE?/v1.7/client/23 GET?/v1.7/client/23/comments聽我的,馬上開始 API 的版本控制。
6. 保持響應的一致
一致性是好的 API 的優(yōu)秀品質。開發(fā)中,我們應該在各種方面做到一致,包括命名、URI、請求、響應等。而在這里面,響應的一致性是我對團人的一個硬性要求。
API 是要讓別人去調用的。保持資源響應的一致,是對調用者最大的善意。在某個壇子上,我看到過建議每個端點返回不同資源結構的說法。如果你也看到過類似的內容,忘了它,那是錯的。
記著這句話:保持資源響應的一致,是對調用者最大的善意。
API 開發(fā)時,盡可能發(fā)送相同的響應結構。如果沒有數據,就將其作為空值、空對象或空數據發(fā)送。
我們拿論壇的文章結構舉個例子。
文章數據的結構通常是這樣(有簡化,不要糾結):
{"title":?"文章標題","description":?"文章內容","comments":[{"text":?"回復1","user":?"張三"},{"text":?"回復1","user":?"張三"}] }如果需要返回一條數據,并且要列出評論時,結果會是這樣:
{"message":?"fetch?data?successed","status":?true,"article":{"title":?"文章標題","description":?"文章內容","comments":[{"text":?"回復1","user":?"張三"},{"text":?"回復1","user":?"張三"}]} }如果需要返回一個文章列表,并且沒有評論時,會是這樣:
{"message":?"fetch?data?successed","status":?true,"articles":[{"title":?"文章標題1","description":?"文章內容1","comments":?[]},{"title":?"文章標題2","description":?"文章內容2","comments":?[]}] }看到了吧?這樣的方式下,我們對于里面元素 article 里結構是完全一樣的,而對于整個返回結構,也是相似的。
堅持這樣做,可以為自己和他人節(jié)省大量的時間。
7. 重視出錯后的返回信息
API 開發(fā),應該既能處理正確的請求,也能處理錯誤的請求。錯誤的請求并不可怕,可怕的是你沒有考慮到,或者考慮到了,但沒有給到調用端足夠的細節(jié)。
在 API 返回中,很多人在這里會忽略 HTTP 的狀態(tài)代碼,也就是 HttpStatus。
HTTP 協(xié)議,為我們定義了超過 50 種不同的狀態(tài)代碼,涵蓋了幾乎所有的場景。每個代碼都有獨特的含義,應該在獨特的場景中使用。這個內容網上有很多,我就簡單列一下:
1xx?- 信息性響應代碼,簡單說就是一個狀態(tài)通知。
2xx?- 成功響應代碼。所有的成功都會在這個范圍。通常我們見到的是 200,但也有別的成功情況。
3xx?- 重定向響應代碼。請求被服務器重定向到另一個 URL,就會有這個返回。
4xx?- 客戶端錯誤響應代碼。最常見的是 400,請求協(xié)議格式或內容錯誤。
5xx?- 服務器錯誤響應。最常見的是 500,服務端程序,也就是 API 的內部,有內存溢出或異常拋出。
開發(fā)中,我們可以充分并準確使用這些狀態(tài)碼。這樣,所有的開發(fā)人員,會在相同的認識層次上理解問題的狀態(tài)和原因,從而使得 API 變得普遍易懂、一致和標準。
這不是 REST 的標準,但應該作為我們開發(fā) REST 的標準。
有了狀態(tài)碼,這只是第一步。當運行出錯時,我們需要向調用端提供盡可能多的細節(jié)。當然,這并不容易,我們需要能夠考慮并預測 API 會如何出錯,調用者會做什么,不會做什么。所以,通常一個 API 第一步是進行嚴格的請求數據驗證:數據是否存在、值是否在我們期望的范圍內、是否可以將他們存入數據庫。
拿上面的例子來說,GET /client/23,取 clientId = 23 的數據,我們需要做以下的工作:
檢查請求是否有 clientId 參數,如果沒有,應該是一個 400 的狀態(tài)
檢查傳入的 clientId = 23 的記錄是否存在,如果不存在,返回響應 404
如果找到記錄,則返回響應 200
這只是一個簡單的例子,真實的編程時,需要考慮的會更多。
而且,除了狀態(tài)碼外,還要返回相應的錯誤消息,例如:輸入參數 clientId 沒有輸入、ID 為 23 的數據記錄不存在,等等。
重要的是,提供詳細的錯誤信息,可以幫助開發(fā)者和調用方了解到底什么地方發(fā)生了問題。
放心,調用者不會將這些信息顯示給最終用戶,但可以通過這些信息來快速的定位和解決問題。
8. 盡可能優(yōu)化
在現(xiàn)代編程中,API 在體系中的角色,絕對是整個操作的大腦。所以,對于 API 的開發(fā),最基本的要求是快速和優(yōu)化,決不能讓 API 成為整個系統(tǒng)和生態(tài)的痛點。
要求就這么簡單。
我們可以做很多事情來確保交付一個具備良好性能和可伸縮性的 API。來看看我們能做什么?
首先是數據庫級別的優(yōu)化。通常說 API 慢的時候,十有八九與數據庫有關。糟糕的數據庫設計、復雜的查詢、緩慢的硬件環(huán)境,甚至缺乏緩存,都是慢的理由。所以,開發(fā)過程中,應該隨時關注并始終優(yōu)化數據庫結構、查詢、索引以及與數據庫交互的所有內容。
接下來是緩存。很多人不愿意用緩存,因為會將代碼變復雜。但是從實際效果上,越大、越復雜的系統(tǒng),越應該通過緩存?zhèn)鬟f數據。有時候,緩存數據庫查詢能減少 100% 的加載時間。而絕大多數數據,不會進行頻繁的改變。把緩存用起來,調用端的兄弟們,會把你當親兄弟的。
另一個影響性能的因素是 API 發(fā)送到調用端的數據量。要做到確保 API 只返回調用端需要的數據,而不是全部。如果可能,不要每次都返回完整的模型細節(jié)和關系。試一下,但要與響應中的返回模型保持一致。
最后,別忘了壓縮。如果可以,使用 Brotli,或者至少也使用 Gzip 來壓縮數據。簡單的配置,可以獲得減少 50-75% 的傳輸數據,多好!
9. 做個體貼的開發(fā)者
這個要求無關技術,但我還是想寫出來。
作為一個開發(fā)人員,我們要明白,項目不是一個人的事。當我們寫完最后一行代碼,提交并合并后,你可能會認為工作已經完成。但不是,對其他很多人來說,這才是個剛剛開始。
很多人在我們完成了工作后,才能開始他們的工作。所以,我們需要以多種方式準備 API。我們要確保 API 能正常工作,要有很好的文檔,更重要的事,我們需要準備好集成支持。不過文檔寫得有多好,在集成過程中,及以后的過程中,總會有問題,各種問題。
所以,設身處地的為他人著想,盡量讓他們的工作變得容易些。構建一個良好的API,遵循我們在這里定義的規(guī)則,編寫優(yōu)秀的文檔,并為所有人服務。
10. 寫完了
寫完了。
上面九條,是我團隊中執(zhí)行的標準和要求。
這里我也必須說, REST 本身并不是一個標準,所以也不會有人告訴你什么是對的,什么是錯的。開發(fā)的時候多想一下:作為開發(fā)人員,我們每天都在尋找使代碼更好、更漂亮、更高效的模式,那么為什么不在 API 中也做同樣的事呢?
全文完。
喜歡就來個三連,讓更多人因你而受益
總結
- 上一篇: SkyWalking配上告警更优秀
- 下一篇: Serilog 日志框架如何自动删除超过