RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

GraphQLのアプリケーションへの組み込みを考える

こんにちは、株式会社ラクスで先行技術検証を行っている技術推進課の@t_okkanです。 技術推進課では、新サービス立ち上げ時の開発速度アップを目的に、現在ラクスでは採用されていない新しい技術の検証を行う、技術推進プロジェクトがあります。 今回はその技術推進プロジェクトで、GraphQLについて検証を行いましたので、その結果の報告を行います。

なお、別テーマの取り組みや、過去の取り組みに関しては、こちらからご覧ください。

tech-blog.rakus.co.jp

GraphQL

GraphQLとは、Facebookが2015年のReact.js Conf 2015で発表した、クライアントとサーバーの通信のための言語仕様になります。グラフ理論を用いて複雑なデータを階層構造にしてデータの取得を行います。元々は2012年にFacebookがネイティブモバイルアプリで使用するために開発しましたが、現在はOSSとなっておりGraphQL Foundationによってエコシステムの開発などが進められています。

graphql.org

GraphQLを導入している企業としては、GitHubTwitter、Yelp、Shopify、Netflixなどがあげられます。 GraphQLの仕様は2017年まで頻繁にリリースされたいましたが、2018年以降はリリースがなく安定していると言えます。

spec.graphql.org

実装言語や通信方式には縛りはありませんが、通信方式に関してはプロトコルはHTTP、HTTPメソッドはPOST、データ形式JSONが推奨されています。
対応言語は公式ドキュメントで公開されています。

graphql.org

GraphQLの言語仕様

クエリ言語

データの取得や更新の操作を指定する言語です。基本的にはクライアント側でクエリ言語を構築し、GraphQLサーバーにリクエストとして送信します。
基本的なオペレーションとして、Query、Mutation、Subscriptionが存在します。その他にもFragementやVariables、Directivesなどの重要な言語仕様がありますが、詳細は公式ドキュメントもしくはGraphQL Specで確認してください。

  • Query

    データの取得、読み込み系の処理を担当します。REST APIのGETに相当します。

  • Mutation

    データの書き込み、更新系の処理を担当します。REST APIのPOST、PUT、DELETEに相当します。

  • Subscription

    WebSocketを利用した双方向のリアルタイムデータ通信を行います。サーバー側からクライアント側にリアルタイムで通知を送信できます。

スキーマ言語

GraphQLサーバーのAPIの仕様を定義します。サーバー側にスキーマファイルを配置し、GraphQLの型システムとAPIの操作を定義します。
ここでは主な仕様のみ紹介しますので、その他の仕様に関しては公式ドキュメントGraphQL Specで確認してください。

  • Schema

    GraphQLサーバーで実行可能な操作(Query, Mutation, Subscription)を定義します。Schemaで宣言した操作がクライアントで実行できる操作となります。

  • Scalars

    GraphQL型システムのプリミティブ型を宣言します。GraphQL型システムには組み込み型として、String(UTF-8の文字列)、Int(符号付き32ビットの整数)、Float(符号付き不動小数点)、Boolean(true, falseの論理値)、ID(一意の識別子でStringと同じ文字列)が存在します。開発者が独自にCustom Scalar型を定義できます。

  • Types

    GraphQL型システムの型で開発者が独自で定義できます。型は固有のオブジェクトで対応する複数のフィールドを持つことができます。アプリケーションの特性を反映できます。

