/users/:userName/articles/:articleId

關(guān)于這個:

/comments/:commentId
/articles/:articleId

采用這種方法的主要原因是可讀性;嵌套資源 URL 可以傳達一個資源屬于另一個資源的信息。它呈現(xiàn)出一種層次關(guān)系,就像文件系統(tǒng)中的目錄一樣。

這些 URL 傳達的關(guān)系含義較少:

/books/:bookId
/rating/:ratingId

比這些 URL:

/books/:bookId
/books/:bookId/ratings/:ratingId

我們可以直接看到我們請求的評分屬于某本特定的書。在很多情況下,這可以使調(diào)試更容易。

之所以說是層次關(guān)系,是因為底層數(shù)據(jù)模型不一定是層次化的。例如,在 GitHub 上,一個用戶可以為多個存儲庫貢獻代碼,而一個存儲庫可以有來自不同用戶的貢獻。這是一種多對多關(guān)系。

/users/:userName/repos
/repos/:repoName/users

如果您只知道其中一個端點,那么它可能看起來是一對多的關(guān)系。

其他更技術(shù)性的原因是嵌套資源的相對 ID上下文。

例如,房子有門牌號,但這些門牌號只與它們所屬的街道有關(guān)。如果你知道房子的門牌號是 42,但你不記得街道,那么這對你沒有多大幫助。

/street/:streetName/house/:houseNumber

另一個例子是文件系統(tǒng)中的文件名。如果數(shù)百個不同目錄中有數(shù)百個文件以同樣的名字命名,那么僅僅知道我們的文件叫什么名字是沒有用的。

/home/kay/Development/project-a/README.md
/home/kay/Development/project-b/README.md

如果我們使用關(guān)系數(shù)據(jù)庫,我們通常對所有數(shù)據(jù)記錄都有唯一的鍵,但正如我們所見,對于其他類型的數(shù)據(jù)存儲(如文件系統(tǒng)),情況不一定如此。

嵌套 URL 也很容易操作。如果 URL 中編碼了層次結(jié)構(gòu),我們可以刪除 URL 的部分內(nèi)容以向上爬升此層次結(jié)構(gòu)。這使得帶有嵌套資源的 API 導(dǎo)航變得相當(dāng)簡單。

總而言之,我們希望使用嵌套資源來提高可讀性,進而改善開發(fā)人員體驗,有時我們甚至必須使用它們,因為數(shù)據(jù)源不提供僅通過其 ID 來識別嵌套資源的方法

缺點

現(xiàn)在我們討論了為什么應(yīng)該使用嵌套的原因,討論另一方面也很重要:為什么我們不應(yīng)該嵌套我們的資源?

雖然筑巢有時是必要的并且無法避免,但它通常是一個我們應(yīng)該牢記的特定成本或危險的選擇。

讓我們逐一看一下。

可能很長的 URL

我們之前了解到嵌套資源可以使我們的 URL 更具可讀性,但這并不是萬無一失的。

特別是在資源之間存在多種關(guān)系的相當(dāng)復(fù)雜的系統(tǒng)中,嵌套方法可能會導(dǎo)致相當(dāng)長且復(fù)雜的 URL。

/customers/:customerId/projects/:projectId/orders/:orderId/lines/:lineId

如果我們使用長字符串作為 ID,這個問題可能會變得更加嚴重:

/customers/8a007b15-1f39-45cd-afaf-fa6177ed1c3b/projects/b3f022a4-2970-4840-b9bb-3d14709c9d2a/orders/af6c1308-613f-40ff-9133-a6b993249c88/lines/3f16dca9-870e-4692-be2a-ea6d883b9dfd

因此,當(dāng)我們開始沿著這條路走下去時,我們應(yīng)該有時退后一步,看看我們是否仍然能夠?qū)崿F(xiàn)提高可讀性的目標。

經(jīng)驗法則是最大嵌套深度為 2。有時深度為 3 也是可以的。例如,如果我們的 ID 很短且易于閱讀。

/author/kay-ploesser/book/react-from-zero/review/23

冗余端點

一般來說,使用嵌套資源不如僅使用根資源那么靈活。

例如,如果我們有多對多關(guān)系。存儲庫有多個貢獻者,但每個用戶也可以為各種存儲庫做出貢獻。

如果我們想通過嵌套資源實現(xiàn)這一點,我們必須為這種關(guān)系單獨創(chuàng)建兩個端點

/user/:userName/repositories
/repositories/:repositoryName/contributors

如果我們想在不嵌套的情況下實現(xiàn)這一點,我們可以為貢獻定義一個根資源,該資源還允許在其 URL 中使用過濾參數(shù)。

/contributions?userName=:userName&repositoryName=:repositoryName

參數(shù)是可選的,所以我們也可以用它來獲取所有貢獻,并且我們可以用PUTPOST來改變和創(chuàng)建關(guān)系。

雖然這似乎不是一對多關(guān)系的問題,其中關(guān)系的一部分不能有多個連接,但我們?nèi)匀豢梢栽谀硞€時候搜索嵌套資源在其父資源中的所有記錄。

