徹底明白如何設計一個良好的 API

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在軟件開發流程都是協同合作的,前後端分離,那麼我們如何實現對 API 的統一認知?又該如何設計一個良好的 API 接口?隨着業務的演進,如何設計一個有兼容性的API?面對多種客戶端,如何設計一個處處適用的 API 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RESTful 是目前最流行的 API 設計規範,通過一定的規範可以解決上面這些問題,對於 RESTful 架構的理解可以參考阮一峯老師的這篇文章(http://www.ruanyifeng.com/blog/2011/09/restful.html),簡單一句話就是對服務器端資源進行操作,實現\"表現層狀態轉化\"。URI(統一資源定位符)代表一種資源,它可以是一段文本、一張圖片、一首歌曲、一種服務;每種資源對應一個特定的 URI,存在着這種對應關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"什麼樣的 API 是好的設計呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們要遵循以下幾個原則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"標準化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RESTful API 通過 GET POST PUT DELETE 等方式對服務器的資源進行操作,因此在定義 API 的時候需要明確定義出:請求方式、版本、資源名稱和資源ID,格式如: GET http://{host}/{version}/{resources}/{resource_id},如查看用戶編碼爲1 的用戶信息 GET /v1/users/1。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標準化主要從 URL 設計、狀態碼的使用、服務器響應碼使用,具體可參考阮一峯老師的RESTful API 最佳實踐(https://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html),千萬不要自定義狀態碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"兼容性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着業務的發展,設計一下有兼容性的 API 是非常重要的,如果接口不能夠向下兼容,業務就會受到很大影響,產品涵蓋 Android iOS PC 等客戶端,用戶需要產品升級到最新的版本,才能更好地使用,這種情況下引入版本的概念,實現 API 的兼容性。可以參考一些開放 API 的設計,通過版本號或一些開關參數來支持一些新功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"抽象性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接口抽象都是基於業務需求,定義清晰的業務問題域模型,並建立起和某個問題的對應,實現抽象,可以很好地屏蔽具體的業務實現細節,提供更好的可擴展性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"簡單性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遵守最少知識原則,就是客戶端,不需要知道那麼多服務的 API 接口,以及這些 API 接口的調用細節,如外觀接口對各個微服務統一管理(進行業務封裝與整合),對調用方提供一個簡單的 API ,客戶端只需要調用一個接口,而不用關心裏面的細節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"高性能"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"外觀接口雖然保證了簡單性,但是增加了服務端的業務複雜度,多服務之間的聚合,導致接口性能不好,還要考慮入參字段的各種組合,會不會導致數據庫的性能問題,有時候我們暴露了過多字段給外部組合使用,有可能導致數據庫沒有相應的索引,而發生全表掃描,因此,我們只提供存在索引的字段組合,給外部調用,要求索引字段爲必填字段,這樣保障能正常使用索引來確保性能"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"冪等性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"冪等性(Idempotent)在這裏表示發送一次和多次請求引起的邊界效應是一致的。如在網速不夠快的情況下,客戶端發送一個請求後沒有立即得到響應,由於不能確定是否請求是否被成功提交,所以有可能會再次發送另一個相同的請求,冪等性決定了第二個請求是否有效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP 7種方法中的(GET、HEAD 和 OPTIONS)均是冪等方法。由於 DELETE 和 PATCH 請求操作的是現有的某個資源,所以它們是冪等方法。對於 PUT 請求,只有在對應資源不存在的情況下服務器纔會進行添加操作,否則只作修改操作,對於是否冪等操作,還要看這個修改操作是相對的還是絕對的,比如更新年齡,是在原值的基礎上加1操作,還是更新成一個新的年齡了。至於最後一種 POST,由於它總是進行添加操作,如果服務器接收到兩次相同的 POST 操作,將導致兩個相同的資源被創建,所以這是一個非冪等的方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"怎麼解決冪等性問題呢?(簡單場景:表單重複提交)利用 Redisson 實現分佈式鎖,並防止重複提交。這個話題可以單獨寫一篇文章總結一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"安全性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於提供公網域名進行訪問,而且接口和關鍵業務有關,所以安全性很重要。安全措施大體來看主要在兩個方面,一方面就是如何保證數據在傳輸過程中的安全性,另一個方面是數據已經到達服務器端,服務器端如何識別數據,以及如何不被攻擊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"數據加密"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"數據在傳輸過程中是很容易被抓包的,如果傳輸通過 http 協議,那麼用戶傳輸的數據可以被任何人獲取;所以必須對數據加密,常見的做法對關鍵字段加密,比如用戶密碼直接通過 MD5 加密;另外一種主流的做法是使用 https 協議,在 http 和 tcp 之間添加一層加密層(SSL層),這一層負責數據的加密和解密。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"數據加簽"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"數據加簽就是由發送者產生一段無法僞造的一段數字串,來保證數據在傳輸過程中不被篡改,服務端通過驗證這個簽名來判斷是否合法請求。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"時間戳機制"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"經過如上的加密,加簽處理,就算拿到數據也不能看到真實的數據,但是有不法者直接拿到抓取的數據包進行惡意請求;這時候可以使用時間戳機制,在每次請求中加入當前的時間,服務器端會拿到當前時間和消息中的時間驗證,看看是否在一個固定的時間範圍內,這樣惡意請求的數據包是無法更改裏面時間的,所以超過這個時間範圍就視爲非法請求。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":4,"normalizeStart":4},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"AppId 機制"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"對外提供的接口其實也需要一種機制,並不是誰都可以調用,需要使用接口的用戶需要在後臺開通 AppId,提供給用戶相關的密鑰,在調用的接口中需要提供 AppId + 密鑰,服務器端會進行相關的驗證,獲取接口調用憑據 access_token。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":5,"normalizeStart":5},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"限流機制"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"本來就是真實的用戶,並且開通了 AppId,但是出現頻繁調用接口的情況,這種情況需要給相關 AppId 限流處理,常用的限流算法有令牌桶和漏桶算法。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":6,"normalizeStart":6},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"黑名單機制"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"如果此 AppId 進行過很多非法操作,經過分析之後直接將此 AppId 列入黑名單,所有請求直接返回錯誤碼。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":7,"normalizeStart":7},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"數據合法性校驗"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"只有在數據是合法的情況下才會進行數據處理;每個接口都要有驗證規則,當然也可能有一些常規性的規則,比如身份證長度和組成,電話號碼長度和組成等等。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文大致列舉了幾種 API 設計常見的原則:標準化、兼容性、抽象性、簡單性、高性能、冪等性、安全性,其中安全措施機制包括:數據加密、數據加簽、時間戳機制、AppId 機制、限流機制、黑名單機制以及數據合法性校驗。當然肯定有其他方式,歡迎補充。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更多內容請關注:https://yezhwi.github.io/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章