GraphQL Server with Hot Chocolate

使用 Hot Chocolate v13.81 實作 GraphQL Server。

引言

在 .NET Core 有多種實作 GraphQL 方案。本人選取 ChilliCream GraphQL PlatformHot Chocolate v13 實作 Server 端;Strawberry Shake v13 實作 Client 端。

實作前需知

GraphQL 發展過程已過數個迭代。就 Hot Chocolate 來說至少有主要三種世代的開發模式:Schema-first 、Code-first、Annotation-based。我們只採用最新的開發模式:Annotation-based。

開發教學部份官網已有最新的版本教學說明。強烈建議以官網的說明為主,因為其他網頁的說明太多是舊的模式,就本人經驗上與 Annotation-based 模式不相容。

開發環境

平台: .NET6

IDE: Visual Studio 2022

框架: Blazor Swagger API

在 .NET6 Core 框架上,Swagger API 與 GraphQL Service 可以共存。

Hot Chocolate 官方教學

官方教學已從入門開始,就不再手把手教學。

Hot Chocolate 關鍵程式碼紀錄

官方教學已從入門開始,就不再手把手教學。以下只紀錄重要的關鍵的部份。

Program.cs -註冊 GraphQLServer

使用 Annotation-based 模式開發需要寫的碼變得很少。

GraphQL Schema 只有一套,在註冊時,AddQueryType、AddMutationType、AddSubscriptionType 各別只能註開一次,為各別的進入點。

Program.cs
[...略...]
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
//## for GrqphQL
builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()   // GrqphQL query.
    .AddType<ProductQuery>() // ExtendObjectType of Query
    .AddType<BookQuery>()    // ExtendObjectType of Query
    .AddMutationType<Mutation>()         // for GrqphQL mutation.
    .AddSubscriptionType<Subscription>() // for GrqphQL subscriptions.
    .AddInMemorySubscriptions();
    
[...略...]
var app = builder.Build();
[...略...]

app.MapControllers();

//## for GrqphQL
app.MapGraphQL();
app.UseWebSockets(); // for GrqphQL subscriptions.

app.Run();

GraphQL - Query 主類別

若該 GraphQL query 的函式量不大其實可以全部集中實作,相信實務上這情境不多,大多情況一定需要分猜偶合,這技巧在此稱作 ExtendObjectType

Query.cs
/// GrqphQL query root
/// 若 query 的函式量不大其實可以全部集中在此,相信實務上這情現不多。
public class Query
{
  public string Hello(string name = "World") => $"Hello, {name}";
}

ExtendObjectType of Query

當然實務上 Query 的量一定很多,這時用ExtendObjectType 來擴充。若 Mutation 、Subscription 的量很多也是依樣擴充。

ProductQuery.cs
[ExtendObjectType(nameof(Query))] //※ ExtendObjectType of Query
public class ProductQuery
{
  readonly ProductService _bizSvc;

  public ProductQuery([Service]IServiceProvider provider)
  {
    _bizSvc = provider.GetRequiredService<ProductService>();
  }

  public Product[] GetProductList() => _bizSvc.QueryProducts().ToArray();
}

GraphQL Schema 範例

若成功的話預設 graphql 網址是:/graphql

透過 GraphQL 標註, Hot Chocolate 將自動生成相應的 GraphQL Schema。

Mutation 實作注意事項

mutation 的實作本以為會很麻煩,Hot Chocolate 已整理的非常簡易就能完成。DI injection 大量且全面性的採用。比較麻煩的是命名規範:

輸入:xxxInput

輸出:xxxPayload

using HotChocolate.Subscriptions;

public record ProductAddedPayload(bool IsSuccess, Product NewProduct);
public record ProductInput(string Name, int Quantity);

public class Mutation
{
  public async Task<ProductAddedPayload> AddProdcut(ProductInput input,
    [Service] IServiceProvider provider,     // DI injection
    [Service] ITopicEventSender eventSender) // DI injection, 用於subscription。
  {
    var bizSvc = provider.GetRequiredService<ProductService>(); // DI injection

    var product = bizSvc.AddProduct(new Product(0, input.Name, input.Quantity));
    var result = new ProductAddedPayload(true, product);

    await eventSender.SendAsync(nameof(AddProdcut), result); 
    // for subscription, 一般並不需要。 
    // mutation 通告:通告某 client 已有異動了。
    
    return result;
  }
}

Subscrition 實作注意事項

注意:GraphQL subscrition 的通訊協定是 WebSocket 記得要打開。

GraphQL subscrition 應用情境主要有二種:

一、mutation 通告。

二、server side streaming。用於即時訊息廣播或 Message Queue、Message Bus 訊息串流等。

Subscription.cs
using HotChocolate.Subscriptions;

public record TimerTick(string Tick);

public class Subscription
{
  #region Server side streaming sample

  /// <summary>
  /// for: Server side streaming
  /// </summary>
  public async IAsyncEnumerable<TimerTick> OnTimerTickStream([Service] ITopicEventReceiver eventReceiver)
  {
    do
    {
      yield return new TimerTick(Tick: DateTime.Now.ToString("HH:mm:ss.fff"));
      await Task.Delay(1000);
    }
    while (true);
  }

  /// <summary>
  /// Server side streaming
  /// subscription 的 in type 與 out type 可以不一樣,此範例太簡單才會一樣。
  /// </summary>
  [Subscribe(With = nameof(OnTimerTickStream))] //------ 關鍵點
  public TimerTick OnTimerTick([EventMessage] TimerTick timerTick)
    => timerTick;

  #endregion

  /// <summary>
  /// Subscribe mutation evnet sample
  /// mutation 通告:通告某 client 已有異動了。
  /// subscription 的 in type 與 out type 可以不一樣,此範例太簡單才會一樣。
  /// </summary>
  [Subscribe]
  [Topic(nameof(Mutation.AddProdcut))]
  public ProductAddedPayload OnProductAdded([EventMessage] ProductAddedPayload message)
    => message;
}

開發測試工具 - Banana Cake Pop

Hot Chocolate 的開發測試工具叫作 Banana Cake Pop。如何使用請看教學影片吧。

完整程式碼

(EOF)

Last updated