REST 深度進階

最近團隊人數在擴大,才發現,REST 這個出來很多年頭的東西,居然還有人用不好。

說起來,REST 出現已經很久了。

今天我們不去詳細解釋 REST,只說說 REST 應用中間的一些要點。

REST 應用之多,是有他的原因的。他很容易理解,很靈活,並且可以適用於任何規模的應用。

當然,REST 並不是唯一的規範,還有 SOAP、GraphQL。但是,這只是字面上的並列的規範。所有的規範用過了,你就會知道:SOAP 很笨重,有時候還很古怪:你需要花大量的心思去想接口的表示,而不是邏輯本身。至於 GraphQL,又延伸的太多了,居然需要調用 API 的客戶端去考慮和設計,這絕不是個好主意。

好吧,這個問題見仁見智,我們不展開討論。

不管怎麼說,在我看來,REST 仍然是 API 接口規範的王者,並且不會在短時間內被取代。

在我的習慣中,使用 REST 會有以下幾個約束。

  1. 使用 JSON 數據

別誤解,這是我的習慣,不是 REST 的。

REST 並沒有規定使用什麼樣的格式來傳遞數據,XML 也行,JSON 也行。但是在我的團隊中,JSON 傳遞數據是一個硬性要求。

相比較而言,JSON 比 XML 有太多的優勢了:

如果不理解這些優勢,沒關係,去網上隨便找一個 XML,試着自己解析一下看看。熟悉大廠的各種開放平臺的同學們也會有直覺的感覺:早期的 SOAP 和 XML,已經被逐步替換爲了 REST 和 JSON。

此外,這裏說的使用 JSON 數據,不僅僅是響應數據,還包括請求數據。不要使用 form-data 或者 x-www-form-urlencoded 發送數據,轉成 JSON 發送,會更容易閱讀、編寫、測試和管理。

真心的,如果你這麼做了,我會替所有開發的同學們感謝你。

  1. 認真對待方法

想一下,你有沒有見到過只用 GET 方法來處理一切事情的 API?

這並不是不可以,只不過,這樣的寫法說明沒有深入理解這個工具,以及 HTTP 的準確的工作方式。要知道,HTTP 中每個方法都被設計爲處理特定的工作和內容。

這兒我逐個說說:

GET - 在僅僅用於讀數據時,應該用 GET。不寫入、不更新,只讀取數據。這個概念很簡單。而且,在這個前提下,相同的請求一定會返回相同的結果。

POST - 看字面的意思就明白,就是存儲一些東西,像是在數據庫中創建一條記錄、在某處寫入一些內容。通常來說,可以選擇很多種方式 POST 數據:multipart/form-data、x-www-form-urlencoded、application/json 或者 text/plain,等等,很多。不過,我們要求只使用 application/json 方式,這樣做可以保持開發和調用的一致性。

PUT - 字意就是更新內容。所以當我們需要更新數據時,就需要定義爲 PUT 方法。當然,也可以用來創建新數據。

DELETE - 刪除,很好理解。

PATCH - 打補丁,對於已經存在的數據進行更新操作。這個跟 PUT 有一點點區別,通常 PATCH 是有範圍的,更新需要更新的內容,而 PUT 更多時候是更新整個數據。

當然,在某些文章裏,還會有 OPTIONS、HEAD、TRACE 等等,這些用得少,就不說了。想了解的,可以去查 HTTP 相關的文檔。

說這麼多,重要的是 --- 既然 HTTP 提供了這樣的方法定義,我們完全可以把任何 CRUD 的操作對映到這些方法,而不是隻用 GET,這決不是一個好習慣。

  1. 注意語義

在團隊開發 API 時,有一個嚴格的要求,就是 API 名稱需要有語義感。語義感這個詞是我自己生造的,不是什麼高大上的東西,就是要求寫的 API 名稱能使用正確的英文和次序,能夠讓人看得懂。都 9021 年了,居然還有人用拼音首字符,說出來你敢信嗎?

在我看來,所有的 API 都應該可以在不看註釋和說明的情況下被調用方理解,從調用端點,到參數,和 JSON 的鍵。

這兒,我參考了國外的一些規則。規則也很簡單:

下面用一些例子來理解一下這個規則。

// 好的方式
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

這兒要多說兩句:規則只是規則,不用那麼死板的去記。要把這種規則理解了,並習慣性地應用在編程的過程中,變成一種類似肌肉記憶的東西。

  1. 隨時留心 API 的安全