GraphQLのアーキテクチャ

  • GraphQLクライアント

    クエリ言語を構築し、データの取得やデータの更新操作を指定し、GraphQLサーバーにリクエストを送信します。

  • GraphQLサーバー

    GraphQLクライアントから送信されたクエリを解析し、クエリで指定されたオペレーションを実行しデータを返却します。Query Parser、Query Validation、Resolverで構築されています。

  • Query Parser

    GraphQLサーバーでクライアントからリクエストされたクエリ言語がGraphQLの仕様を満たしているか構文解析を行います。構文解析後にクエリ言語を機械語に変換します。基本的にはGraphQLサーバーのライブラリによって実装されています。

  • Query Validation

    Query ParserでGraphQLの構文解析をした後に、スキーマファイルで定義されたGraphQLサーバーの仕様とクエリ言語の検証を行います。クエリ言語で宣言されている各オペレーションとResolverのナビゲーションを行います。基本的にはGraphQLサーバーのライブラリによって実装されています。

  • Resolver

    スキーマファイルで定義した型と操作に基づいて、サーバー側にて各プログラミング言語で実装を行います。GraphQLサーバーのビジネスロジックやデータベースなどの永続化データ、またはバックエンドのAPIにアクセスし必要なデータを取得しレスポンスとして返却します。開発者が各プログラミング言語で実装します。

GraphQLのアーキテクチャ

スキーマファーストな開発

GraphQLを導入することでスキーマファーストな開発が可能になります。 開発初期にAPIの仕様からGraphQLのスキーマファイルを定義し、そのスキーマファイルを元にフロントエンドとバックエンドがそれぞれ実装します。 APIの仕様が実装前に明確となるため、フロントエンドとバックエンドの実装を並行に行え、かつ結合した際にエラーが起きづらくなることを期待できます。

検証してわかったこと

GraphQLの導入を検証しスキーマファーストな開発を行うことにより以下のようなことがわかりました。

  • フロントエンドとバックエンドの実装を切り離せ、お互いを疎結合にできる
  • 共通の定義ファイルを元に実装を行うため、APIの仕様が明確になり実装の手戻りの防止や開発速度の向上が見込める

フロントエンドとバックエンドの実装を切り離せ、お互いを疎結合にできる

従来のREST APIではSPAなどでフロントエンドとバックエンドを分離をしたい場合、開発の担当を分離することはできてもフロントエンドがバックエンドのAPIと直接通信していたため、実装を分離できずお互いのI/Fを確認しながら開発を行っていました。そのため、開発速度の低下やでき上がったAPIが想定とは異なる場合は手戻りが発生していました。 そこでGraphQLを導入することでスキーマファイルの存在により、バックエンドとフロントエンドの実装が隠蔽され、疎結合に分離できることがわかりました。

実装の分離

バックエンドはスキーマファイルで定義されているクエリと型に合わせたデータを提供し、フロントエンドはスキーマファイルにて定義されているクエリを実行し定義されているデータを取得することになります。お互い確認するのがスキーマファイルになり、それ以降の実装について関心がなくなるため実装を分離できます。実装を分離できることにより、バックエンドの実装やアーキテクチャの変更による影響を受けづらくなります。 また、お互いの実装が分離されることにより各開発チームの最適な開発手法を取り入れることができます。 バックエンドは画面を意識せずビジネスドメイン知識に基づいたAPIを構築でき、フロントエンドはAPIの仕様を意識せず画面で必要なデータのみを取得できます。

共通の定義ファイルを元に実装を行うため、APIの仕様が明確になり実装の手戻りの防止や開発速度の向上が見込める

GraphQLを導入することで、バックエンドはスキーマファイルに定義されているクエリと型を実装し、フロントエンドはスキーマファイルに定義されているクエリを実行して型の値を取得します。お互いスキーマファイルで定義されていない実装をすると、Query Validationでエラーが発生します。そのため、フロントエンド・バックエンドのどちらかが主導で仕様を変更できなくなります。仕様を変更する場合は、スキーマファイルを変更してからお互いの実装を変更する必要があります。 スキーマファイルがAPIの仕様を明確にするので、仕様の齟齬による手戻りを防ぎ、それぞれの実装に集中できるので開発速度の向上が期待できるかと思います。

その他わかったこと

上述のこと以外で検証を通じてわかったことは以下になります。

REST APIとGraphQLのAPIを1つのアプリケーションで運用できる

