
詳解API:應(yīng)用程序編程接口終極指南
/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 更具可讀性,但這并不是萬無一失的。
特別是在資源之間存在多種關(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ù)是可選的,所以我們也可以用它來獲取所有貢獻,并且我們可以用PUT
它POST
來改變和創(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)建、更新或刪除它。
如果我們向下遍歷關(guān)系圖而不是使用一個唯一標識符(如果存在)來從資源中檢索表示,則我們需要檢查 URL 中實現(xiàn)的關(guān)系是否成立。
以獲取嵌套評論為例
/blogs/X/articles/Y/comments/Z
獲取所有博客所有文章的所有評論也是一個問題。
此 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
如果我們的關(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)系,但通過不受此改變影響的自己的根資源直接鏈接我們的其他資源。
那么這一切的啟示是什么呢?
我們是否應(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
詳解API:應(yīng)用程序編程接口終極指南
精通API規(guī)范:構(gòu)建明確指導(dǎo)和預(yù)期的指南
API 優(yōu)先方法如何徹底改變軟件開發(fā)
掌握良好的 API 設(shè)計原則:是什么、為什么和怎么辦
API-first產(chǎn)品經(jīng)理的熱門 API 工具和 API 指標
ChatGPT生態(tài)系統(tǒng)的安全漏洞導(dǎo)致第三方網(wǎng)站賬戶和敏感數(shù)據(jù)泄露
想要系統(tǒng)了解Agentic Workflow,看這25篇論文就夠了
生成式 AI 在電商領(lǐng)域究竟有多牛,這款產(chǎn)品給出了回答
AI+搜索:在Elastic的推理API中嵌入大語言模型Cohere API