就算你做得不是公開的 API,也一定要記着,使用某些手段,讓你的 API 安全起來。這是 API 編程一個基本的要求。

這又有幾個方面的要求:

1. 使用 HTTPs

HTTPs 已經出來非常久了,而且,如果你對接過大廠的 API,你會發現使用 HTTPs 是一個基本的要求。

HTTPs 提供了一種比 HTTP 更安全的方式,可以在基本網絡層面除去中間人攻擊,並加密調用端和 API 的通訊。在編程時,使用 HTTPs 是個成本最低但又確實有效的安全方式。

把使用 HTTPs 當成一個標準和習慣,有一天你會感謝自己的。

2. 從構建 API 開始,就要做到控制訪問

你看得沒錯,是從構建 API 開始。

不需要做得很麻煩,但要有控制,要能控制誰能訪問這個 API。通常可以先加入一個簡單的 JWT Auth,等 API 成形後,再轉爲 OAuth。目的很簡單,就是控制訪問。如果真出現了 API 被攻擊什麼的,簡單地關閉暴露的密鑰就可以了。當然,我們還可以用密鑰來跟蹤 API 的調用,包括調用量、調用異常等。

3. 小心對待敏感數據

API 代表了網絡,代表了通訊。在網絡和通訊上,傳遞敏感數據一定要小心再小心。我們前邊提到了一定使用 HTTPs,也是因爲這個。如果不想面向監獄編程,一定要確保這些敏感數據通過正確的方式,給到正確的調用方。

看了一眼數據,就被追了刑責,這是我身邊的真事。

4. 確保運行環境的安全

網關、防火牆,有就用上,別因爲麻煩就關掉。更深的內容,可以扔給運維,但基礎的部分,自己要懂要會。

  1. 版本控制

API 疊代升級,是每個開發的會面對的事。有時候,升級僅僅是邏輯的改變,而更多時候,是會改變輸入輸出結構的。這種情況下,保持和維護 API 的版本很重要。作爲後端開發人員,我們無法保證調用端會隨時同步進行相應的改動。極端情況下,改變內部邏輯,也有可能影響到調用端。

API 版本控制,不用猶豫,馬上開始使用。不要覺得某個 API 比較小,或者調用端少,就不去做。記着,任何的代碼改動,對於不更新應用或其它內容的調用者來說都是有風險的。你不僅需要確保你的代碼不會破壞任何東西或任何人,還需要知道某個應用版本的表現。這件事一點都不好玩。

關於 API 版本控制的詳細實現,我前邊一篇推文,可以去看看。傳送門

至於版本的方式,倒是不那麼重要,可以看個人的習慣,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 的版本控制。

  1. 保持響應的一致

一致性是好的 API 的優秀品質。開發中,我們應該在各種方面做到一致,包括命名、URI、請求、響應等。而在這裏面,響應的一致性是我對團人的一個硬性要求。

API 是要讓別人去調用的。保持資源響應的一致,是對調用者最大的善意。在某個罈子上,我看到過建議每個端點返回不同資源結構的說法。如果你也看到過類似的內容,忘了它,那是錯的。

記着這句話:保持資源響應的一致,是對調用者最大的善意。

API 開發時,儘可能發送相同的響應結構。如果沒有數據,就將其作爲空值、空對象或空數據發送。

我們拿論壇的文章結構舉個例子。

文章數據的結構通常是這樣(有簡化,不要糾結):