GraphQLの仕様を実装しているライブラリには、Spring BootやLaravel、Expressとと言ったFrameworkと統合するためのライブラリが存在します。そのため、既存のREST APIを一気にGraphQLに移行するのではなく、新機能や一部の機能のみ先行してGraphQLに移行できます。

DDDとGraphQLの相性が良い

先述したビジネスドメインをGraphQLの型システムに変換しやすいことから、DDDとGraphQLの相性が良いと考えられます。AWSのreInvent:2019のBuilding modern APIs with GraphQLのセッションでもDDDとGraphQLとの親和性について紹介されています。(35:39あたりからです)

www.youtube.com

登壇者の方のブログでもDDDとGraphQLの相性がよいと紹介されています。

GraphQL and DDD: the Missing Link | HackerNoon

マイクロサービスアーキテクチャとの相性が良い

NetflixではモノリスAPIからマイクロサービスへ移行する際にGraphQLを導入しました。GraphQLを導入後にさらにサービスが増加しGraphQLのResolverの実装が複雑化しボトルネックになっていたため、Apollo社が提唱しているApollo Federationを導入しました。Apollo Federationは複数のGraphQLサービスを1つのGraphQL Gatewayで集約しGraphQLサーバーとして提供する仕組みです。またその際に自社で開発したGraphQLをSpring Bootに統合するための実装を、Domain GraphQL Spring Boot FrameworkというフレームワークとしてOSSで公開しています。

Home - DGS Framework

GraphQLが向いているケース

GraphQLの導入が向いているケースとして、適切にドメインを分割できるアプリケーションが考えられます。 GraphQLは導入すれば複雑な仕様を解消するツールではありません。同じクエリを扱うSQLではデータベースの正規化で関係を整理するのと同様に、GraphQLにおいてもまずはアプリケーションの仕様を適切に分割できるかを検討してください。仕様を分割できる場合は、その分割の粒度に合わせてGraphQLの型を設計します。

例えば、ビジネスドメイン単位でGraphQLの型を設計することでバックエンドの実装をシンプルにできるのではないかと思います。 画面駆動からドメイン駆動のAPI開発になり、画面に合わせた実装がなくなるためController層やView層といったフロントエンドと会話する層の実装が楽になるかと思います。

ドメインとGraphQLの型

ビジネスドメインをGraphQLの型に変換することで、「検証でわかったこと」欄に上げたようなGraphQLの特徴を活かしながら導入できると思います。 この条件を満たしていれば、後述する向いていないケースに該当しない限り、基本的にはどのWeb APIにも導入できるかと思います。

GraphQLが向いていないケース

GraphQLの導入が向いていないケースとしては、

  • メディアファイルを扱うAPI
  • データベースのテーブルを直接操作するAPI

が考えられます。

GraphQLのデータ形式JSON形式が推奨されています。そのため周辺のエコシステムもJSON形式を前提として実装されています。ファイルアップロードの機能を提供しているライブラリもありますが、メディアファイルを扱う場合は、GraphQLとは別のAPIを提供すべきかと思います。

また管理型のシステムなど、データベースのテーブルを直接操作するようなアプリケーションにも向いていないのではと考えています。GraphQLを導入するためのオーバーヘッドが高いため、モノリスな構成かREST APIで構築すべきかと思います。ただPrismaHasuraなどテーブルとGraphQLの型が1対1になるようなツールを導入する場合は、検討の余地があるかと思います。

GraphQLのエコシステム

GraphQLのエコシステムやその他のツールは公式のドキュメントでまとめられています。今回は検証で利用したツールを中心に紹介します。

GraphQL Landscape

GraphQLサーバー

今回はJavaとNodeでGraphQLサーバーを構築しました。それぞれで有効なライブラリを紹介します。

GraphQLクライアント

