儘管你的API代碼超級乾淨和亮眼,但你的客戶可能永遠看不到它。他們對你的API架構並不感冒。前端
您應該嘗試使用清晰的能解釋的名稱和儘量接近文檔要求的架構。此外,經過一些簡單的規則,您應該可以對其進行之後的擴展,以即可以繼續向現有響應中添加更多信息,而沒必要早早的淘汰舊版本。數據庫
幾年來,我遇到的最多見的與模式相關的錯誤就是因爲API開發環境缺少嚴格性。在設計API時,使用適合您的編程語言的框架對您將要作的事情大有裨益。編程
花些時間研究可用的選項,並記住,儘管大多數Web框架提供了可幫助您實現REST API的工具,但仍須要您本身完成大量的工做,永遠不要重複造輪子。json
最流行的後端Web技術(也包括前端)是基於鬆散類型的腳本編程語言,這些語言具備不良的編程習慣,這些習慣很難擺脫。PHP和Javascript(以Node.js的形式)是數據類型鬆散型的典型,由於它們常常在沒有嚴格的數據模型下使用。後端
確保爲您的數據庫使用ORM或其餘數據模型基礎結構嚴格建模的內部數據層。您的API響應應該被包含在嚴格定義的嵌套數據中。不要將數據庫查詢的結果生成簡單的數據結構。不管怎樣,保持嚴格的數據層是您在全部應用程序中應該作的。數組
在暴露給API響應以前,應始終對數據進行過濾,以避免泄露敏感信息。對暴露給API的字段所作的任何修改,都應該注意與當前API版本的兼容性,並將其添加到API的更新日誌中安全
對代碼的命名應該在整個API,URI以及請求和響應中保持一致,而且具備特殊的意義。命名不明確會給其餘開發人員以及您本身形成很大的壓力。數據結構
請記住,您的URI和架構應儘量清晰地向使用者傳達目的,而無需他們不斷的瀏覽文檔。不要懼怕使用冗長的名稱。架構
即便您用的編程語言不嚴格,您也應該使用嚴格的數據類型。數字字段永遠應該只有數字,字符串字段永遠應該只包含字符串,諸如子類。您永遠都不該該在相同的字段中混合不一樣的數據類型。您的字段對應的值有可能會不同,但該字段的額數據類型應該肯定。app
在鬆散類型的環境中看到同一個字段在一個響應中是數字42,但在另外一個響應中是字符串「42」是很常見的錯誤。這個作法先後不一,很難再全部的客戶端安全解析。在鬆散類型的架構中,客戶在解析每一個字段時都很危險。
顯然,這不只使用與原始數據類型(數字,字符串,布爾值等),並且使用於JSON對象和數組。請勿在包含Table類型的對象字段中返回Chair類型的對象,或在包含Bicycles數組的字段中返回Cars數組。
雖然上面說的都是很是基礎的知識,但不少優秀的開發人員都犯過相似的錯誤。
一個強壯的模型層可幫助您避免此類尷尬的錯誤。
當某個字段沒有可用值時,請勿徹底忽略該字段。根據數據類型和缺失值的語義,使用null,空字符串,空數組或零。
當一個接口知足要求時,不要使用10個接口來獲取全部的字段。也許他們能夠在文檔中查找它,但爲何不能讓文檔更容易理解呢?請記住,文檔變得陳舊比代碼快得多,而代碼纔是生成架構的緣由。
再一次強調,若是在實現中使用強大的模型層,則會更容易避免這種類型的錯誤。
我見過的次數超過了我在API請求和響應中記得的次數,這也一般源於與鬆散類型語言相關的不良作法。
假設您有一個包含惟一id做爲主鍵和子對象做爲值的people對象
{ "people": { "1234": { "name": "John", "surname": "Smith" }, "5678": { "name ": "John ", "surname": "Smith" } } }
people對象隨着它內部的內容數據類型的變化而變化,在這個例子中,它的key值是1234和5678,可是沒有人知道下一次請求時它的key值是什麼。
這是個很可怕的作法,而且在任何嚴格類型的語言中進行解析時,都會致使代碼不一致而廣泛糟糕。API中的每一個JSON對象在請求時都應始終具備一組不變的嚴格定義的字段。
下面的是一個很好的數組用例,只需返回一個數組,並將id包含在每一個數組元素中便可。
{ "people": [ { "id": 1234, "name": "John", "surname": "Smith" }, { "id": 5678, "name": "John", "surname": "Smith" } ] }
一般,當您嘗試經過使用帶有惟一ID鍵的字段來使後端代碼的查找更容易時,就會出現JSON濫用。您必須謹記,在這種狀況下,內部實現的詳細信息會泄露給用戶-在軟件開發的全部方面都應避免這種現象。
若是您遵循了先前的建議,並將某些對象更改成數組,那你作的很好!
如今,您必須確保數組僅包含一種類型的對象。不要將apples和oranges混合一塊兒! 請記住,並不是全部的客戶端都是用鬆散類型的容器來存儲數據,而且解析異構資源列表不只不一致且使人討厭,並且也不安全。
當你確實沒法避免在同一數組中返回不一樣種類的實體時,請嘗試返回一個超級對象列表,這些列表足夠抽象以描述您須要返回的全部對象類型的屬性。
在蘋果和橙子的例子中,或許你應該返回一個Fruit對象,一個Fruit對象能夠包含Apple和Organge對象的全部屬性,以及一個字段,該字段能夠準確描述每一個對象的水果類型。
若是您返回的項的屬性對於每種返回的類型都是徹底不一樣的,但仍必須將它們返回到同一列表中,則可能必須使用極端措施,好比容器對象。雖然不是很是優雅的解決方案,但這也是必須的。
下面是容器對象的例子:
你的接口將返回一我的擁有的飛行器的列表,以及每一個飛行器的一些基本特徵。飛行器能夠是飛機,也能夠是熱氣球,二者之間有很大的不一樣。從語義上講,向熱氣球添加翼展,引擎數量或馬力等屬性幾乎沒有意義,向飛機添加藍,氣球材質和睦球形狀等屬性也沒有意義。
將全部這些屬性字段添加到單個對象類型是毫無心義的。相反,您能夠將飛機對象和熱氣球對象存儲在一個容器對象中。
在這種狀況下,類型爲Vehicle(本質上是超類型)的容器對象將包含兩個字段airplane和ballon,分別對應不一樣的子對象。 請記住,即便其中的一個字段沒有數據,也要將該字段及其數據類型返回。
{ "vehicles": [ { "type": "airplane", "airplane": { "engines": 1, "wing\_span": 12, "horsepower": 240 }, "balloon": null }, { "type": "balloon", "airplane": null, "balloon": { "basket": "rattan", "balloon\_material": "dacron", "balloon\_shape": "natural" } } ] }
再次強調,若是可能的話,請避開這種設計,可是若是您必須在同一集合中返回徹底不一樣的對象,則容器對象是一種維護嚴格類型的架構的好方法。
我不想讓您失望,可是不管您的錯誤消息多麼有趣和風趣,它們幾乎都不會引發用戶的興趣。並非其餘開發人員不欣賞您的寫做技巧,而是你永遠不知道客戶將怎樣呈現一個錯誤。
此外,您必須始終以簡潔,機器可讀的方式返回錯誤,以使其被客戶端易於解析。您應該返回正確的HTTP狀態代碼,並在相應正文中的錯誤對象中包含特定的錯誤消息。
下面是一個例子:
您的用戶經過下面的API請求一個訂單
GET /customers/21/order/42
若是未找到客戶或訂單,則響應404 Not Found狀態碼,但這樣就好了嗎?用戶將沒法準確的區分致使錯誤的緣由,由於他不知道錯誤的緣由是客戶仍是訂單。
這就是您的響應正文中的錯誤對象派上用場的地方。
一個可讀的機器碼使事情對客戶端而言更簡單。此外,將說明(例如:「customer_not_found」)而不是數字,可使開發人員更輕鬆-無需查看API文檔中的數字表和錯誤說明。
最後,message 字段能夠更好地向開發人員解釋錯誤的緣由,所以他們對如何處理錯誤以及在何處查找其餘信息有了更好的瞭解。 理想狀況下,應根據客戶請求的「接受語言」標頭對錯誤進行本地化。 誰知道呢,也許最終用戶會在某種程度上閱讀您的傑做。
正如一遍又一遍提到的,您應該有一個易於閱讀和自我記錄的架構。 枚舉時請勿使用數字。 使用簡單的字符串。
您的動物對象中有一個類型字段嗎? 請勿使用一、二、三、4和5做爲其值。「 dog」,「 cat」,「 parrot」,「 armadillo」和「 elephant」更容易被人閱讀,而且對 知道如何比較字符串的機器。
人們一般在後端內部使用數字枚舉時執行此操做,但這(一樣)是一種實現細節,不該泄漏給API使用者。
我還聽到了一些藉口,例如增長字符串方法的帶寬消耗,可是還有其餘更好的方法來解決該問題。 您的架構應足夠詳細,以便一眼就能理解,而且應該使用Gzip減小帶寬消耗,與使用數字枚舉節省幾個字節相比,Gzip具備很大的不一樣。
在這種狀況下,封裝(或JSON)究竟是什麼?簡而言之,這意味着將響應數據包裝(或封裝)到JSON對象中,而後將其返回到響應主體根目錄中的data(或其餘相似名稱)字段中。
某些人彷佛認爲這是對全部響應的一種好習慣,由於它容許您未來添加元數據字段(例如錯誤或分頁信息),而不會篡改主要響應對象。儘管在解析時可能須要更多的代碼,但這確實使API模式更加簡潔。
即便您不想對全部響應都這樣作,但我相信當您返回對象集合時,它很是有用(甚至有必要)。在這種狀況下,您永遠不要將數組做爲響應的根容器!
上面的主要理由是,若是您的根容器是JSON數組,則在響應須要返回錯誤(不可避免地將是JSON對象)時,您的架構會發生根本性的變化。這使得解析更加複雜,而沒有提供任何實際好處。
此外,(即便您不理會上述狀況),數組也使早期棄用API的可能性更大,由於在不棄用架構的狀況下,毫不能以任何方式對其進行更改或修改。另外一方面,將對象用做根響應容器可以讓您之後添加任意多個字段,而不會引發棄用。哎呀,您甚至能夠在新數組中返回不一樣的更新類型的對象,只要您確保保留舊數組便可。
我我的的喜愛始終是將Unix時間戳做爲響應中的日期,由於它們相對較短而且很容易解析。 可是,除非您是機器,不然它們很難轉換,而且實際上只能做爲真實日期來讀取。 另外一方面,從可讀性的角度來看,ISO-8601日期更好,但解析起來卻有點困難(儘管很少)。
應該不惜一切代價避免使用除這兩種之外的任何其餘字符串格式,由於它可能在解析時形成歧義。 我知道您能夠在文檔中指定本身的日期時間格式,客戶端能夠基於此格式解析日期,可是請記住:在沒有太多外部幫助的狀況下,API應該儘量易於理解。
若是對象A不是用戶; 而且包含諸如user_id,user_name,user_favorite_color,user_pet等字段,也許是時候在對象A內使用封裝的User對象了……
因爲(數據庫以及API)架構會隨着時間的流逝而變得愈來愈複雜,所以最好一開始就對其進行規範化並使其保持儘量的乾淨。
始終盡力以可能的方式使API面向將來。嘗試保留未來極可能須要其餘信息的屬性,以即可以延長主版本的使用壽命。
您須要先考慮一下:
假設您每一個Book對象都有一個is_available布爾值。儘管這足以讓咱們知道一本書爲假時該書不可用,但它並無告訴咱們爲何該書不可用或什麼時候能夠再次使用。未來,若是要添加該信息,則必須經過添加兩個額外的字段來加入到Book對象。
一種更乾淨的方法是使用一個可用性字段,該字段存儲一個Availability對象,該對象最初僅包含is_available字段,但能夠進行修改以包含有關該書的可用性的其餘信息(例如,該書不可用的緣由以及什麼時候將其再次可用的時間戳記),而不會在原來的架構中添加更多字段。
若是您使用的是版本控制方法,可在保證結構穩定的同時對API進行小的增量更改,可是每次更改時都應格外當心。
對結構的某些更改意味着當即棄用當前的主版本。 除非絕對必要,不然應避免使用它們。
請記住,下面列出的更改不是架構棄用的惟一緣由(一般能夠由您特定設計中的細節引發),而只是最多見的緣由。
您永遠沒法肯定您的用戶如何使用您的信息。無論字段看起來多麼微不足道或多餘,若是您將其交付生產時都犯了錯誤,那麼您將一直堅持到下一個主要版本。更改字段名稱顯然與刪除字段相同。
數據類型不只必須在響應中保持嚴格,並且在次要版本中也必須保持嚴格。這是與鬆散類型的開發環境有關的另外一種很差的作法。
您必須更改當前版本才能更改字段的數據類型。若是您在初次發佈時的響應中將數字做爲字符串傳遞,那麼在下一個主要版本以前,應始終將其做爲字符串返回。
返回數字(請記住,您不該該這樣)或字符串是枚舉形式,這很常見。
例如,您可能使用如下狀況:「 car」,「 truck」和「 motrcycle」字段用於vehicle_type字段。若是您注意到「摩托車週期」中的錯字,我知道您必定感到沮喪,但您沒法解決!不過,您能夠在下一個主要版本中進行操做(而且不要忘記將其添加到變動日誌中)。