graphql-java を Spring Boot などで使う場合、簡単に使えるようにする場合 graphql-java 以外にも色々なライブラリを読み込む必要があります。
これは graphql-java はプリミティブな API しか持っておらず、web 側との繋ぎとか、subscription の対応をそれ単体ではできないためです。
この辺りの関係性が自分の中でも結構曖昧だったり、ドキュメントの情報しか読んでなかったので、コードリーディングしてわかったことをこの記事にメモします。
前提
Spring Boot + Kotlin で graphql-java-tools を使う場合で考えます。この辺りについては会社のブログの方でも書いたので、こちらもよければご参考にしてください。
以下のライブラリを使います。
- graphql-java
- graphql-java-servlet
- graphql-java-tools
- graphql-spring-boot-starter
構成
大雑把に言うと、graphql-java が GraphQL としてのコア機能を提供し、graphql-java-tools がコア機能を graphql-java のプリミティブな API を呼び出さずに済むように便利にします。
graphql-java-servlet は servlet api を用いて graphql-java をウェブ上で使えるようにし、
graphql-spring-boot-server が graphql-java-servlet を Spring Boot でのやり方に合わせるようにしてくれ DI での開発をやりやすくしてくれます。
正直色々あって混乱します。私もそれまでは Rails 使いだったこともあり、初見では、なんでこのライブラリが必要なのか?これを外したらどうなるのか?など全くわからなかったですし、今も分かっていないことが多いです。
なので間違いがありましたらご指摘いただけたら幸いです。
コードから見た関係性
話がそれますが、コードリーディングする際、各種リポジトリを落としてきて読んだのと、実際にサンプルのアプリを作って動かしてデバッグ実行しての合わせ技で理解していきました。
その際にはこちらのサンプルアプリで動作確認しました。ライブラリのバージョンは古いですが、大枠は変わっていないだろうと考えてアップデートしていません(変わってたら泣きたい)。
依存関係
- graphql-java
- 大元のコア機能
- graphql-java-servlet
- graphql-java に依存
- graphql-java-tools
- graphql-java に依存
- graphql-spring-boot-starter
- 同リポジトリの graphql-spring-boot-autoconfigure を経由して、graphql-java, graphql-java-tools, graphql-java-servlet に依存
graphql-java と graphql-java-servlet の関係
graphql-java は単体で HTTP で受けるインターフェースを持っていません。そのため HTTP を通して GraphQL 機能を作るためのライブラリが graphql-java-servlet になります。
servlet と名がつく通り、AbstractGraphQLHttpServlet を通して HttpServlet を継承した、GraphQLHttpServlet がライブラリ中で定義されています。
AbstractGraphQLHttpServlet にて doGet, doPost をオーバーライドしており、HTTP での受け答えができるようにしており、
さらに GraphQLQueryInvoker クラスを通して graphql-java の API を叩き GraphQL の処理を行った結果を返しているようです。
graphql-java と graphql-java-tools の関係
graphql-java で使う GraphQLSchema
を生成する部分の手続きを、graphql-java-tools では GraphQL の各型のフィールド用 Resolver 関数が定義されたクラスと、GraphQL の各型を表すデータクラスを定義することで簡便に行うための仕組みを提供している。graphql-java の schema 定義のドキュメントを見ると確かに結構面倒そうで、他の言語で提供されている仕組みと比べるともっと簡易にできる方法があるだろうとは確かに感じる。それもあって graphql-java-tools が便利なところを補ってくれている。
SchemaParserBuilder
を使いスキーマを定義したファイル、GraphQLResolver
を継承した Resolver クラスを渡し SchemaParser
クラス(graphql-java にも同名のクラスがあるが異なるので注意)を生成する。スキーマ定義のパースには graphql-java の Parser
クラスを使い(そのためここは graphql-java と共通の処理)、parseDocument
で Definition
を取得し内部的に使う。
SchemaParser
クラスの makeExecutableSchema
を呼び出すことで GraphQLSchema
が生成される。makeExecutableSchema
で呼び出されている toSchema
メソッドは、SchemaObjects
のメソッドで実装を見るとわかるように、graphql-java の GraphQLSchema
用のビルダーを使って GraphQLSchema
を生成している。toSchema
に必要なパラメータは、SchemaObjects
のコンストラクタへ渡されている各 query/mutation/subscription 用の GraphQLObjectType
であるので、ここへ渡される前に、スキーマや各 Resolver の情報から GraphQLObjectType
への変換が済んだ状態になっている。
GraphQLObjectType
への変換は SchemaParser
クラスの parseSchemaObjects
メソッドから呼び出される、createObject
メソッドで行われている。createObject
メソッドでは、GraphQLObjectType
ビルダーを使って GraphQL の型に対応するオブジェクトを定義し GraphQLObjectType
を生成する。この時オブジェクトのフィールドについても定義を行い、fieldResolversByType
(後述) を使ってフィールド用の DataFetcher
を設定する。
fieldResolversByType
は ObjectTypeDefinition
がキーで、MutableMap<FieldDefinition, FieldResolver>
をバリューとしてもつ Map
で、SchemaParser
クラスのコンストラクタで渡される ScannedSchemaObjects
から取得している。ScannedSchemaObjects
は SchemaParserBuilder
から渡されるクラスで、SchemaClassScanner
が scanForClasses
メソッドで生成する。
各 Resolver の動きに注目すると、SchemaClassScanner
にてまず ResolverInfo
を継承した各クラスに Resolver の情報を持たせる。
その上で scanResolverInfoForPotentialMatches
にて、オブジェクトのフィールドにマッチする FieldResolver
を ResolverInfo
を渡して生成し、fieldResolversByType
へオブジェクトの型( ObjectTypeDefinition
)をキーにして、FieldDefinition
とともにセットする。FieldResolver
を継承した各クラスは DataFetcher
を生成するための createDataFetcher
メソッドを持っているため、これで各 Resolver をフィールドにマッピングすることができる。FieldResolver
は MethodFieldResolver
, PropertyFieldResolver
, PropertyMapResolver
と定義されており、適切なクラスがマッピングされる。
ちょっと理解がふわふわしているところもあるが、大まかな動きはこんな感じであろうと思う。
graphql-spring-boot-starter と各ライブラリの関係
上記 graphql-java, graphql-java-servlet, graphql-java-tools を組み合わせたものを、Spring Boot っぽく DI の仕組みで使えるようにしたライブラリです。
graphql-spring-boot-starter の実体はほぼ同一リポジトリにある graphql-spring-boot-autoconfigure なので、このコードをもとに説明します。
graphql-spring-boot-autoconfigure には、Spring Framework の @Configuration アノテーションがついたクラスが存在していて、XxxAutoConfiguration という postfix がついています。WebSocket 向けの設定などもありますが、この記事では GraphQLJavaToolsAutoConfiguration
と GraphQLWebAutoConfiguration
について見ていきます。
GraphQLJavaToolsAutoConfiguration
はその名の通り graphql-java-tools 向けの設定で、graphql-java の GraphQLSchema
を Bean
として作るための一連の設定となっています。GraphQLSchema
を作るための SchemaParser
に渡す Resolver は List<GraphQLResolver<?>> resolvers
として Constructor Injection で受け取るようにしており、使う側としては GraphQLResolver
の各種 IF を定義したクラスを作るだけで、アプリ起動後に GraphQL の機能が使えるようになります。graphql-java-tools もこれがあるおかげで、その真価を発揮できているように感じます。
GraphQLWebAutoConfiguration
は graphql-java-servlet 用の一連の設定となっており、ServletRegistrationBean<AbstractGraphQLHttpServlet>
を @Bean
として定義することで、GraphQLHttpServlet
(デフォルトでは)を Servlet として登録しています。ServletRegistrationBean
についてはこちら。GraphQLHttpServlet の設定として渡される GraphQLSchema
は GraphQLJavaToolsAutoConfiguration
で定義されたものが使用されるようになっており、これによって GraphQL のスキーマ定義から実際に HTTP 上で動作する GraphQL の機能が使えるようになります。
ServletRegistrationBean
以外の @Bean
には @ConditionalOnMissingBean
や @Autowired(required = false)
が付いているため、デフォルト挙動は決めつつ、必要なところは上書きするってことができるようになっています。
GraphQLJavaToolsAutoConfiguration
もそうですが上手く Spring の機能を上手く使っているなと感じます。なんか Rails っぽいですよね?(筆者は Rubyist で Rails が比較的得意です)。
まとめ
今までドキュメントでだけ理解していた箇所が、コードリーディングで関係を明らかにしたことによって中身の理解がかなり深まりました。
他の言語だと graphql-java-tools 相当のライブラリにいきなり辿り着けるので、比較的理解もしやすいのですが、graphql-java はもうちょっと間に挟まってしまう感じがあって自由度は高いもののちょっとハードルがあるかなと感じました。
もともと Ruby, Elixir が好きなのもあるとは思うものの、graphql-ruby とか Elixir の Absinthe とかはかなりイメージがつかみやすいです(graphql-ruby と Absinthe はコードファーストなライブラリというのもありますが)。
ただこれからの流れとして Java などで GraphQL をやるなら graphql-java をそのまま使うのではなく graphql-java-tools を使うか、同じように schema 定義を楽にしてくれるライブラリとセットで使うのが普通になっていくのではないかと思います。
それと今回調査をするにあたって graphql-java 関連の情報があまりなく、graphql-java ほとんど使われていないんじゃ疑惑が拡大しています。
graphql-java 使っている方たちがいましたらアウトプットに期待しています。
参考
GraphQL Java 関連
- Building a GraphQL Server with Java Backend Tutorial https://www.howtographql.com/graphql-java/0-introduction/
- ちょっと古いけどサラッと概要を把握できます
- GraphQL Java https://www.graphql-java.com/documentation/
- 公式のドキュメント。ここ最近(12/06現在)新しくなった。
- GraphQL Java Kickstart https://www.graphql-java-kickstart.com/
- graphql-java-servlet, graphql-java-tools, graphql-spring-boot-starter の公式ドキュメント。こちらも graphql-java-kickstart organization ができて新しくなったかも。
- graphql-java/graphql-java: GraphQL Java implementation https://github.com/graphql-java/graphql-java
- graphql-java-kickstart/graphql-java-servlet: Servlet endpoint for GraphQL Java https://github.com/graphql-java-kickstart/graphql-java-servlet
- graphql-java-kickstart/graphql-java-tools: A schema-first tool for graphql-java inspired by graphql-tools for JS https://github.com/graphql-java-kickstart/graphql-java-tools
- graphql-java-kickstart/graphql-spring-boot: GraphQL and GraphiQL Spring Framework Boot Starters - Forked from oembedler/graphql-spring-boot due to inactivity. https://github.com/graphql-java-kickstart/graphql-spring-boot
Spring 周りについて
- Spring FrameworkとDIについて - mookjp.io https://blog.mookjp.io/blog-ja/spring-framework%E3%81%A8di%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/
- 定期的に mook さんの資料見直している気がする。感謝
関連記事、サンプル実装、他言語での情報とか
- フロントエンド向けの API サーバリニューアルに GraphQL を検討している話 - エムスリーテックブログ https://www.m3tech.blog/entry/graphql-on-spring-boot-with-kotlin
- m3dev/graphql-spring-boot-kotlin-sample https://github.com/m3dev/graphql-spring-boot-kotlin-sample
- rmosolgo/graphql-ruby: Ruby implementation of GraphQL https://github.com/rmosolgo/graphql-ruby
- Elixir で Absinthe を使って GraphQL に触れてみた - Qiita https://qiita.com/ma2ge/items/52dcebd54c5bdecafcb6