因此,當(dāng)有這個端點時:

/mothers/:motherName/children

我們可能仍然想獲取所有母親的所有孩子,并為此創(chuàng)建一個新的端點

/children

冗余端點也會增加我們的 API 的表面,雖然資源關(guān)系中更易讀的 URL 對開發(fā)人員體驗來說是一件好事,但大量的端點卻不是。

多個端點增加了 API 所有者記錄整個過程的努力,并使新客戶的入職變得更加麻煩。

返回相同表示的多個端點也可能導(dǎo)致緩存問題,并可能違反RESTful API 設(shè)計的核心原則之一。

這個問題可以通過 HTTP 重定向來解決,因此所有表示都從中央根資源返回并可以緩存,但仍然需要代碼來實現(xiàn)這一點。

它還可能違反另一個核心原則,即統(tǒng)一接口。

當(dāng)客戶端持有資源的表示(包括附加的任何元數(shù)據(jù))時,它就擁有足夠的信息來修改或刪除服務(wù)器上的資源,前提是它有這樣做的權(quán)限。

如果表示不包含有關(guān)嵌套的信息,并且我們沒有根資源來直接訪問它;我們就無法創(chuàng)建、更新或刪除它。

多個數(shù)據(jù)庫查詢

如果我們向下遍歷關(guān)系圖而不是使用一個唯一標識符(如果存在)來從資源中檢索表示,則我們需要檢查 URL 中實現(xiàn)的關(guān)系是否成立。

以獲取嵌套評論為例

/blogs/X/articles/Y/comments/Z

獲取所有博客所有文章的所有評論也是一個問題。

  1. 查詢所有博客
  2. 查詢每個博客的每篇文章
  3. 查詢每篇文章的每條評論

此 API 設(shè)計給我們帶來了N+1 查詢問題的巨大困擾。

如果我們只有評論的根資源,我們可以查詢它并在需要時添加一些過濾參數(shù)。如果評論具有全局唯一 ID,我們可以直接查詢它們。

/comments/Z
/comments?before=A&after=B

安全

如果我們共享資源鏈接,則 URL 內(nèi)編碼的所有數(shù)據(jù)都可能會暴露給第三方,即使他們無權(quán)從我們的 API 請求表示。

當(dāng)在互聯(lián)網(wǎng)上通過 HTTP 請求任何內(nèi)容時,中間件都會記錄 URL,因此甚至不必在社交媒體或類似媒體上主動分享鏈接。

例如此圖片鏈接:

/users/:userName/images/:imageId

如果我們將其分享到某個地方,我們就會知道我們有一個具有特定名稱的用戶,并且他們在我們的服務(wù)上上傳了圖像。

如果圖像鏈接是根資源,則不會出現(xiàn)此類信息。

/images/:imageId

更改 URL

如果我們的關(guān)系發(fā)生變化,它們編碼的 URL 就不再穩(wěn)定。

有時這可能很有用,但更多的時候我們希望保留我們的 URL,以便舊鏈接不會停止工作。

例如,這種所有者-產(chǎn)品關(guān)系:

/owners/kay/products/1234
/owners/xing/products/1234

如果該產(chǎn)品可以作為根資源訪問,那么誰擁有它就無關(guān)緊要了。

/products/1234

正如我之前提到的,如果關(guān)系經(jīng)常發(fā)生變化,我們也可以考慮將關(guān)系本身視為一種資源。

/posessions?owner=kay&product=1234

通過這種方法,我們可以通過一個端點改變關(guān)系,但通過不受此改變影響的自己的根資源直接鏈接我們的其他資源。

總結(jié)

那么這一切的啟示是什么呢?

我們是否應(yīng)該嵌套我們的資源?

有時這是無法避免的,因為數(shù)據(jù)源根本沒有給我們?nèi)魏纹渌x擇,但如果我們有選擇,我們應(yīng)該考慮所有的利弊。

如果數(shù)據(jù)是嚴格分層的,嵌套不是太深,并且關(guān)系不會經(jīng)常更改,我會使用嵌套資源。

與開發(fā)人員體驗方面的優(yōu)勢相比,這些缺點并不算太大。

如果數(shù)據(jù)容易發(fā)生關(guān)系變化或者一開始就有相當(dāng)復(fù)雜的關(guān)系,那么維護根資源甚至考慮完全不同的方法(如 GraphQL)會更容易。

更多的端點,以及嵌套場景所暗示的更復(fù)雜的端點意味著需要編寫更多的代碼和文檔。這不會引起技能或專業(yè)知識方面的可行性問題,而往往只是開發(fā)和維護成本的問題。因此,即使我們知道如何做,并且安全性或可緩存性不是問題,我們也必須問自己這是否能給我們帶來任何競爭優(yōu)勢。

原文鏈接:REST API Design Best Practices for Sub and Nested Resources

上一篇:

REST API 設(shè)計參數(shù)和查詢字符串使用的最佳實踐

下一篇:

精通API規(guī)范:構(gòu)建明確指導(dǎo)和預(yù)期的指南
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費