お知らせ フロントエンド バックエンド インフラ 品質保証 セキュリティ 製品 興味・関心 その他

2023.06.30

バックエンド

GraphQLをさくっと実装してみた

GraphQLとは、APIと通信を行うための比較的新しい通信規格です。
よく知られているRESTとはまた違ったアプローチによって、APIからデータを取得することができます。
本記事では、GraphQLの特徴やメリット、簡単なバックエンド実装例を紹介できればと思います。

GraphQLの特徴

GraphQLにはRESTとは異なり、以下の特徴があります。

  • 1度のリクエストで、必要なデータのみ取得することができる
  • リクエストのエンドポイントは1つのみ
  • メソッドはGET/POST/DELETE etc. ではなく、query/mutation/subscriptionの3種類

GraphQLの長所

GraphQLにはRESTに比べ、以下の点が優れていると言われています。

  • 必要な情報のみをより短時間で柔軟に取得できる
  • エンドポイントの管理が楽になる
  • マイクロサービスやスキーマ駆動開発と相性がいい

グラフ型データとは

グラフ型データとは、グラフ理論に基づいた構造で管理されたデータのことを指します。
データとデータ間の関係性や関係の向きに着目することで、1つのデータに関係したデータをたどって他のデータを検索することができます。
また、NoSQLDBの一つとしてグラフ型DBというものがあり、データとその関係性を保存して膨大なネットワークを形成することで、高速なデータ検索を可能にします。

GraphQLはデータの関係性に着目して検索を行うことで、無駄な情報を省いた検索が可能となります。
本記事は、ライブラリを用いることでRDBのデータをグラフ型データのように扱い、そのデータに対してGraphQLにて問い合わせを行う方法を紹介します。

GraphQLのお試し実装

実際に、簡単なGraphQLのAPIを実装してみましょう。
今回は、以下の技術スタックを利用しました。
言語:Golang
ライブラリ:ent. / gqlgen
DB:PostgreSQL

ent.とはMeta(Facebook)によって開発されたグラフ型データモデル用ライブラリです。
つまり、RDBのデータをグラフ型構造にマッピングし、GraphQLによってDB操作を可能にするORMです。
gqlgenはリクエストを受け取り、DB操作の結果をレスポンスとして返す部分(リゾルバ)を自動で構築してくれるライブラリです。

ent.には定義したデータモデルからスキーマを生成する機能を持っています。
そして、gqlgenはスキーマからリゾルバを自動生成するライブラリです。
つまり、この2つを組み合わせることで、クエリ受け取り→DB操作→レスポンス返却の流れを自動的に実装することができます。

ent.によるデータモデリング

簡単なSNSアプリケーションを想定し、データテーブルをPostgresDBに用意しました。

このテーブルのうち、Accountテーブルのデータモデリングをお見せします。
ent.をインストール後、プロジェクトルートにて次のコマンドを入力します。

go run -mod=mod entgo.io/ent/cmd/ent init Accounts

すると、ent/schema/Accounts.goというファイルが作成されるので、このファイルの内容を変更していきます。
まずは、テーブルとフィールドのモデリングを行います。

// Annotationsにより、紐づけるテーブルや自動生成するリゾルバを定義する
func (Account) Annotations() []schema.Annotation {
	return []schema.Annotation{
		entsql.Annotation{Table: "Accounts"},
		entgql.QueryField(),
		entgql.Mutations(entgql.MutationCreate(), entgql.MutationUpdate()),
	}
}
// Accountsテーブルの各フィールドのデータ型や制限などを定義
func (Account) Fields() []ent.Field {
	return []ent.Field{
		field.String("email").
			MaxLen(256).
			NotEmpty().
			Unique(),
		field.String("password").
			Sensitive(),
		field.Enum("type").
			Values(
				"ACTIVE",
				"INACTIVE",
				"ADMIN",
			),
		field.String("name"),
		field.Int("age").
			Positive(),
		field.Enum("gender").
			Values(
				"MALE",
				"FEMALE",
				"NONE",
			),
		field.String("avatar"),
		field.String("introduction"),
	}
}

ここまではデータテーブルの定義と見比べて想像がしやすく、手軽に実装が可能と思われます。
次は「エッジ」と呼ばれる、データ同士の関係性、関係の向きを定義していきます。
ここではAccountsテーブルと同じようにPostテーブルのデータモデリングを行い、
2つの設定ファイル内でエッジを定義する様子をお見せします。

