FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

GANMA!にGraphQLを導入した話

こんにちは。2022年の1月から株式会社FLINTERSに入社した平野です。

この度GANMA!にGraphQLを導入したので、その経緯などについてまとめようと思います。

GraphQLとは

GraphQLはデータに対する問い合わせ言語です。GraphQL自体はクエリ言語とそのクエリを解釈&実行する仕様を定めているだけです。なので、主にWeb APIで使われますが使おうと思えばWEB API以外のアプリでも使えます。

REST APIと違ってスキーマ定義が必須なのでクライアントがどのようなクエリを実行できるのかが理解しやすいです。

また、GraphQLを使うことでクライアントは必要なデータだけを問い合わせて取得できるようになります。

たとえば以下のようなクエリを実行すると

クエリ

{
    hero {
        name
    }
}       

以下のようにクエリで指定したnameのデータだけが結果として返ってきます。

結果

{
    "hero": {
        "name": "Luke Skywalker"
    }
}

次に以下のようにクエリにheightフィールドを増やすと

クエリ

{
    hero {
        name
        height
    }
}       

nameのデータに合わせてheightのデータも返ってきます。

結果

{
    "hero": {
        "name": "Luke Skywalker",
        "height": 1.22
    }
}

さらに、GraphQLを使えば複数のリソース取得が1回のリクエストで行えるようになります。GraphQLではフィールドのデータはリゾルバと呼ばれる関数やメソッドの実行で取得します。サーバーの実装者はこのリゾルバの実装とフィールドとリゾルバの対応関係をコードとして書けばよく、クエリに含まれるフィールドとリゾルバの対応付け、リゾルバの実行、およびデータのつめ込みはGraphQLエンジン(GANMA!ではcalibanを使用)が行ってくれます。

例えばGraphQLで以下のクエリを実行すると以下のような結果が返ってきます。

クエリ

{
    hero {
    name {
    friends {
        name
        }
    }
}   

結果

{
    "hero": {
        "name": "Luke Skywalker",
        "friends": {
            { "name": "Obi-Wan Kenobi" },
            { "name": "R2-D2" },
            { "name": "Han Solo" },
            { "name": "Leia Organa" }
        }
    }
}

このような結果をREST APIエンドポイントで取得しようとするとまず GET /hero/:id のようなエンドポイントにアクセスしてheroのデータを取得し、その後 GET /friends?heroId=:id のようなエンドポイントにアクセスしてfriedsのデータを取得することになるでしょう。もしくは、エンドポイントをまとめて1つにしheroとfriendsのデータをまとめて返すという方法も考えられます。ただその場合、データをまとめる部分やデータストアからのデータの取得クエリ(SQLクエリなど)が複雑になるという問題が発生します。

GANMA!の課題

GANMA!のWeb APIではこのようなSQLが複雑になる部分が多くありました。例えばホーム画面では表示しなければならないデータが多岐にわたり参照するDBのテーブルの数が増えjoinを多様したSQLが発行されていました。このようなクエリは以下のような課題がありました。

  • 読みにくい
  • テストしづらい
  • 再利用性が低い

GraphQLによる解決

そこでGraphQLを使うことによりデータストアへのアクセスメソッドをリゾルバに分けました。そのリゾルバでは基本的に1テーブルにアクセスするようなシンプルなSQLを発行するだけにしました*1。このようにして1つ1つのリゾルバを読みやすく、テストしやすく、再利用可能なようにしました。GANMA!では漫画の情報を表示するページやセクションが多いので MagazineResolver (MagazineはGANMA!で配信している漫画を表す)が様々なページやセクションで使い回されています。

また必要なデータをクライアント側で選択できるというメリットも得られました。 以下の型はGANMA!で使われているMagazine型(一部フィールドを省略)になります。

type Magazine implements Node {
  id: ID!
  title: String!
  author: Author
  "G!TOONか?"
  isGTOON: Boolean!
  "完結作か?"
  isFinished: Boolean!
}

このうち isGTOONisFinished などのフィールドはページやセクションによって必要かどうか変わってくるので必要なければクエリ中から消すことができます。

まとめ

GraphQLをGANMA!に導入することで以下のようなメリットが得られました。

  • サーバーで発行するSQLクエリがシンプルになる
  • レスポンスフィールドが増減するような微妙な違いのエンドポイントを再実装する必要がなくなる
  • クライアントが必要なデータを選択できる

このように関係するリソースを1度に取得したいような場合に、GraphQLは向いていると思いますのでそのようなケースがあればぜひ導入を検討してみると良いかと思います!

*1:joinを使わずSQLを分けるとN+1問題が発生しやすくなりますがGANMA!ではZQueryを使用してN+1問題に対処しています