
掌握API建模:基本概念和實(shí)踐
"status": "success",
"data": {
"url": "https://www.wundergraph.com/blog/long_running_operations",
"sentiment": "positive"
}
}
但是,正如我們之前所說(shuō),此操作可能需要很長(zhǎng)時(shí)間才能完成,并且用戶可能會(huì)取消它。
更好的方法是將我們的 API 設(shè)計(jì)為異步 API。
現(xiàn)在讓我們將同步 API 轉(zhuǎn)換為異步 API。
我們不應(yīng)立即返回響應(yīng),而應(yīng)返回具有唯一標(biāo)識(shí)符的響應(yīng),以便客戶端可以輪詢服務(wù)器以獲取結(jié)果。
設(shè)計(jì)此類 API 的正確方法是返回 202 Accepted 狀態(tài)代碼。
因此,在這種情況下,我們的 API 響應(yīng)可能如下所示。它將是一個(gè)帶有狀態(tài)代碼 202 和以下正文的響應(yīng)。
{
"data": {
"url": "https://www.wundergraph.com/blog/long_running_operations",
"id": 1
},
"links": [
{
"rel": "status",
"href": "http://localhost:3000/api/v1/analyze_sentiment/1/status"
},
{
"rel": "cancel",
"href": "http://localhost:3000/api/v1/analyze_sentiment/1/cancel"
}
]
}
我們不是立即返回結(jié)果,而是返回一個(gè)鏈接列表,以便 API 的調(diào)用者可以獲取作業(yè)的當(dāng)前狀態(tài)或取消它。
客戶端然后可以使用以下命令來(lái)獲取作業(yè)的狀態(tài):
curl http://localhost:3000/api/v1/analyze_sentiment/1/status
太好了,我們現(xiàn)在已經(jīng)將 API 設(shè)計(jì)為異步的。
接下來(lái),我們將看看如何使用 GraphQL 來(lái)設(shè)計(jì)類似的 API。
與 REST 類似,我們可以使用 GraphQL 實(shí)現(xiàn)異步 API 來(lái)輪詢服務(wù)器的作業(yè)狀態(tài)。但是,GraphQL 不僅支持查詢和變更,還支持訂閱。這意味著,我們有更好的設(shè)計(jì)方法我們的 API 而不是強(qiáng)制客戶端輪詢服務(wù)器。
這是我們的 GraphQL 架構(gòu):
type?Job?{
??id:?ID!
??url:?String!
??status:?Status!
??sentiment:?Sentiment
}
enum?Status?{
????queued
????processing
????finished
????cancelled
????failed
}
enum?Sentiment?{
??positive
??negative
??neutral
}
type?Query?{
????jobStatus(id:?ID!):?Job
}
type?Mutation?{
??createJob(url:?String!):?Job
??cancelJob(id:?ID!):?Job
}
type?Subscription?{
??jobStatus(id:?ID!):?Job
}
創(chuàng)建作業(yè)將如下所示:
mutation?($url:?String!)?{
??createJob(url:?$url)?{
????id
????url
????status
??}
}
一旦我們得到了 id,我們就可以訂閱 Job 的變化:
subscription?($id:?ID!)?{
??jobStatus(id:?$id)?{
????id
????url
????status
????sentiment
??}
}
這是一個(gè)好的開始,但我們甚至可以進(jìn)一步改進(jìn)此架構(gòu)。在當(dāng)前狀態(tài)下,我們必須使“情感”可為空,因?yàn)樵撟侄沃挥性谧鳂I(yè)完成后才有值。
讓我們的 API 更直觀:
interface?Job?{
??id:?ID!
??url:?String!
}
type?SuccessfulJob?implements?Job?{
??id:?ID!
??url:?String!
??sentiment:?Sentiment!
}
type?QueuedJob?implements?Job?{
??id:?ID!
??url:?String!
}
type?FailedJob?implements?Job?{
??id:?ID!
??url:?String!
??reason:?String!
}
type?CancelledJob?implements?Job?{
??id:?ID!
??url:?String!
??time:?Time!
}
enum?Sentiment?{
??positive
??negative
??neutral
}
type?Query?{
????jobStatus(id:?ID!):?Job
}
type?Mutation?{
??createJob(url:?String!):?Job
??cancelJob(id:?ID!):?Job
}
type?Subscription?{
??jobStatus(id:?ID!):?Job
}
將 Job 變成一個(gè)接口使我們的 API 更加明確。我們現(xiàn)在可以使用以下訂閱來(lái)訂閱作業(yè)狀態(tài):
subscription?($id:?ID!)?{
??jobStatus(id:?$id)?{
????__typename
????...?on?SuccessfulJob?{
??????id
??????url
??????sentiment
????}
????...?on?QueuedJob?{
??????id
??????url
????}
????...?on?FailedJob?{
??????id
??????url
??????reason
????}
????...?on?CancelledJob?{
??????id
??????url
??????time
????}
??}
}
只有當(dāng) __typename 字段設(shè)置為“SuccessfulJob”時(shí)才會(huì)返回 sentiment 字段。
從上面的例子我們可以看出,REST 和 GraphQL 都可以用來(lái)設(shè)計(jì)異步 API?,F(xiàn)在讓我們開始討論每種方法的優(yōu)缺點(diǎn)。
首先,我要說(shuō)的是,異步 API 的兩種方法都比同步 API 更好。無(wú)論您選擇使用 REST 還是 GraphQL,當(dāng)一個(gè)操作需要超過(guò)幾秒鐘才能完成時(shí),我總是建議您以異步方式設(shè)計(jì) API。
現(xiàn)在,讓我們看看造成不同的微小細(xì)節(jié)。
REST 方法的一個(gè)巨大好處是一切都是資源,我們可以利用超媒體控件(Hypermedia)。讓我將這個(gè)“行話”翻譯成簡(jiǎn)單的詞: REST API 的核心概念之一是每個(gè)“事物”都可以通過(guò)唯一的 URL 訪問(wèn)。如果您向 API 提交作業(yè),您將得到一個(gè) URL,您可以使用該 URL 檢查作業(yè)的狀態(tài)。
相比之下,GraphQL 只有一個(gè)端點(diǎn)。如果你通過(guò) GraphQL mutation 提交作業(yè),你得到的是一個(gè) ID! 類型的 id。如果你想檢查作業(yè)的狀態(tài),你必須使用 id 作為Query 或 Subscription 類型的正確根字段的參數(shù)。作為開發(fā)人員,您如何知道 Job id 與 Query 或 Subscription 類型的根字段之間的關(guān)系?不幸的是,您不知道!
如果你想在設(shè)計(jì)你的 GraphQL schema 時(shí)做得很好,你可以將這些信息放入字段的描述中。但是,GraphQL 規(guī)范不允許我們像在 REST 中那樣使這些“鏈接”顯式。
相同的規(guī)則適用于作業(yè)的取消。使用 REST API,您可以向客戶端返回一個(gè) URL,可以調(diào)用該 URL 來(lái)取消作業(yè)。
使用 GraphQL,您必須知道必須使用 cancelJob mutation 來(lái)取消作業(yè)并將 id 作為參數(shù)傳遞。
這聽起來(lái)可能有點(diǎn)夸張,因?yàn)槲覀兪褂玫氖且粋€(gè)非常小的 schema,但想象一下,如果我們的 Query 和 Mutation 類型上都有幾百個(gè)根字段??赡芎茈y找到正確的根字段來(lái)使用。
缺乏資源和唯一 URL 似乎是 GraphQL 的弱點(diǎn)。但是,我們也可以反過(guò)來(lái)論證。
可以在 REST API 中返回帶有操作的鏈接。也就是說(shuō),我們從提交作業(yè)中返回的鏈接并不明顯。此外,我們也不知道例如是否應(yīng)該使用 POST 或 GET 調(diào)用取消 URL?;蛘呶覀儜?yīng)該只刪除作業(yè)?
有額外的工具可以幫助解決這個(gè)問(wèn)題。Kevin Swiber 的 Siren(https://github.com/kevinswiber/siren) 就是這樣的工具/規(guī)范。
如果你想設(shè)計(jì)好的 REST API,你絕對(duì)應(yīng)該研究像 Siren 這樣的解決方案。
也就是說(shuō),考慮到 REST API 是占主導(dǎo)地位的 API 風(fēng)格這一事實(shí),Siren 已有 5 年多的歷史并且只有 1.2k 星表明存在問(wèn)題。
對(duì)我來(lái)說(shuō),好的 (REST) API 設(shè)計(jì)似乎是可選的。大多數(shù)開發(fā)人員構(gòu)建簡(jiǎn)單的 CRUD 風(fēng)格的 API,而不是利用超媒體的強(qiáng)大功能。
另一方面,由于缺乏資源,GraphQL 不允許您構(gòu)建超媒體 API。但是,Schema 在 GraphQL 中是強(qiáng)制性的,迫使開發(fā)人員使他們的 CRUD 風(fēng)格的 API 更加明確和類型安全。
在我個(gè)人看來(lái),超媒體 API 比 CRUD 風(fēng)格的 API 強(qiáng)大得多,但這種強(qiáng)大是有代價(jià)的,并且增加了復(fù)雜性。正是這種復(fù)雜性使 GraphQL 成為大多數(shù)開發(fā)人員更好的選擇。
作為 REST API 的開發(fā)者,你“可以”使用 Siren,但大多數(shù)開發(fā)者就是不在乎。作為 GraphQL API 的開發(fā)者,你“必須”有一個(gè) Schema,沒(méi)有辦法繞過(guò)它。
如果你看看我們的 GraphQL 模式的第二個(gè)版本,接口的使用幫助我們使 API 非常明確和類型安全。它并不完美,我們?nèi)匀蝗狈Α版溄印?,但這是一個(gè)很好的權(quán)衡。
訂閱作業(yè)的狀態(tài)比輪詢狀態(tài)更優(yōu)雅,這是顯而易見的。從 API 用戶的心智模型來(lái)看,訂閱事件流比輪詢狀態(tài)更直觀。
也就是說(shuō),沒(méi)有什么是免費(fèi)的。
為了能夠使用訂閱,您通常必須使用 WebSockets。WebSockets 是有狀態(tài)的,它是有代價(jià)的。
將 WebSockets 添加到您的堆棧中也意味著 API 后端更加復(fù)雜。您的托管服務(wù)提供商是否支持 WebSockets?一些無(wú)服務(wù)器環(huán)境不允許長(zhǎng)時(shí)間運(yùn)行的操作或只是拒絕 HTTP 升級(jí)請(qǐng)求。WebSocket 連接的擴(kuò)展方式也不同于短期連接HTTP 連接。
WebSockets 也是 HTTP 1.1 功能,這意味著您不能將它們與 HTTP/2 一起使用。如果您的網(wǎng)站對(duì)所有端點(diǎn)都使用 HTTP/2,則客戶端必須為 WebSocket 打開另一個(gè) TCP 連接。
此外,WebSockets 可能無(wú)法在所有環(huán)境中工作,例如如果您使用反向代理或使用負(fù)載均衡器。
另一方面,HTTP 輪詢是一種非常簡(jiǎn)單而乏味的解決方案。因此,雖然 GraphQL 訂閱為 API 用戶提供了一種更簡(jiǎn)單的心智模型,但它們?cè)趯?shí)施方面帶來(lái)了巨大的成本。
請(qǐng)記住,您不會(huì)被迫將訂閱與 GraphQL 一起使用。您仍然可以通過(guò)使用查詢而不是訂閱來(lái)使用 HTTP 輪詢作業(yè)的狀態(tài)。
REST 和 GraphQL API 樣式都是設(shè)計(jì)同步和異步 API 的絕佳工具。它們各有優(yōu)缺點(diǎn)。
GraphQL 對(duì)模式及其類型系統(tǒng)更加明確。另一方面,由于獨(dú)特的 URL 和超媒體控件,REST 可以更加強(qiáng)大。
我個(gè)人非常喜歡 Siren 的方法。但是,REST API 缺乏明確的架構(gòu)給普通開發(fā)人員留下了太多的解釋空間。
借助正確的工具和良好的 API 治理,您應(yīng)該能夠設(shè)計(jì)出色的 REST API。
有人可能會(huì)爭(zhēng)辯說(shuō) GraphQL 具有更多開箱即用的功能并且需要更少的治理,但我認(rèn)為這不是真的。正如您從我們的 GraphQL 模式的兩個(gè)版本中看到的那樣,在設(shè)計(jì)方面有很大的靈活性GraphQL Schema。即使是 Schema 的第二個(gè)版本也可以進(jìn)一步改進(jìn)。
最后,我看不出一種解決方案比另一種解決方案好多少。在 API 設(shè)計(jì)上投入精力比在 REST 或 GraphQL 之間進(jìn)行選擇要重要得多。
與您的用戶交談并弄清楚他們想如何使用您的 API。他們是習(xí)慣 REST API 還是 GraphQL API?他們會(huì)從 Subscriptions 而不是 WebSockets 中受益,還是他們更喜歡簡(jiǎn)單無(wú)聊的輪詢?
也許你甚至不必在 REST 和 GraphQL 之間做出選擇。如果你可以構(gòu)建一個(gè)很棒的 REST API,你可以輕松地用 GraphQL 包裝它,或者反過(guò)來(lái)。這樣,你可以為你的用戶提供兩種 API 樣式,如果這能給他們帶來(lái)價(jià)值。
你的收獲應(yīng)該是良好的 API 設(shè)計(jì)和與你的用戶交流比選擇酷炫的技術(shù)重要得多。
文章轉(zhuǎn)自微信公眾號(hào)@云原生AI視界
掌握API建模:基本概念和實(shí)踐
程序員常用的API接口管理工具有哪些?
簡(jiǎn)化API縮寫:應(yīng)用程序編程接口終極指南
如何為你的項(xiàng)目挑選最佳API?完整選擇流程解讀
應(yīng)用程序開發(fā)蓬勃發(fā)展的必備開放API
.NET Core Web APi類庫(kù)如何內(nèi)嵌運(yùn)行和.NET Core Web API 中的異常處理
.NET Core Web API + Vue By Linux and Windows 部署方案知識(shí)點(diǎn)總結(jié)
優(yōu)化利潤(rùn):計(jì)算并報(bào)告OpenAI支持的API的COGS
用于集成大型語(yǔ)言模型的LLM API
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)