まずは、Account.goファイル内にて、アカウントと投稿の関係性を定義します。

次に、Post.goファイル内にて、反対向きのエッジを定義します。

ここまでの流れを全テーブルに対して行うことで、グラフ型データモデリングを行います。

gqlgenによるリゾルバの実装

ent.から生成したスキーマをgqlgenに渡してリゾルバを自動生成するには、gqlgenによる自動生成前に簡単なコードを仕込みます。
ent/entc.goというファイルを作成し、次のように記述します。

// gqlgenに渡すスキーマの場所や、gqlgenのconfigファイルの場所、
// リゾルバを生成する場所などを指定する
func main() {
	ex, err := entgql.NewExtension(
		entgql.WithConfigPath("./gqlgen.yml"),
		entgql.WithSchemaGenerator(),
		entgql.WithSchemaPath("./schemas/ent.graphql"),
		entgql.WithWhereFilters(true),
	)
	if err != nil {
		log.Fatalf("creating entgql extension: %v", err)
	}
	opts := []entc.Option{entc.Extensions(ex)}
	if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

これで、gqlgenとent.の連携の準備が整いましたので、gqlgenによってリゾルバを自動生成しましょう。

リゾルバ内部処理の実装

リゾルバを自動生成すると、生成されたファイル内に以下のような関数が作られています。

func (r *queryResolver) Posts(ctx context.Context) ([]*ent.Post, error) {
	panic(fmt.Errorf("not implemented"))
}

これは、DBのPostテーブルに対して検索をかけるための関数ですが、まだその機能が実装されていません。次のようにコードを変更します。

func (r *queryResolver) Posts(ctx context.Context) ([]*ent.Post, error) {
	return r.client.Post.Query().All(ctx)
}

このように書くだけで、PostやPostと関係性を持つデータを必要な分だけ取得できる関数が実装可能です。

GraphQLによる検索

実際に、Postと関連するデータを取得してみましょう。
次のようなクエリをリクエストボディに入れてサーバーにリクエストを飛ばしてみましょう。

query Posts {
    posts {    // 全Postを検索
        id     // Postのid
        title  // Postのタイトル
        body   // Postの本文
        account {  // Postを投稿したアカウント
            name   // 投稿したアカウントの名前
        }
        comments {     // Postへのコメント
            account {  // コメントしたアカウント
                name   // コメントしたアカウントの名前
            }
            body       // コメントの内容
        }
    }
}

このようなクエリを送信した際、DBのデータから指定した分だけデータが返ってきます。

[
  {
    "id": "post_no_1",
    "title": "Post_title_test",
    "body": "Post_body_test",
    "account": {
      "name": "AAA 太郎"
    },
    "comments": [
      {
        "account": {
          "name": "BBB 二郎"
        },
        "body": "Comment_body_test"
      },
      {
        "account": {
          "name": "CCC 三郎"
        },
        "body": "Comment_body_test"
      }
    ]
  },
  {
    "id": "post_no_2",
    "title": "Post_title_test",
    "body": "Post_body_test",
    "account": {
      "name": "DDD 四郎"
    },
    "comments": [
      {
        "account": {
          "name": "EEE 五郎"
        },
        "body": "Comment_body_test"
      }
    ]
  }
]

上の例では、DB内の全Postの取得のみならず、そのPostを投稿したアカウントの名前や、
Postへのコメントの内容やコメントしたアカウントの名前まで取得できました。
このように、GraphQLによって必要なデータだけを1度のリクエストで取得することが可能になります。

まとめと振り返り

今回は、グラフ型データモデリングとGraphQLによるDB問い合わせまで、簡単に実装してみました。
自身としても、「必要なデータだけを取ってくる」という無駄のないデータ取得が可能となるGraphQLは、以前から非常に興味深く感じておりましたため、簡単なアプリのGraphQL実装ができたことはとても楽しく感じられました。
また、ent.やgqlgenなど便利で素晴らしいライブラリに出会うことができ、非常に刺激的なコーディングができたと思いました。
本記事はやや駆け足気味な記載となりましたため、実装方法の紹介としては不十分な部分が多々あると思われますが、少しでもGraphQLに興味を持っていただければ幸いです。

カツシ

カツシ

記事一覧

マーケライズ開発チームのカツシです!
割とフルスタックに開発を行っていますが、特にバックエンド開発が楽しいです!
趣味は一人旅やロードバイク、温泉や水泳などです!