またプロトコルにHTTP、HTTPメソッドにPOST、データ形式JSONを指定すれば、クライアントツールを利用せずにクエリをリクエストできます。ただしその場合はクエリの解析や、レスポンスのマッピングなどの機能を独自で実装する必要があります。

その他エコシステム

GraphiQL・GraphQL Playground

GraphQL APIのクライアントツールです。クエリの実行や、スキーマファイルからドキュメントを自動生成することもできます。基本的にGraphQLサーバーのライブラリには開発環境用として内包されています。

github.com

github.com

GraphQL Code Generator

スキーマファイルから自動で型定義ファイルを生成するツールです。TypeScriptやJavaなどの静的型付け言語に対応しています。CLIも提供しており、ローカル環境で自動生成することもできます。GraphQLサーバーとクライアントのどちらの型定義ファイルを生成できます。

Home – GraphQL Code Generator

GraphQL Multipart Request

multipart/form-dataを利用したGraphQLでファイルアップロードを行う場合の仕様を定めています。この仕様を各GraphQLサーバーとクライアントのライブラリが実装をしています。GraphQLでファイルアップロードを行う場合はまずこの方法を選択すべきと思います。またライブラリを選定する場合もこの仕様を実装しているかを判断軸にすべきだと思います。GraphQLでファイルアップロードを行う他の手段としては、別APIを用意して登録先の識別子をGraphQLで登録するか、ファイルデータをBase64エンコードしてGraphQLで登録する方法があります。

github.com

dataloader

GraphQLでは型同士が接続している場合にN+1問題が発生しやすいです。その回避策としてdataloaderを活用する方法があります。dataloaderはFacebookが提供しているJavaScriptのライブラリで、このライブラリを基に各プログラミング言語で移植されています。一意のIDを集合型でdataloaderに集約し、その集合体を利用して登録されている処理によって一括でデータを取得する仕組みになっています。そのためバッチ処理とも言われています。N+1問題以外でも利用されています。

github.com

また、dataloaderを利用することでバックエンドの負荷が高くなることもあるので、N+1問題を受け入れる選択もあるそうです。Apollo Serverではサーバー側のキャッシュを利用する方法を推奨しています。

Data sources - Apollo GraphQL Docs

GraphQL Depth Limit

GraphQLのアンチパターンに循環参照があります。GraphQLの方がお互いに結合している場合に発生し、多段にデータが紐づいてしまいバックエンドの負荷が高くなります。対策としてクエリの深さを制限する方法があります。 graphql-depth-limitというライブラリや、各GraphQLサーバーのライブラリでも提供されています。

Apollo Studio

GraphQLは単一のエンドポイントでAPIを提供するため、エンドポイント単位でのメトリクスの計測ができません。Apollo Studioを利用すると、クエリごとに実行数や処理時間を計測でき、バックエンドのボトルネックを発見できます。しかし、Apollo Serverか対応しているライブラリでGraphQLサーバーを構築する必要があります。

Introduction to Apollo Studio - Apollo GraphQL Docs

Apollo Studio以外でメトリクスを計測する方法として、マイクロサービスで利用されているOpen Tracingを利用する方法もあります。

まとめ

GraphQLの検証で得た知見のまとめは以下のようになります。

  • GraphQLを導入することで、フロントエンドとバックエンドでお互いの実装が切り離せ疎結合にできる
  • 共通の定義ファイルを元に実装を行うため、APIの仕様が明確になり開発速度の向上が期待できる
  • GraphQLを導入する前にアプリケーションの仕様を分割できるかを検討する必要がある
  • DDDやマイクロサービスアーキテクチャとの相性が良い
  • GraphQLとREST APIは同じアプリケーションで共存できるため、段階的に移行することができる

検証のご紹介は以上になります。GraphQLの導入事例が徐々に増えてきており、エコシステムの強化も進んでいる印象です。 既存のREST APIをGraphQLに移行する場合や、新規のAPIをGraphQLで開発することを検討されている方は是非参考にしてください。


◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.