こんにちは、株式会社ラクスで先行技術検証を行っている技術推進課の@t_okkanです。 技術推進課では、新サービス立ち上げ時の開発速度アップを目的に、現在ラクスでは採用されていない新しい技術の検証を行う、技術推進プロジェクトがあります。 今回はその技術推進プロジェクトで、GraphQLについて検証を行いましたので、その結果の報告を行います。
なお、別テーマの取り組みや、過去の取り組みに関しては、こちらからご覧ください。
GraphQL
GraphQLとは、Facebookが2015年のReact.js Conf 2015で発表した、クライアントとサーバーの通信のための言語仕様になります。グラフ理論を用いて複雑なデータを階層構造にしてデータの取得を行います。元々は2012年にFacebookがネイティブモバイルアプリで使用するために開発しましたが、現在はOSSとなっておりGraphQL Foundationによってエコシステムの開発などが進められています。
GraphQLを導入している企業としては、GitHub、Twitter、Yelp、Shopify、Netflixなどがあげられます。 GraphQLの仕様は2017年まで頻繁にリリースされたいましたが、2018年以降はリリースがなく安定していると言えます。
実装言語や通信方式には縛りはありませんが、通信方式に関してはプロトコルはHTTP、HTTPメソッドはPOST、データ形式はJSONが推奨されています。
対応言語は公式ドキュメントで公開されています。
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を導入することでスキーマファーストな開発が可能になります。 開発初期にAPIの仕様からGraphQLのスキーマファイルを定義し、そのスキーマファイルを元にフロントエンドとバックエンドがそれぞれ実装します。 APIの仕様が実装前に明確となるため、フロントエンドとバックエンドの実装を並行に行え、かつ結合した際にエラーが起きづらくなることを期待できます。
検証してわかったこと
GraphQLの導入を検証しスキーマファーストな開発を行うことにより以下のようなことがわかりました。
フロントエンドとバックエンドの実装を切り離せ、お互いを疎結合にできる
従来の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あたりからです)
登壇者の方のブログでも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で公開しています。
GraphQLが向いているケース
GraphQLの導入が向いているケースとして、適切にドメインを分割できるアプリケーションが考えられます。 GraphQLは導入すれば複雑な仕様を解消するツールではありません。同じクエリを扱うSQLではデータベースの正規化で関係を整理するのと同様に、GraphQLにおいてもまずはアプリケーションの仕様を適切に分割できるかを検討してください。仕様を分割できる場合は、その分割の粒度に合わせてGraphQLの型を設計します。
例えば、ビジネスドメイン単位でGraphQLの型を設計することでバックエンドの実装をシンプルにできるのではないかと思います。 画面駆動からドメイン駆動のAPI開発になり、画面に合わせた実装がなくなるためController層やView層といったフロントエンドと会話する層の実装が楽になるかと思います。
ビジネスドメインをGraphQLの型に変換することで、「検証でわかったこと」欄に上げたようなGraphQLの特徴を活かしながら導入できると思います。 この条件を満たしていれば、後述する向いていないケースに該当しない限り、基本的にはどのWeb APIにも導入できるかと思います。
GraphQLが向いていないケース
GraphQLの導入が向いていないケースとしては、
が考えられます。
GraphQLのデータ形式はJSON形式が推奨されています。そのため周辺のエコシステムもJSON形式を前提として実装されています。ファイルアップロードの機能を提供しているライブラリもありますが、メディアファイルを扱う場合は、GraphQLとは別のAPIを提供すべきかと思います。
また管理型のシステムなど、データベースのテーブルを直接操作するようなアプリケーションにも向いていないのではと考えています。GraphQLを導入するためのオーバーヘッドが高いため、モノリスな構成かREST APIで構築すべきかと思います。ただPrismaやHasuraなどテーブルとGraphQLの型が1対1になるようなツールを導入する場合は、検討の余地があるかと思います。
GraphQLのエコシステム
GraphQLのエコシステムやその他のツールは公式のドキュメントでまとめられています。今回は検証で利用したツールを中心に紹介します。
GraphQLサーバー
今回はJavaとNodeでGraphQLサーバーを構築しました。それぞれで有効なライブラリを紹介します。
-
graphql-java
GraphQLの仕様をJavaで実装しているライブラリです。クライアントからのクエリのパーサーや、スキーマファイルとの検証を実装しています。JavaでWebフレームワークを利用しない場合はもっとも有効な選択肢となります。
graphql-java-spring
Spring BootとGraphQLを統合するためのライブラリです。内部でgraphql-javaを参照しているため、GraphQLの仕様はカバーできています。
@SpringBootTest
に相当する@GraphQLTest
を提供しています。Domain GraphQL Spring Boot Framework(DGS)
先述したNetflixがGraphQLを導入し、Apollo Federationを実現させた際に利用したフレームワークです。Spring BootとGraphQLを統合し、テストライブラリの提供やSpring Securityとの統合をサポートしています。Apollo Federationで必要な機能をサポートしているため、マイクロサービスとGraphQLの組み合わせを検討している場合は有効なライブラリであると考えられます。
Node
GraphQL.js
GraphQL Foundationが実装しているライブラリです。GraphQLの仕様を準拠しており信頼性は高いがApollo Serverに比べると低機能なため検証などで使用できると考えられます。
Apollo Server
Apollo社によって開発されているフレームワークです。NodeでGraphQLサーバーを構築する際のデファクト的存在となっています。dataloaderやサーバー側のキャッシュなど機能が豊富で、Apollo Federationを利用することでマイクロサービスアーキテクチャとの相性もよいです。
Express GraphQL
Expressで構築されているアプリケーションにミドルウェアとしてGraphQL APIを追加できます。
GitHub - graphql/express-graphql: Create a GraphQL HTTP server with Express.
Apollo Server Plugin
GraphQLクライアント
Apollo Client
Apollo社が開発しているライブラリです。React, Vue.js, Angular, iOS, Androidに対応しています。機能が充実しており、ドキュメントや2次情報も充実しています。基本的にはApollo Clientを利用するべきであると思います。
Relay
Facebookが開発しているフレームワークです。ReactとReact Nativeに対応しており、事前にクエリをコンパイルできるなどより安全にGraphQLのクライアントを構築できます。しかし、フレームワークであり学習コストが高いことや、React Hooksに未対応であることが懸念点として上げられます。
その他
その他の選択肢としてGraphQL Requestやurql、graphqurlなどがあります。JavaScriptフレームワークとの統合はサポートされていません。使用範囲が限られる場合は選択してもよいと思います。
またプロトコルにHTTP、HTTPメソッドにPOST、データ形式にJSONを指定すれば、クライアントツールを利用せずにクエリをリクエストできます。ただしその場合はクエリの解析や、レスポンスのマッピングなどの機能を独自で実装する必要があります。
その他エコシステム
GraphiQL・GraphQL Playground
GraphQL APIのクライアントツールです。クエリの実行や、スキーマファイルからドキュメントを自動生成することもできます。基本的にGraphQLサーバーのライブラリには開発環境用として内包されています。
GraphQL Code Generator
スキーマファイルから自動で型定義ファイルを生成するツールです。TypeScriptやJavaなどの静的型付け言語に対応しています。CLIも提供しており、ローカル環境で自動生成することもできます。GraphQLサーバーとクライアントのどちらの型定義ファイルを生成できます。
GraphQL Multipart Request
multipart/form-dataを利用したGraphQLでファイルアップロードを行う場合の仕様を定めています。この仕様を各GraphQLサーバーとクライアントのライブラリが実装をしています。GraphQLでファイルアップロードを行う場合はまずこの方法を選択すべきと思います。またライブラリを選定する場合もこの仕様を実装しているかを判断軸にすべきだと思います。GraphQLでファイルアップロードを行う他の手段としては、別APIを用意して登録先の識別子をGraphQLで登録するか、ファイルデータをBase64でエンコードしてGraphQLで登録する方法があります。
dataloader
GraphQLでは型同士が接続している場合にN+1問題が発生しやすいです。その回避策としてdataloaderを活用する方法があります。dataloaderはFacebookが提供しているJavaScriptのライブラリで、このライブラリを基に各プログラミング言語で移植されています。一意のIDを集合型でdataloaderに集約し、その集合体を利用して登録されている処理によって一括でデータを取得する仕組みになっています。そのためバッチ処理とも言われています。N+1問題以外でも利用されています。
また、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で開発することを検討されている方は是非参考にしてください。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.comラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com