{
    "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 裏結構是完全一樣的,而對於整個返回結構,也是相似的。

堅持這樣做,可以爲自己和他人節省大量的時間。

  1. 重視出錯後的返回信息

API 開發,應該既能處理正確的請求,也能處理錯誤的請求。錯誤的請求並不可怕,可怕的是你沒有考慮到,或者考慮到了,但沒有給到調用端足夠的細節。

在 API 返回中,很多人在這裏會忽略 HTTP 的狀態代碼,也就是 HttpStatus。

HTTP 協議,爲我們定義了超過 50 種不同的狀態代碼,涵蓋了幾乎所有的場景。每個代碼都有獨特的含義,應該在獨特的場景中使用。這個內容網上有很多,我就簡單列一下:

1xx - 信息性響應代碼,簡單說就是一個狀態通知。

2xx - 成功響應代碼。所有的成功都會在這個範圍。通常我們見到的是 200,但也有別的成功情況。

3xx - 重定向響應代碼。請求被服務器重定向到另一個 URL,就會有這個返回。

4xx - 客戶端錯誤響應代碼。最常見的是 400,請求協議格式或內容錯誤。

5xx - 服務器錯誤響應。最常見的是 500,服務端程序,也就是 API 的內部,有內存溢出或異常拋出。

開發中,我們可以充分並準確使用這些狀態碼。這樣,所有的開發人員,會在相同的認識層次上理解問題的狀態和原因,從而使得 API 變得普遍易懂、一致和標準。

這不是 REST 的標準,但應該作爲我們開發 REST 的標準。

有了狀態碼,這只是第一步。當運行出錯時,我們需要向調用端提供儘可能多的細節。當然,這並不容易,我們需要能夠考慮並預測 API 會如何出錯,調用者會做什麼,不會做什麼。所以,通常一個 API 第一步是進行嚴格的請求數據驗證:數據是否存在、值是否在我們期望的範圍內、是否可以將他們存入數據庫。

拿上面的例子來說,GET /client/23,取 clientId = 23 的數據,我們需要做以下的工作:

這只是一個簡單的例子,真實的編程時,需要考慮的會更多。

而且,除了狀態碼外,還要返回相應的錯誤消息,例如:輸入參數 clientId 沒有輸入、ID 爲 23 的數據記錄不存在,等等。

重要的是,提供詳細的錯誤信息,可以幫助開發者和調用方瞭解到底什麼地方發生了問題。

放心,調用者不會將這些信息顯示給最終用戶,但可以通過這些信息來快速的定位和解決問題。

  1. 儘可能優化

在現代編程中,API 在體系中的角色,絕對是整個操作的大腦。所以,對於 API 的開發,最基本的要求是快速和優化,決不能讓 API 成爲整個系統和生態的痛點。

要求就這麼簡單。

我們可以做很多事情來確保交付一個具備良好性能和可伸縮性的 API。來看看我們能做什麼?

首先是數據庫級別的優化。通常說 API 慢的時候,十有八九與數據庫有關。糟糕的數據庫設計、複雜的查詢、緩慢的硬件環境,甚至缺乏緩存,都是慢的理由。所以,開發過程中,應該隨時關注並始終優化數據庫結構、查詢、索引以及與數據庫交互的所有內容。

接下來是緩存。很多人不願意用緩存,因爲會將代碼變複雜。但是從實際效果上,越大、越複雜的系統,越應該通過緩存傳遞數據。有時候,緩存數據庫查詢能減少 100% 的加載時間。而絕大多數數據,不會進行頻繁的改變。把緩存用起來,調用端的兄弟們,會把你當親兄弟的。

另一個影響性能的因素是 API 發送到調用端的數據量。要做到確保 API 只返回調用端需要的數據,而不是全部。如果可能,不要每次都返回完整的模型細節和關係。試一下,但要與響應中的返回模型保持一致。

最後,別忘了壓縮。如果可以,使用 Brotli,或者至少也使用 Gzip 來壓縮數據。簡單的配置,可以獲得減少 50-75% 的傳輸數據,多好!

  1. 做個體貼的開發者

這個要求無關技術,但我還是想寫出來。

作爲一個開發人員,我們要明白,項目不是一個人的事。當我們寫完最後一行代碼,提交併合併後,你可能會認爲工作已經完成。但不是,對其他很多人來說,這纔是個剛剛開始。

很多人在我們完成了工作後,才能開始他們的工作。所以,我們需要以多種方式準備 API。我們要確保 API 能正常工作,要有很好的文檔,更重要的事,我們需要準備好集成支持。不過文檔寫得有多好,在集成過程中,及以後的過程中,總會有問題,各種問題。

所以,設身處地的爲他人着想,儘量讓他們的工作變得容易些。構建一個良好的 API,遵循我們在這裏定義的規則,編寫優秀的文檔,併爲所有人服務。

  1. 寫完了

寫完了。

上面九條,是我團隊中執行的標準和要求。

這裏我也必須說, REST 本身並不是一個標準,所以也不會有人告訴你什麼是對的,什麼是錯的。開發的時候多想一下:作爲開發人員,我們每天都在尋找使代碼更好、更漂亮、更高效的模式,那麼爲什麼不在 API 中也做同樣的事呢?

全文完。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7x_7wUG8dlIubT4NdtVJOw