大型網站架構的技術細節:後端架構規整化 - 接口設計如何實現?
規整化
本節講解具體規整地方法,主要從接口設計、編碼規範、集中配置、Cookie 和 Session、應用拆分和應用協調、日誌 6 個方面進行深入講解。在項目前期,後端架構需要充分考慮這幾個方面。
注意:規整化是要把握一個度的,標準太高會拖慢項目進度,標準太低又達不到規整的目的,所以後端架構需要根據實際的團隊水平和項目週期制定規整化的標準。
接口設計
後端應用程序可以看作一堆接口的集合體,因此,“規整化後端應用程序”應該先關注 “接口設計”。由 4.1.4 小節後端應用程序的工作原理中介紹的“後端應用程序處理接口請求” 可知,一個完整的接口請求包含 7 個部分,本小節將會對這 7 個部分進行詳細的分析。
· 請求的 URL;
· 請求的方式;
· 請求的報文頭信息,一般稱爲請求的 Header 信息;
· 請求的報文體,一般稱爲請求參數;
· 響應的狀態碼和狀態碼描述;
· 響應的報文頭信息,一般稱爲響應的 Header 信息;
· 響應的報文體,一般稱爲響應數據。
注意:後端應用程序的接口一般被稱爲 RESTful API,業內有很多關於 RESTful API 的設計規範。但是,不建議全盤照搬這些設計規範,因爲現成的設計規範都過於完整,以至於開發者很難有耐心對其嚴格遵守。再者,設計規範的目的是統一規則、簡化邏輯、便於團隊合作,所以具體項目應該根據實際情況,制定儘量精簡且有效的接口設計規範。
1. 請求的 URL
一個 URL 一般分爲 4 部分,即協議、IP 地址(域名)和端口、路徑、參數,如圖 4.40 所示。
(1)協議。一般爲 HTTP 或 HTTPS。協議這部分在接口設計時可以先忽略,因爲協議一般是整個網站系統統一的,單個接口無權決定使用的協議。而且一般情況下,無論使用 HTTP 還是 HTTPS,後端應用程序的代碼都不會有所差別。
圖 4.40 URL 的結構
(2)IP 地址和端口。這裏的 IP 地址一般爲域名。IP 地址和端口這部分在接口設計時也可以先忽略,其也是由網站整體規劃決定的。
說明:一般情況下,接口的 URL 會與網頁資源的 URL 有所區別,這個區別可以體現在域名上,如 api.xxx.com,或者體現在路徑上,如 xxx.com/api/xxxx。
(3)路徑。路徑是接口設計中比較重要的部分。路徑的作用是讓 Web 應用服務器軟件調用對應的後端應用程序的函數,所以從技術層面上講,無論路徑有多少層或者用什麼單詞,只要這個路徑能與代碼對應上,就是沒問題的。但是,由於 URL 本身標記的是資源,所以 URL 上的單詞最好都爲名詞,而且最好是名詞的複數形式,如 / zoo/animals。
而路徑推薦分爲三層,分別是模塊、子模塊和具體資源。模塊指的是處理不同業務的後端應用程序;子模塊指的是模塊內部的分類;具體資源指的是對應具體資源的標識。具體資源內部還可以根據具體情況增加分層,不過最好不要超過 2 層。路徑的分層對應關係如圖 4.41 所示,其中,在實際編碼中,路徑分層會根據不同的框架有不同的映射關係,在 Spring Boot 框架中,模塊一般指的是. war 文件,子模塊一般指 XXcontroller.java 文件,具體資源標識的是函數(需要加上請求方式才能唯一標識具體函數)。
圖 4.41 URL 中路徑分層的對應關係
注意:模塊的劃分可以按照業務架構的子系統劃分,業務架構設計可以參考 2.1.1 小節業務架構面臨的挑戰和 2.2 節業務架構的基本思路。後端應用程序的模塊劃分除了根據業務架構劃分外,還需要考慮權限分類(普通用戶和管理員)及併發壓力(高併發接口和普通接口)等因素。
一些接口設計規範裏強調版本管理,版本管理有利於對不同版本的後端應用程序進行管理,在不影響使用者使用舊版本接口的同時,提供新版本的接口。版本號可以體現在 URL 上,如 / v1/zoo/animals,其中 v1 即爲版本號,也可以體現在 Header 信息(報文頭信息)中,不過體現在 URL 上比較直觀和普遍一些。但是,版本管理的實質是同時運行舊版本和新版本的後端應用程序,那麼有了新版本還有必要保留舊版本嗎?答案是不是任何時候都需要保留舊版本,也就是說,不是任何後端應用程序都需要做版本管理。在實際項目當中,只需要對一些開放性的接口(被自身網站系統以外的軟件使用)進行版本管理就可以了,不需要對網站系統內部的接口進行版本管理。
這是因爲自身網站系統以外的使用者會有很多個,且大多都不願意隨着接口更新而立刻改變自己的代碼,如果功能穩定的話,他們會更願意繼續使用舊版本;對於自身網站系統內部而言,接口使用者只是網站系統本身,代碼修改起來比較方便,而且維護幾個版本是需要一定成本的。因此,版本管理需要根據具體情況而定,並非所有接口都需要做版本管理。
注意:一般而言,“需要做版本管理的接口”和 “不需要做版本管理的接口” 需要區分出來,劃分成不同模塊(不同的. war 文件),這樣有利於管理。
(4)參數。問號以後都爲參數部分,這部分不建議使用,參數部分應該放在請求參數當中,這樣可以讓 URL 精簡一些。但是,當請求方式爲 GET 時,則不得不把參數部分放在 URL 中,因爲此時一些請求端是不允許有報文體的(如瀏覽器)。
當請求方式爲 DELETE 時,一些規範推薦在 URL 中使用 {} 作爲參數的標識,如
http://hostname/user/user/{userid},其中,userid 爲需要刪除的具體 id。這是因爲某些 Web 應用服務器軟件可能會丟棄伴隨 DELETE 發送的報文體。
不過,這種會丟棄報文體的服務器是不常見的,在默認配置下,Tomcat 不會丟棄伴隨 DELETE 發送的報文體。因此,當請求方式是 DELETE 時,還是推薦把參數放到報文體中,這樣能統一接口設計邏輯。
綜上,除非請求方式爲 GET,否則參數部分都應該放在請求參數當中。
2. 請求的方式
請求方式表示對資源的操作類型,一般來說,通過 “URL” 和“請求方式”這兩項才能唯一定位後端應用程序對應的函數。具體的請求方式及其對應的含義如表 4.2 所示。
注意:目前比較常用的 HTTP 版本是 HTTP 1.1,而在舊版本 HTTP 1.0 當中,不支持 PATCH 請求方式。
表 4.2 請求方式及其對應含義
在實際應用當中,一般使用的是 GET、POST、PUT、DELETE,對應四個基本操作:查詢、增加、修改、刪除。請求 URL 和請求方式結合起來,即可表示爲對某個資源的具體操作,即使不看接口文檔,也能大概猜出接口的功能。當然,以上的請求方式其實只有字面上的含義,用 DELETE 標識更新操作也不會有問題,不過,這些都是約定俗成的東西,最好不要特立獨行。
說明:對於修改操作,無論更新的是全部屬性還是部分屬性,其實都可以選用 PUT 或者 PATCH,PUT 更常用一些。
3. 請求的 Header 信息
請求的 Header 信息(報文頭信息)用於記錄相關的請求屬性,Web 應用服務器會處理這種 Header 信息。Header 信息的種類很多,但接口設計時一般考慮表 4.3 中的屬性即可。其中,Accept 屬性的值保持默認(/,全部類型)即可,一般不需要設置;Cookie 屬性的值一般是服務器端通過 Set-Cookie 設置的,瀏覽器請求時會自動攜帶上,更詳細的內容會在 4.3.4 小節中介紹;Content-Type 屬性對應請求參數的數據類型,這個一般需要手動指定,不然 Web 應用服務器無法把請求參數轉換成對應的數據對象。
表 4.3 接口設計時需要考慮的 Header 屬性
Header 信息可以被後端應用程序獲取和使用,後端應用程序可以對一些自定義的 Header 信息進行處理,從技術層面講,可以通過自定義 Header 屬性給後端應用程序傳遞參數。但是,一般情況下,Header 信息(除了 Cookie 外)都是 Web 應用服務器軟件處理的,而 Cookie 在瀏覽器設置成 “阻止 Cookie” 時,也不會自動保存和發送,因此,接口設計時最好不要添加自定義屬性。
綜上,接口設計只需要指定 Content-Type 屬性即可,一般設置爲 application/json(對應 JSON 數據類型),但這需要與請求參數的數據類型匹配。其他 Header 屬性保持默認即可,Cookie 屬性會在 4.3.4 小節中講述用法,但不建議個別接口使用。
說明:請求時的 Header 信息記錄了相關請求屬性,其屬性處理應該交由 Web 應用服務器處理,後端應用程序處理請求參數即可。對於 “Web 應用服務器軟件處理 Header 信息” 和“後端應用程序處理請求參數”這個劃分需要明確,否則接口使用者會覺得很麻煩。
4. 請求參數
請求參數(報文體)部分是根據每個接口的功能而定的,不過也應該遵守一定的規範,包括參數類型、參數結構、明確必填參數和可選參數等。
參數類型可以是 JSON、XML、字符串等。在項目沒有特殊要求的情況下,推薦使用 JSON 類型。這是因爲目前 JSON 類型比較流行,而且相對於其他類型(如 XML)更加易讀和簡潔,便於人工檢查。JSON 數據結構如代碼 4.17 所示,XML 數據結構如代碼 4.18 所示。
注意:無論選擇哪種參數類型,請求參數部分的參數類型必須統一,不能一個接口選用一個類型。
代碼 4.17 JSON 數據結構
{
"id":"AA0302",
"name":"張三",
"class":"1-1",
"born":"2011.11.20"
}
代碼 4.18 XML 數據結構
<?xml version="1.0" encoding="utf-8" ?>
<student>
<id>AA0302</id>
<name> 張三 </name>
<class>1-1</class>
<born>2011.11.20</born>
</student>
參數結構應該儘量簡潔,層級最好只有一層,一些特殊情況除外(如批量添加等情況)。結構層級超過一層會讓整體結構看起來有點混亂,以 JSON 數據爲例,一層結構的數據和多層結構的數據對比如代碼 4.19 所示。
代碼 4.19 一層結構的數據和多層結構的數據的對比
一層結構的數據
{
"id":"AA0302",
"name":"張三",
"class":"1-1",
"born":"2011.11.20"
}
多層結構的數據
{
"id":"AA0302",
"info":{
"basic":{
"name":"張三",
"class":"1-1"
},
"born":"200.11.20"
}
}
明確必填參數和可選參數,必填參數需要明確,可選參數需要明確其默認值。以添加學生信息接口爲例,其參數說明如表 4.4 所示,其中學生 id 由服務器生成。
表 4.4 添加學生信息接口的參數說明
5. 響應的狀態碼和狀態碼描述
當接口請求處理完畢後,會返回響應報文,其中,狀態碼標識的是此次請求的結果狀態,狀態碼描述是狀態碼對應的簡短文字描述。狀態碼大致有 5 個種類,如表 4.5 所示,其中,XX 表示省略,如 2XX 的狀態碼包含 200、201、202 等狀態碼。
表 4.5 狀態碼種類
狀態碼和狀態碼描述一般交由 Web 應用服務器填寫,後端應用程序不需要關心。雖然後端應用程序可以修改狀態碼,但是一般不推薦這樣做。因爲狀態碼標識的是此次請求本身的狀態,與後端應用程序的功能無關。例如,當請求參數填寫錯誤時,後端應用程序會返回一個 “參數錯誤” 的結果,但是請求過程本身是沒有問題的,所以返回的狀態碼應該是 200(成功),而 “參數錯誤” 的結果應該放到響應數據(報文體)當中。
綜上,接口設計其實不需要關心狀態碼和狀態碼描述。
6. 響應的 Header 信息
響應的 Header 信息(報文頭信息)用於記錄相關的響應屬性,大部分屬性都是 Web 應用服務器根據 “請求時的 Header 信息” 和“後端應用程序返回的結果”自動填寫的。響應的 Header 信息種類很多,但接口設計時一般考慮表 4.6 中的屬性即可。其中,Content-Type 屬性是自動填寫的;Set-Cookie 屬性一般由後端應用程序操作,一般情況下,該屬性對應的值會被客戶端保存,當客戶端再次發送請求時,會被記錄在請求 Header 信息的 Cookie 屬性當中。更詳細的內容會在 4.3.4 小節中講述。
表 4.6 接口設計時需要考慮的響應 Header 參數
接口設計可以使用 Set-Cookie 屬性,但是由於 Cookie 在瀏覽器設置 “阻止 Cookie” 時是不會自動生效的,所以 Set-Cookie 一般也是不推薦使用的。
因此,一般情況下,接口設計不需要關心響應 Header 參數。
7. 響應數據
響應數據(報文體)部分是根據每個接口的處理結果而定的,不過也應該遵守一定的規範。
數據類型最好與請求參數的數據類型保持一致;數據結構,響應數據中最好包含 “結果代碼” 和“結果代碼對應的描述”,把響應結果數據放到固定的字段裏面。以獲取學生信息的接口爲例,響應數據如代碼 4.20 所示,其中,數據類型爲 JSON。
說明:這裏沒有強調數據層級限制,這是因爲響應數據一般比較複雜,採用合理的數據結構能方便使用者使用。
代碼 4.20 獲取學生信息接口的響應數據
{
"errorCode":"200", // 結果代碼
"message":"成功", // 結果代碼對應的描述
"result":{ // 結果數據的字段
"count":20, // 學生總個數
"list":[ // 篩選出的學生列表及其信息
{"id":"AA0302", "name":"張三", "class":"1-1", "born":"2011.11.20"},
{"id":"AA0303", "name":"李四", "class":"1-1", "born":"2011.10.20"},
{"id":"AA0304", "name":"王五", "class":"1-1", "born":"2011.11.22"},
{"id":"AA0305", "name":"趙六", "class":"1-1", "born":"unknown"}
]
}
}
相對應的,響應數據也應該有說明表格,以代碼 4.20 所示的響應數據爲例,其參數說明如表 4.7 所示。
表 4.7 獲取學生列表接口的響應數據說明
8. 總結
綜上 7 點的分析和描述,接口設計的邏輯如圖 4.42 所示。URL 的路徑部分負責劃分資源模塊,請求方式負責規劃對資源的 “增、刪、改、查” 操作,請求參數作爲處理參數,響應數據作爲處理結果。一般而言,接口設計需要關注的只有 4 部分,即 URL 中的路徑部分、請求方式、請求參數和響應數據。
注意:當請求方式爲 GET 時,請求參數需要放在 URL 上。
圖 4.42 接口設計邏輯
來源:
https://www.toutiao.com/article/7167595949848560180/?log_from=6dd522f64cefe_1669079214464
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cJ9KAMrY1Us9UlBOutBgfQ