Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

ent + gqlgen で怠惰に GraphQL API を作る

この記事ははてなエンジニア Advent Calendar 2022 の 2023年1月4日の記事です。


Go 言語の ORM である ent をご存知でしょうか? ent はコード生成を用いて型安全に RDB のクエリを記述できる ORM です。エンティティ間の関係を Edge として定義するのが特色となっており、その結果、簡単にグラフ構造データのスキーマモデリングができます。

entgo.io

そんな ent には GraphQL インテグレーションという機能があります。これを使うと、ent のスキーマ定義を用いて簡単に RDB をデータストアとした GraphQL API サーバを作れるのでご紹介します。

ent GraphQL インテグレーションの仕組み

Go 言語で GraphQL API を開発するとき、最初に名前が上がるのが gqlgen というライブラリでしょう。GraphQL のスキーマファイルを食わせてやると、あとは resolver を手で書くだけの状態になった GraphQL API のコードが生成される、というものです。

gqlgen.com

ent によるコード生成時に SchemaGenerator というものを用いると、同時に GraphQL の スキーマファイル(データやクエリの型情報が入っている)を書き出すことができます。このファイルを gqlgen に食わせてやると、 resolver の雛形が生成されます。この未実装部分に ent で生成した CRUD API を利用して実装を埋め込んでいきます。すると、あっという間に GraphQL API の出来上がりです。

// Decks is the resolver for the decks field.
func (r *queryResolver) Decks(ctx context.Context) ([]*ent.Deck, error) {
    panic(fmt.Errorf("not implemented: Decks - decks"))
    // ここの panic を消して、代わりに ent で生成される ORM Client を使った実装を書く
    // return r.Client.Deck.Query().All(ctx)
}

雑多な説明になってしまいましたが、簡単にいうと以下のコーディングだけで GraphQL API が作れます。便利

  • ent のスキーマ定義ファイル
  • ent が吐き出した CRUD API を利用した Resolver の実装(関数を呼び出して return するだけ)

登場人物と関係図

以下のように generate.go を用意すると、この一連のコード生成を go generate . 一発で行えます。

//go:generate go run -mod=mod ./ent/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen

参考

詳しい説明は今回はしません。気が向いたら手順をエントリにまとめていこうと思います。

自分が参考にしたのは以下のドキュメントです。

entgo.io

entgo.io

感想

id:arthur-1 の個人開発プロジェクトに活用してみたので、使ってみた感想を述べていきます。

github.com

よかったこと

レールに従っているだけで、DB のスキーマ、ORM のクライアント、そして GraphQL API が簡単に作れるのはとてもよかったです。

GraphQL API が手軽にできてしまうと、フロントエンドの実装に集中することができます。もともとこのアーキテクチャが「面倒な実装は全部サーバサイドにやらせよう」という雰囲気のものですが、その面倒な部分を ent に丸投げできてしまいます。

さらに、できた GraphQL API がオモチャではないのも良いです。Cursor 形式のページネーションにも対応していたり、dataloader を自前で実装しなくても n+1 問題を自然と回避できたりするので、production として使う上で気になるパフォーマンス上の懸念もあまりなさそうです。

0 からサービスを作り上げたいときに GraphQL を活用したいと考えている場合、手早くスタートする方法としてピッタリではないかと思います。

よくなかったこと

これは ent の GraphQL インテグデーションというより ent 本体の問題なのですが、生成されるデータベースのテーブル定義が「マジかよ」という感じでした。

同じ型のエンティティ間の多対多の関係を定義した時に、その関係を保持する中間テーブルのカラム名が、本来想定されたものと入れ替わってしまうという不具合があります。

ent が生成する CRUD API を通してやりとりする場合には問題ありませんが、データベースに初期データを入れてテストしたいときなどに、カラム名が入れ替わっていることに気をつけなければなりません。

この問題は既知ですが、今だに解決されていません。

github.com

migration に atlas を使うと直るかも?

まとめ

GraphQL のエコシステムに乗っかっていくと、リクエストの数やサイズを減らしたり、決まった型がついた状態でデータを fetch できたりと、嬉しいことが多いと思います。一方で、自由に投げられるクエリを処理しなければならないため、サーバの実装はかなり難解なものになっています。

ent の GraphQL インテグレーションを利用すると、すでに定義したエンティティとその間の関係の情報を生かし、自然な形で GraphQL の API サーバを作ることができます。

ささっと GraphQL API を作り、その嬉しさを手早く体感しませんか?というお話でした。


prev: 2023年1月3日分は id:ikesyo さんの 『Production Ready GraphQL』の「Anemic GraphQL」 - いけだや技術ノート でした

next: 2023年1月5日分は id:hogashi さんの aタグで#topにリンクするとページ先頭にスクロールするのは仕様 - hogashi.* です