Hot Chocolate？何それ美味しいのって美味しいに決まってるだろ！

# はじめに
これは、[C# Advent Calendar 2024](https://qiita.com/advent-calendar/2024/csharplang) の22日目の記事です。

忘れかけていたGraphQLを思い出すためにこの記事を書きました。頭の整理となる良い機会でした:writing_hand:ありがとうアドベントカレンダー:bow:

https://graphql.org/

## GraphQLの大雑把なまとめ

GraphQLとは、ユーザーと様々なデータソースとを橋渡しするクエリ言語であり、そのための実行環境です。GraphQLサーバーは、どのようなデータをどのように処理するかをサーバー構築者が予め定義します。

データ構造を**スキーマ**、データ取得（SELECT）の定義を**クエリ**、データ変更（INSERT、UPDATE、DELETE）の定義を**ミューテーション**、データ処理（関数）を**リゾルバ**と言います。

<details><summary>最近仲良くなったGeminiくんがまとめた表</summary>

| 概念        | GraphQL用語 | SQLでの対応例 | 説明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| データ構造    | スキーマ    | CREATE TABLE | データの型（例：Int, String, Boolean, ID）やオブジェクトの構造（フィールドとその型）を定義します。クライアントとサーバー間のデータ交換のルールを定める契約のような役割を果たします。型安全性を提供し、開発効率を高めます。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| データ取得    | クエリ      | SELECT     | サーバーからデータを取得するための操作を定義します。取得したいデータのフィールドをクライアントが自由に指定できるため、必要なデータだけを取得できます（オーバーフェッチ/アンダーフェッチの解消）。GraphQLサーバーに送信するリクエストそのものでもあります。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| データ変更    | ミューテーション | INSERT, UPDATE, DELETE | サーバー上のデータを変更（作成、更新、削除）するための操作を定義します。クエリと同様に、操作名と必要な引数、返り値の型を定義します。データ変更に加えて、キャッシュの更新や他の副作用を伴う場合もあります。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| データ処理    | リゾルバ    | (SQLの実行、ストアドプロシージャなど) | クエリまたはミューテーションで定義された操作に対応する処理（関数）を実装します。データベースへのアクセス、外部APIへのリクエスト、データの変換・加工、ビジネスロジックの実装、エラーハンドリングなど、データ処理の中核を担います。クエリやミューテーションが「何をしたいか」を宣言するのに対し、リゾルバは「どのようにそれを実現するか」を記述します。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |


</details>

当初はクエリやミューテーションに関数宣言（シグネチャ）が記載されているため、リゾルバと何が違うのかと混乱していました。定義に目を向けるとクエリ/ミューテーション、処理（実装）に目を向けるとリゾルバ、と言うことですね。

## Hot Chocolateの飲み方
さて、C#でGraphQLサーバーを構築する場合、有名どころと言えばHot Chocolateです。Geminiくんに聞くとHot Chocolateには二つのアプローチがあるそうです。

+ コードファースト
C#のコードから直接GraphQLスキーマを構築する（EF Coreを使用）
+ スキーマファースト
SDL (Schema Definition Language) を使用してスキーマを記述


コードファーストしか知らなかったので、スキーマファーストとはちょっとビックリしました。そんなアプローチも用意されていたんですね。（Geminiくんありがとう:blush:）

ただ、C#を使っているほとんどの人はコードファーストを選択するのではないでしょうか。

そしてその場合、SDLのリゾルバを書く必要がありません。リゾルバは`Query`クラスと`Mutation`クラスの中のメソッドとなります。EF Coreで書くだけなので、その方面に強い方は迷わずコードファースト一択となるでしょう:laughing:

### コードファーストによるリゾルバ実装例

以下は`Query`クラスのリゾルバです。`[UseFiltering]`アノテーションがあることで、Hot Chocolateは自動的にフィルタリングのためのスキーマを生成します。そのため、GraphQLクエリ・リクエストで`where`引数を使って`id`で絞り込むことができます。
```c#:Query.cs
// 別途Importモデル・クラスを書いています（後述）
public class Query
{
    [UseFiltering]
    public IQueryable<Import> Imports([Service] AppDbContext context) => context.Imports;
}
```
```graphql:SDLの場合
# これはImportモデル・クラスに相当
type Import {
  id: ID!
  createDate: DateTime
  section: String
  tanto: String
  scm: String
  vessel: String
  voy: String
  # ... 他のフィールド
}

# こちらが関数宣言
type Query {
  imports: [Import!]! 
}
```
```bash:GraphQLリクエスト・テキスト
{
  "query": "query GetImportById($id: ID!) { imports(where: { id: { eq: $id } }) { id createDate section } }",
  "variables": { "id": "100" }
}
```


以下は`Mutation`クラスのリゾルバです。`BlNo`と`Section`を埋め込んだリクエストを受けて、新規で`Import`を登録します。
```c#:Mutation.cs
public class Mutation
{
    public async Task<Import> CreateItemAsync(ImportInput input, [Service] AppDbContext context)
    {
        var item = new Import
        {
            BlNo = input.BlNo,
            Section = input.Section
        };
        context.Imports.Add(item);
        await context.SaveChangesAsync();
        return item;
    }
}
```
```graphql:SDLの場合
input ImportInput {
  blNo: String
  section: String
  createDate: DateTime
  # ... 他のフィールド
}

# Queryの方に書いてあっても重複して書く必要があるらしい（Geminiくん談）
type Import {
  id: ID!
  blNo: String
  section: String
  createDate: DateTime
  # ... 他のフィールド
}

# 関数宣言
type Mutation {
  createItem(input: ImportInput!): Import!
}

```
```bash:GraphQLリクエスト・テキスト
{
  "query": "mutation CreateNewImport { createItem(input: { blNo: \"BL0123456789\", section: \"営業3部\" }) { id blNo section } }"
}
```


以下は、データソースとなるデータベース設定とGraphQLサーバーの立ち上げです。ここに認証ロジックも書きますがこの記事では割愛します。
```c#:Program.cs

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfiguration configuration = builder.Configuration;

// データベースの設定（SQL Server）
builder.Services.AddDbContext<AppDbContext>(options =>
    options
        .LogTo(message => Debug.WriteLine(message),
            new[] { DbLoggerCategory.Database.Name },
            LogLevel.Information,
            DbContextLoggerOptions.LocalTime)
        .UseSqlServer(configuration.GetConnectionString("DefaultConnection"))
);

builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()        // Queryクラスでスキーマの一部（シグネチャ）とリゾルバを登録
    .AddMutationType<Mutation>()  // Mutationクラスでスキーマの一部（シグネチャ）とリゾルバを登録
    .AddType<ImportType>()        // Importクラスをスキーマの一部として登録
    .AddFiltering();
 
WebApplication app = builder.Build();

app.MapGraphQL();
app.Run();

```
<details><summary>その他必要なクラス</summary>

```c#:AppDbContext.cs
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Import> Imports { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Import>().ToTable("Import");
     }
 }
```

```c#:Import.cs
// Importモデル
public class Import
{
    public int Id { get; set; }
    public DateTime? CreateDate { get; set; }
    public string? Section { get; set; }
    public string? Tanto { get; set; }
    public string? Scm { get; set; }
    public string? Vessel { get; set; }
    public string? Voy { get; set; }
    public int? SitateruCode { get; set; }
    public string? Shipper { get; set; }
    public string? BlNo { get; set; }
    public DateTime? LoadingDate { get; set; }
    public DateTime? ArrivalDate { get; set; }
    public string? LoadingPort { get; set; }
    public string? ArrivalPort { get; set; }
    public string? IraiNo { get; set; }
    public string? Agent { get; set; }
    public float Qty { get; set; }
    public float Weight { get; set; }
    public float Volume { get; set; }
    public string? ContainerSize { get; set; }
    public string? Freight { get; set; }
    public string? Hds { get; set; }
    public string? Lss { get; set; }
    public string? Cic { get; set; }
    public string? Afr { get; set; }
}
```

```c#:その他
// ミューテーションの引数として使用するDTO (Data Transfer Object) 
public class ImportInput
{
    public int Id { get; set; }
    public DateTime? CreateDate { get; set; }
    public string? Section { get; set; }
    public string? Tanto { get; set; }
    public string? Scm { get; set; }
    public string? Vessel { get; set; }
    public string? Voy { get; set; }
    public string? Shipper { get; set; }
    public string? BlNo { get; set; }
    public DateTime? LoadingDate { get; set; }
    public DateTime? ArrivalDate { get; set; }
    public string? LoadingPort { get; set; }
    public string? ArrivalPort { get; set; }
    public string? IraiNo { get; set; }
}

//　GraphQLのImport型を生成
public class ImportType : ObjectType<Import> { }
```
</details>

おわりに
--
GraphQLの参考書と言えばオライリーの『[初めてのGraphQL](https://www.oreilly.co.jp/books/9784873118932/)』があり、わたしも購入しましたが、半分しか読んでません。後半からのサーバー実装の説明についてはHot Chocolateのコードファーストを使う場合、必要無いからです:grin:（いや、それぐらい読んどけよ:sweat_smile:）

***C#の型システムを最大限に活用して、型安全なGraphQL APIを提供します***

Hot Chocolateによるコードファーストを高評価するGeminiくんのこの言葉通りだと思います:thumbsup:

:santa_tone2::christmas_tree::santa_tone2::christmas_tree::santa_tone2::christmas_tree::santa_tone2::christmas_tree::santa_tone2::christmas_tree:

https://chillicream.com/docs/hotchocolate/

https://github.com/ChilliCream/graphql-platform/tree/main/website/src/docs/hotchocolate