Graphql是什麼?先來一段AI給的回答:
GraphQL是一種為API設計的查詢語言,與REST相比,它提供了更高效、強大和靈活的方法來與資料互動。GraphQL由Facebook於2012年開發,並於2015年開源。其主要的優勢在於能夠允許客戶端精確地指定他們需要的資料,從而避免了過度獲取或資料不足的問題。
主要特性
- 精確獲取需要的資料:
- 單一端點:
- 型別系統:
- 查詢與修改:
- 實時資料(Subscription):
優勢和侷限
優勢:
- 減少資料傳輸:只返回客戶端請求的資料。
- 減少請求數:多個資料需求可以在單一查詢中解決。
- 靈活性高:客戶端可以自由構造查詢,無需伺服器頻繁更新API。
侷限:
- 複雜查詢效能問題:如果不加限制地進行深度查詢或大規模的資料巢狀,可能會對伺服器效能造成影響。
- 快取策略:相比於REST的URL級別快取,GraphQL需要更復雜的快取策略來最佳化效能。
- 學習曲線:對於開發者來說,需要學習新的查詢語法及其底層實現。
其他內容就不過多介紹了,大家感興趣可以自行去搜尋有關理論或說明。接下來我直接提供實戰入門演示。
以下開始正式演示正文:
先建立一個webapi專案作為服務端和一個控制檯專案作為客戶端,用來測試使用。以及對應的引用包,如下圖所示:

新建Quries資料夾,用來存放查詢使用的類和方法。以及新增一個測試用的類和string型別返回值的方法 Hello()

在啟動項或Program裡面,新增Graphql服務,並新增Query的型別註冊:

最後還要記得對映端點:

然後執行程式,例如我預設執行起來埠是5264,則開啟url(根據自己情況更改url地址):http://localhost:5264/graphql/
然後輸入查詢語句:query:{hello}就可以查出對應的返回內容。

客戶端裡面,建立graphql的客戶端請求,並輸入查詢的方法為hello的query語句,以及輸出的結果,如下圖所示。結果和上面的一樣,只是我只輸出data裡面的資料,data裡面的資料就是我們需要的結果。

接著做個擴充演示,建立一個巢狀實體類,用來模擬多種情況:

建立一個測試使用的服務,模擬具體查詢業務使用。

註冊服務和介面以後,執行程式,並在graphql裡面進行執行測試。當前測試的是輸出所有欄位。

現在,例如我把子集合去掉不要,那查詢出來也就不會帶有子集合的任何內容:

或者只需要指定的其他欄位,刪掉了描述、子集合的城市欄位:

同樣的,把查詢語句丟到客戶端程式裡面進行查詢,也可以查出指定欄位的內容:

上面演示的是查詢效果,也可以做增刪改等其他操作。
在測試服務類新增一個業務操作,模擬接收到引數以後進行了業務操作,最終返回一個代表成功的資料。例如:

新建一個Mutations資料夾,用來存放增刪改操作的類等。例如此處的測試使用的TestMutation.然後建立一個模擬傳入引數進行操作的方法,該方法返回上面服務類裡面的測試方法。

需要新增對修改有關操作的註冊:

然後啟動,做個測試。使用mutation語句進行操作,操作指定方法,方法裡面指定引數和欄位資料。可以看到服務端進入了前面預設的業務方法內,並且返回的true被客戶端成功接收。

在控制檯客戶端,也執行一下mutation操作,也能夠成功呼叫:

以上是查詢和修改操作的例子,graphql還可以做資料推送和訂閱,用於實現websocket的效果。
新建一個subscriptions資料夾,用來存放所有的訊息推送和訂閱有關的定義類。例如TestSub,裡面定義了一個推送方法OnTestPublish

在前面的測試服務裡面,新增ITopicEventSender事件介面的注入,以及新增一個方法,用來觸發推送功能。並且推送的主題,使用剛才定義的OnTestPublish

然後需要提供對推送服務的註冊,以及持久化選擇。

