本文首先闡述了 RESTful 風(fēng)格 API 的基礎(chǔ)理論知識以及 Richardson 成熟度模型,隨后討論了好的 API 應(yīng)該具有哪些特征,最后對流行的 API 實現(xiàn)方式,即 GraphQL 和 RESTful,進(jìn)行了對比。
現(xiàn)在,每個人都在關(guān)注 API。API 最早開始流行于大約 20 年前,2000 年,Roy Fielding 在他的博士論文中首次提出了 REST 這個術(shù)語。同年,Amazon、Salesforce 和 eBay 向全世界的開發(fā)者介紹了他們的 API,永遠(yuǎn)改變了我們構(gòu)建軟件的方式。在 REST 之前,Roy Fielding 論文中的原則被稱為“HTTP 對象模型”,隨后你會明白這為何非常重要。隨著閱讀的深入,你還會看到如何確定你的 API 是否成熟,好 API 的主要品質(zhì)是什么以及為何在構(gòu)建 API 的時候,要注重適應(yīng)性。
REST 代表表述性狀態(tài)轉(zhuǎn)移(Representational State Transfer),由 Roy Fielding 在他的博士論文中定義,長期以來,它就是服務(wù) API 的圣杯。它并不是構(gòu)建 API 的唯一方式,但是由于它的流行,即便是非開發(fā)人員也知道這種標(biāo)準(zhǔn)。客戶端 - 服務(wù)器端架構(gòu)
無狀態(tài)
可緩存
分層系統(tǒng)
按需編碼(可選)
統(tǒng)一接口
但是,對日常使用來說,這過于理論化了。我們需要更具操作性的東西,這也就是 API 成熟度模型。
該模型是由 Leonard Richardson 提出的,它將 RESTful 開發(fā)原則結(jié)合成四個簡單易行的步驟。在模型中的位置越高,就越接近 Roy Fielding 所定義的 RESTful 原始理念。
?Level 0:POX(Plain Old XML)的泥沼
Level 0 的 API 是一組簡單 XML 或 JSON 的描述。在前文中,我曾經(jīng)說過在 Fielding 的論文之前,RESTful 原則被稱為“HTTP 對象模型”。這是因為 HTTP 是 RESTful 開發(fā)中最重要的組成部分。REST 要盡可能多地使用 HTTP 固有屬性中的理念。在 Level 0,沒有使用任何這樣的東西。我們只是構(gòu)建自己的協(xié)議并把它作為一個專有層。這種架構(gòu)被稱為遠(yuǎn)程過程調(diào)用(Remote Procedure Call,RPC),適用于遠(yuǎn)程過程 / 命令。通常我們會有一個端點,可以對它進(jìn)行調(diào)用以獲取一堆 XML。在這方面,一個典型的例子就是 SOAP 協(xié)議:另外一個很好的例子就是 Slack API。它有些多樣化,有多個端點,但依然是 RPC 風(fēng)格的 API。它暴露了 Slack 的各種功能,中間沒有附加任何特性。如下的代碼展示了如何向一個特定的通道發(fā)送消息:雖然按照 Richardson 的模型,這是一個 Level 0 的 API,但是這并不意味著它是不好的。只要它是可用的,并且恰當(dāng)?shù)胤?wù)于業(yè)務(wù)需求,那它就是很棒的 API。
為了構(gòu)建 Level 1 的 API,我們需要找出系統(tǒng)中的名詞并將它們通過不同的 URL 暴露出來,如下面的樣例所示:其中,“/api/books”能讓我訪問一個通用的圖書目錄,“/api/profile”能夠讓我訪問這些書的作者的基本信息。為了獲取某個資源的第一個特定實例,我可以在 URL 中添加 ID(或其他引用)。在 URL 中還可以嵌套資源,這展示了它們是以層級結(jié)構(gòu)的形式組織的。回到 Slack 的樣例,如下展示了按照 Level 1 API,它們會是什么樣子的:現(xiàn)在,URL 發(fā)生了變化,從原先的“/api/chat.postMessage”變成了現(xiàn)在的“/api/channels/general/messages”。信息中“channel”部分從請求體轉(zhuǎn)移到了 URL 中。從字面就能看出,通過使用這個 URL,我們可以預(yù)期有條消息發(fā)布到了 general 通道上。
Level 2 利用 HTTP 動作(verb)來添加更多的含義和意圖。在這方面可用的動作比較多,我這里只用到一個基礎(chǔ)的子集:PUT / DELETE / GET / POST。借助這些動作,我們可以預(yù)期包含它們的 URL 有不同的行為:POST:創(chuàng)建新數(shù)據(jù)
PUT:更新現(xiàn)有的數(shù)據(jù)
DELETE:移除數(shù)據(jù)
GET:查找特定 id 的數(shù)據(jù)輸出,獲取某個資源(或整個集合)
“安全”的方法指的是永遠(yuǎn)不會改變數(shù)據(jù)的方法。REST 建議 GET 方法只能用來獲取數(shù)據(jù),所以在上面的集合中,它是唯一一個安全的方法。不管你調(diào)用多少次基于 REST 的 GET 方法,它永遠(yuǎn)不會改變數(shù)據(jù)庫中的任何東西。但是,這并不是該動作的固有特性,而是關(guān)系到你該如何實現(xiàn)它,所以我們需要確保它是這樣運行的。所有其他的方法都會以不同的方式改變數(shù)據(jù),不能隨意使用。在 REST 中,GET 方法既是安全的,又是冪等的。“冪等”的方法指的是多次使用不會產(chǎn)生不同結(jié)果的方法。按照 REST,DELETE 方法應(yīng)該是冪等的,如果刪除了某個資源,然后針對相同的資源再次調(diào)用 DELETE,它不會改變?nèi)魏螙|西。資源應(yīng)該早就已經(jīng)消失了。在 REST 規(guī)范中,POST 是唯一一個非冪等的方法,所以我們可以對相同的資源多次調(diào)用 POST 方法,這樣我們會得到重復(fù)的資源。我們重新看一下 Slack 樣例,如果我們使用 HTTP 動作來進(jìn)行更多的操作會是什么樣子:我們可以使用 POST 方法發(fā)送消息到通用的通道,我們也可以使用 GET 方法從通用通道獲取消息。我們還可以使用 DELETE 方法和特定的 ID 刪除消息,這里比較有意思的一點在于,消息并不是與特定通道關(guān)聯(lián)的,所以我可以設(shè)計一個單獨的 API 來刪除資源。這個例子表明,設(shè)計 API 并不總是那么簡單,這方面有很多可選項和權(quán)衡。
還記得純文字、沒有任何圖像的電腦游戲嗎?我們只能看到一些文本,描述了你在哪里,以及接下來能干什么。為了取得進(jìn)展,我們必須要輸入自己的選擇。在一定程度上來講,HATEOAS 就是做這件事情的。HATEOAS 指的是“超媒體作為應(yīng)用狀態(tài)引擎(Hypermedia as the Engine of Application State)”。有了 HATEOAS 之后,當(dāng)其他人使用你的 API 的時候,他們就能看到通過 API 還能做哪些其他的事情。HATEOAS 回答了“從這里出發(fā),我還能去哪里?”的問題。但這還不是所有的內(nèi)容。HATEOAS 還可以對數(shù)據(jù)關(guān)系進(jìn)行建模。我們可能會有一個關(guān)于圖書的資源,并且在 URL 中沒有將作者信息嵌套進(jìn)來,但是我們可以包含它們的鏈接,如果有人對作者感興趣的話,那么他們可以訪問這些鏈接并探索相關(guān)的數(shù)據(jù)。HATEOAS 不像其他成熟度模型的等級那樣流行,但是有些開發(fā)人員確實在使用它。其中一個樣例就是 Jira,如下是它們的搜索 API 的響應(yīng):他們將鏈接嵌入到了其他我們可以探索的資源中,以及該 issue 的狀態(tài)過渡列表。另外一個使用 HATEOAS 的樣例是 Artsy。他們的 API 嚴(yán)重依賴 HATEOAS,并且還使用了 JSON Plus 調(diào)用規(guī)范,按照該規(guī)范強(qiáng)制要求使用一種特殊的約定來構(gòu)建鏈接。下面是一個分頁的例子,這是使用 HATEOAS 最酷的樣例之一:我們可以提供到下一頁、上一頁、第一頁和最后一頁的鏈接,還可以按照需要添加其他頁面的鏈接。這樣簡化了 API 的消費,因為這樣不需要在客戶端添加 URL 的解析邏輯,也不需要追加頁碼的方法。我們只需要在客戶端使用已經(jīng)實現(xiàn)結(jié)構(gòu)化的鏈接就可以了。
我們已經(jīng)介紹完了 Richardson 模型,但這并不是實現(xiàn)好的 API 的全部內(nèi)容。其他重要的品質(zhì)還有什么呢?
我對自己使用的 API 的基本期望之一就是,需要有一種明確的方式來判斷是否有錯誤或異常。我想要知道請求是否得到了處理。HTTP 有一種簡單的方式來實現(xiàn)這一點:HTTP 狀態(tài)碼。我們的 API 至少要提供 4xx 和 5xx 狀態(tài)碼。有時候,5xx 是自動生成的。例如,客戶端發(fā)送了一些內(nèi)容到服務(wù)器端,但是這非法的請求,而我們的校驗是有缺陷的,從而導(dǎo)致這個問題繼續(xù)在代碼中執(zhí)行了下去,最終導(dǎo)致出現(xiàn)了異常,這樣就會返回一個 5xx 的狀態(tài)碼。如果你想要承諾使用特定的狀態(tài)碼,那么你會遇到“哪種狀態(tài)碼最適合當(dāng)前情況?”的問題。這樣的問題并不總是那么容易回答,我推薦你去閱讀聲明這些狀態(tài)碼的 RFC,它們給出了比其他來源更廣泛的解釋,并且告訴了你何時使用這些狀態(tài)碼更合適等。幸運的是,網(wǎng)上有些資源可以幫助我們做出選擇,比如 Mozilla 的 HTTP 狀態(tài)碼指南。https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
優(yōu)秀的 API 必須要有優(yōu)秀的文檔。在文檔方面,最大的問題在于,隨著 API 的發(fā)展需要找人同步更新文檔。有個更好的方案是不脫離代碼自更新文檔。例如,注釋與代碼的脫節(jié)。當(dāng)代碼發(fā)生變化的時候,注釋依然保持不變,這樣的話,注釋就過時了。這甚至?xí)雀揪蜎]有任何注釋更糟糕,因為在隨后的一段時間內(nèi),它們會提供錯誤的信息。注釋不會自動更新,所以開發(fā)人員需要記得在維護(hù)代碼的時候同時維護(hù)它們。自更新的文檔工具可以解決這個問題。在這方面,一個流行的工具就是 Swagger,它是基于 OpenAPI 構(gòu)建的工具,可以很容易地描述你的 API。Swagger 很酷的一點在于它是可執(zhí)行的,所以如果你嘗試修改 API,能立即看到它的作用和變化。為了給 Swagger 添加自動更新功能,我們需要使用其他的插件和工具。在 Python 中,有針對大多數(shù)主流框架的插件。它們能生成 API 請求該如何組織的描述,并定義數(shù)據(jù)的輸入和輸出。如果你不想要使用 Swagger,而是想使用更簡單的工具,那該怎么辦呢?有個流行的替代方案是 Slate。https://slatedocs.github.io/slate/#introduction還有一些值得推薦的中間方案,如 widdershins 和 api2html 的組合,它允許我們從 Swagger 的定義中生成類似 Slate 的文檔。https://github.com/Mermade/widdershins
https://api2html.com/docs/overview/
在有些系統(tǒng)中,緩存可能并不是什么大問題。這樣的系統(tǒng)可能沒有很多的數(shù)據(jù)可供緩存,所有的數(shù)據(jù)都在不斷地發(fā)生變化,或者系統(tǒng)根本沒有很大的流量。但是,在大多數(shù)情況下,緩存對于良好的性能至關(guān)重要。它與 RESTful API 密切相關(guān),因為 HTTP 協(xié)議在緩存方面做了很多事情,比如 HTTP 頭信息允許我們控制緩存的行為。你可能想要在客戶端緩存東西,或者如果有注冊表或值存儲的話,那么你可能想要在應(yīng)用程序中緩存數(shù)據(jù)。但是,HTTP 讓我們能夠基本上免費就可以獲得一個很好的緩存,所以如果可能的話,請不要錯過這個免費的午餐。同時,因為緩存是 HTTP 規(guī)范的一部分,所以很多涉及 HTTP 的技術(shù)都知道如何進(jìn)行緩存:瀏覽器原生支持緩存,客戶端和服務(wù)器之間的中間技術(shù)也是如此。
構(gòu)建 API 以及現(xiàn)代軟件最重要的部分就是適應(yīng)性。如果沒有適應(yīng)性,開發(fā)就會變慢,在合理的時間發(fā)布特性就會變得更加困難,當(dāng)面對最后截止時間的時候更是如此。“軟件架構(gòu)”在不同的上下文語境中有不同的含義,不過我們現(xiàn)在采用這個定義:軟件架構(gòu)一種行為 / 藝術(shù),能夠避免會阻礙未來變化的決策。
記住了這一點,在設(shè)計軟件的時候,當(dāng)你必須要在具有相似優(yōu)點的方案中做出選擇時,你應(yīng)該始終選擇更多考慮到未來的方案。好的實踐并不是萬能的。按照正確的方式構(gòu)建錯誤的東西并不是你想要的結(jié)果。最好采取一種成長的心態(tài),接受變化是不可避免的,尤其是如果你的項目要持續(xù)發(fā)展的話更是如此。要想讓你的 API 更具適應(yīng)性,其中很關(guān)鍵的一點就是保持盡可能薄的 API 層,真正的復(fù)雜性應(yīng)該往下層轉(zhuǎn)移。
公開的 API 發(fā)布之后,它就已經(jīng)完成了,是不可改變的,你就不能再去觸碰它了。如果你已經(jīng)有了一個設(shè)計古怪的 API,除了接受現(xiàn)狀之外,還能做些什么呢?你應(yīng)該不斷尋找簡化實現(xiàn)的方法。有時候,你可以通過一個特定的 HTTP 頭信息來控制 API 響應(yīng)的格式,相對于構(gòu)建另外一個叫做 v2 的新 API,這是一種更簡單的解決方案。API 只是另外一層的抽象。它們不應(yīng)該決定如何實現(xiàn),為了避免這種問題,我們可以采用如下幾種開發(fā)模式。
這是一種類似于門面的開發(fā)模式。如果你要把一個單體結(jié)構(gòu)拆分為一組微服務(wù),并且希望向外部暴露一些功能的話,那么你只需要構(gòu)建一個類似門面的 API 網(wǎng)關(guān)。它將為不同的微服務(wù)提供一個統(tǒng)一的接口(這些微服務(wù)可能有不同的 API,使用不同的錯誤格式等等)。
如果你必須要構(gòu)建一個 API 來滿足一堆不同的客戶端的話,那么這可能會非常困難。針對某個客戶端所作出的決策可能會影響其他客戶端的功能。按照適用于前端的后端(backend for frontend)理念,如果你有不同的客戶端,它們喜歡不同形式的 API,比如移動應(yīng)用可能會喜歡使用 GraphQL,那么就單獨為它們構(gòu)建吧。只有當(dāng)你的 API 是一層抽象,并且這個抽象層很薄的時候,這種方式才有效。如果它與你的數(shù)據(jù)庫耦合,或者太大,具有太多的邏輯,那么就無法這樣做了。
很多人都在熱炒 GraphQL。它是一項新興的技術(shù),但是已經(jīng)有了很多粉絲,以至于有些開發(fā)者聲稱它將取代 REST。盡管 GraphQL 比 RESTful 要新的多,但是它們有很多相似之處。GraphQL 最大的不足之處在于它的緩存,它必須要在客戶端或應(yīng)用程序中實現(xiàn)。現(xiàn)在,有內(nèi)置的實現(xiàn)了緩存功能的客戶端庫(比如 Apollo),但是這仍然要比使用 HTTP 提供的幾乎免費的緩存功能要困難。https://www.apollographql.com/docs/react/caching/cache-configuration/從技術(shù)講,GraphQL 位于 Richardson 模型的 Level 0 層級,但是它具有良好 API 的特質(zhì)。我們可能無法同時使用多個 HTTP 的功能,但是 GraphQL 的出現(xiàn)就是解決這一問題的。GraphQL 的殺手锏就是聚合不同的 API,并將它們作為一個 GraphQL API 暴露出來。GraphQL 在處理數(shù)據(jù)抓取不足和數(shù)據(jù)過量抓取方面有很好的效果,而這些問題是 REST API 很難進(jìn)行管理的。這兩個問題都與性能有關(guān),如果數(shù)據(jù)抓取不足,那說明你沒有高效地使用 API,所以必須要進(jìn)行大量的調(diào)用。如果數(shù)據(jù)過量抓取的話,那么 API 調(diào)用的數(shù)據(jù)傳輸會比必要的數(shù)據(jù)傳輸更大,這是對帶寬的一種浪費。
借助 REST 與 GraphQL 的比較,我們能夠總結(jié)出一個好的 API 最重要的品質(zhì)。我們需要一個清晰的數(shù)據(jù)表述方式:RESTful 以資源的方式提供了表述。我們需要有一種方式顯示有哪些可用的操作:RESTful 通過組合資源和 HTTP 動作實現(xiàn)這一點。我們需要有一種方式來確認(rèn)是否存在錯誤 / 異常:HTTP 狀態(tài)碼可以實現(xiàn)這一點,可能還會包含闡述它們的響應(yīng)信息。最好能夠提供 API 發(fā)現(xiàn)和導(dǎo)航的功能:在 RESTful 中,HATEOAS 負(fù)責(zé)實現(xiàn)這一點。有好的文檔是非常重要的:在這方面,可執(zhí)行、自更新的文檔可以解決這個問題,這超出了 RESTful 規(guī)范的范圍。最后,但同樣重要的是,優(yōu)秀的 API 應(yīng)該具有緩存功能,除非你的特定情況認(rèn)為它是不必要的。REST 和 GraphQL 之間最大的區(qū)別是它們處理緩存性的方式。當(dāng)我們使用 REST 方式構(gòu)建 API 的時候,我們基本上可以免費獲得 HTTP 的緩存功能。如果選擇 GraphQL 的話,你需要自行負(fù)責(zé)為客戶端或應(yīng)用程序添加緩存。原文鏈接:
https://www.stxnext.com/blog/how-to-build-a-good-api-that-wont-embarrass-you
逆鋒起筆是一個專注于程序員圈子的技術(shù)平臺,你可以收獲最新技術(shù)動態(tài)、最新內(nèi)測資格、BAT等大廠的經(jīng)驗、精品學(xué)習(xí)資料、職業(yè)路線、副業(yè)思維,微信搜索逆鋒起筆關(guān)注!
10 種常見的軟件架構(gòu)模式
API 快速開發(fā)平臺設(shè)計思考
老舊的 API,該如何處理?
瞧瞧,人家那后端 API 接口寫得多優(yōu)雅!
臥槽!我隨便寫的一個 API 竟獲得 2.5 億的訪問量