使用預設的持久化,該持久化選擇不建議上生產。具體原因,我去AI一下:
- 可擴充套件性問題:AddInMemorySubscriptions 儲存訂閱資訊是在記憶體中進行的。這意味著訂閱資料僅存在於單個程序中。如果你的應用程式需要在多個伺服器例項之間進行擴充套件,每個例項的記憶體中都會有獨立的訂閱狀態,從而導致狀態不一致。因此,在大型應用或高負載環境中,這種方法不能很好地擴充套件。
- 永續性缺失:使用記憶體儲存的另一個主要問題是資料的永續性。伺服器重啟或發生故障時,所有在記憶體中的訂閱資料將丟失。這對於生產環境來說是不可接受的,因為需要保證服務的穩定性和資料的永續性。
- 資源使用效率:隨著訂閱數量的增加,記憶體的使用量也會隨之上升。在記憶體資源有限的環境中,這可能會影響應用程式的整體效能和響應速度。
- 故障恢復:在記憶體中的訂閱管理缺乏有效的故障恢復機制。如果系統崩潰或需要進行維護,恢復訂閱狀態將非常困難,可能需要從客戶端重新建立訂閱。
為了解決這些問題,生產環境中通常建議使用持久化和可擴充套件的訂閱儲存方案,比如基於 Redis 的 AddRedisSubscriptions 方法。大佬們感興趣可以自己去擴充下。
現在缺少一個觸發條件,由於咱們建立的是webapi專案,自帶控制器,那我把控制器做個改造,透過swagger來呼叫進行觸發資料推送,直接在請求裡面,呼叫推送方法:

最後,由於推送使用了websocket,所以也需要新增對websocket的註冊:

然後啟動程式,使用subscription進行訂閱onTestPublish主題訊息。執行以後,會一直監聽,除非我們取消監聽。

開啟swagger,直接呼叫並測試,可以看到皮膚接收到了測試推送的資料。

客戶端要實現訂閱,需要做一些改動。訂閱的事件是字串型別,所以需要建立一個字串型別的屬性,用來接收資料:

然後客戶端建立時候,需要使用websocket端點。然後再建立訂閱語句

接下來是訂閱的具體實現演示:

允許,並透過swagger呼叫兩次測試,都可以被監聽到。

同時,之前開啟的graphql演示皮膚,也可以看到能夠收到後續訊息,說明支援多客戶端接收,符合websocket的推送效果。

有關實現的核心程式碼。服務端註冊有關:
// 新增GraphQL服務
builder.Services
.AddGraphQLServer()
.AddQueryType<TestQuery>()
.AddMutationType<TestMutation>()
.AddSubscriptionType<TestSub>()
.AddInMemorySubscriptions(); // 預設訊息持久化(生產情況建議更換)
var app = builder.Build();
app.UseWebSockets();
// 對映GraphQL端點
app.MapGraphQL();
客戶端實現:
var option = new GraphQLHttpClientOptions
{
EndPoint = new Uri("http://localhost:5264/graphql"),
// 設定 WebSocket 端點以支援訂閱
WebSocketEndPoint = new Uri("ws://localhost:5264/graphql")
};
using var client = new GraphQLHttpClient(/*"http://localhost:5264/graphql"*/ option , new NewtonsoftJsonSerializer());
// var request = new GraphQLRequest
// {
// Query = @"mutation{
// otherOperation(info:{address:""龍崗區寶龍街道"",city:""大大大深圳"",phone:""10100011""})
//}"
// };
// var response = await client.SendQueryAsync<object>(request);
// Console.WriteLine(response.Data);
// 定義訂閱請求
var subscriptionRequest = new GraphQLRequest
{
Query = @"
subscription {
onTestPublish
}"
};
// 建立訂閱流
var subscriptionStream = client.CreateSubscriptionStream<OnTestPublishResponse>(subscriptionRequest);
// 訂閱訊息流
var subscription = subscriptionStream.Subscribe(
response =>
{
if (response.Errors != null)
{
Console.WriteLine("Error occurred: " + response.Errors);
}
else
{
Console.WriteLine($"Received message: {response.Data.OnTestPublish}");
}
},
error => Console.WriteLine($"Subscription error: {error.Message}"),
() => Console.WriteLine("Subscription completed."));
// 模擬其他邏輯(例如,在某個時刻取消訂閱,這兒透過輸入任意按鍵觸發取消和釋放)
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
// 取消訂閱並關閉 WebSocket 連線
subscription.Dispose();
client.Dispose();
如果需要我本地演示的程式碼專案,可以在本人公眾號【Dotnet Dancer】回覆: 程式碼演示 即可獲取開源專案地址。如果需要其他諮詢或合作,可V:WeskyNet001

如果以上內容對你有幫助,歡迎大佬們點贊、關注公眾號或轉發。謝謝大家!