コードで見る graphql-java 関連ライブラリの関係性

graphql-java を Spring Boot などで使う場合、簡単に使えるようにする場合 graphql-java 以外にも色々なライブラリを読み込む必要があります。
これは graphql-java はプリミティブな API しか持っておらず、web 側との繋ぎとか、subscription の対応をそれ単体ではできないためです。
この辺りの関係性が自分の中でも結構曖昧だったり、ドキュメントの情報しか読んでなかったので、コードリーディングしてわかったことをこの記事にメモします。

## 前提

Spring Boot + Kotlin で graphql-java-tools を使う場合で考えます。この辺りについては会社のブログの方でも書いたので、こちらもよければご参考にしてください。

以下のライブラリを使います。

- graphql-java
  - https://github.com/graphql-java/graphql-java
- graphql-java-servlet
  - https://github.com/graphql-java-kickstart/graphql-java-servlet
- graphql-java-tools
  - https://github.com/graphql-java-kickstart/graphql-java-tools
- graphql-spring-boot-starter
  - https://github.com/graphql-java-kickstart/graphql-spring-boot


## 構成

大雑把に言うと、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 使いだったこともあり、初見では、なんでこのライブラリが必要なのか?これを外したらどうなるのか?など全くわからなかったですし、今も分かっていないことが多いです。
なので間違いがありましたらご指摘いただけたら幸いです。


## コードから見た関係性

話がそれますが、コードリーディングする際、各種リポジトリを落としてきて読んだのと、実際にサンプルのアプリを作って動かしてデバッグ実行しての合わせ技で理解していきました。
その際にはこちらの[サンプルアプリ](https://github.com/m3dev/graphql-spring-boot-kotlin-sample)で動作確認しました。ライブラリのバージョンは古いですが、大枠は変わっていないだろうと考えてアップデートしていません(変わってたら泣きたい)。

### 依存関係

- 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](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/v7.1.1/src/main/java/graphql/servlet/GraphQLHttpServlet.java#L12) がライブラリ中で定義されています。
AbstractGraphQLHttpServlet にて [doGet](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/v7.1.1/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java#L321), [doPost](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/v7.1.1/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java#L326) をオーバーライドしており、HTTP での受け答えができるようにしており、
さらに [GraphQLQueryInvoker](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/v7.1.1/src/main/java/graphql/servlet/GraphQLQueryInvoker.java#L95) クラスを通して graphql-java の API を叩き GraphQL の処理を行った結果を返しているようです。
                                                                    
                                                                    
### graphql-java と graphql-java-tools の関係                       
                                                                    
graphql-java で使う `GraphQLSchema` を生成する部分の手続きを、graphql-java-tools では GraphQL の各型のフィールド用 Resolver 関数が定義されたクラスと、GraphQL の各型を表すデータクラスを定義することで簡便に行うための仕組みを提供している。graphql-java の [schema 定義のドキュメント](https://www.graphql-java.com/documentation/master/schema/)を見ると確かに結構面倒そうで、他の言語で提供されている仕組みと比べるともっと簡易にできる方法があるだろうとは確かに感じる。それもあって graphql-java-tools が便利なところを補ってくれている。

[`SchemaParserBuilder`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParserBuilder.kt) を使いスキーマを定義したファイル、[`GraphQLResolver`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/GraphQLResolver.java) を継承した Resolver クラスを渡し [`SchemaParser`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt) クラス(graphql-java にも同名のクラスがあるが異なるので注意)を生成する。スキーマ定義のパースには graphql-java の `Parser` クラスを使い(そのためここは graphql-java と共通の処理)、[`parseDocument`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParserBuilder.kt#L186-L189) で `Definition` を取得し内部的に使う。

`SchemaParser` クラスの [`makeExecutableSchema`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt#L130) を呼び出すことで `GraphQLSchema` が生成される。`makeExecutableSchema` で呼び出されている `toSchema` メソッドは、[`SchemaObjects`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaObjects.kt#L17-L27) のメソッドで実装を見るとわかるように、graphql-java の `GraphQLSchema` 用のビルダーを使って `GraphQLSchema` を生成している。`toSchema` に必要なパラメータは、[`SchemaObjects`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaObjects.kt#L11) のコンストラクタへ渡されている各 query/mutation/subscription 用の `GraphQLObjectType` であるので、ここへ渡される前に、スキーマや各 Resolver の情報から `GraphQLObjectType` への変換が済んだ状態になっている。

`GraphQLObjectType` への変換は `SchemaParser` クラスの [`parseSchemaObjects`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt#L99) メソッドから呼び出される、[`createObject`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt#L137) メソッドで行われている。`createObject` メソッドでは、`GraphQLObjectType` ビルダーを使って GraphQL の型に対応するオブジェクトを定義し `GraphQLObjectType` を生成する。この時オブジェクトのフィールドについても定義を行い、`fieldResolversByType` (後述) を使ってフィールド用の `DataFetcher` を設定する。

[`fieldResolversByType`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt#L57) は `ObjectTypeDefinition` がキーで、`MutableMap<FieldDefinition, FieldResolver>` をバリューとしてもつ `Map` で、`SchemaParser` クラスのコンストラクタで渡される `ScannedSchemaObjects` から取得している。`ScannedSchemaObjects` は `SchemaParserBuilder` から渡されるクラスで、[`SchemaClassScanner`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt) が [`scanForClasses`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt#L74) メソッドで生成する。
各 Resolver の動きに注目すると、`SchemaClassScanner` にてまず [`ResolverInfo`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/ResolverInfo.kt) を継承した各クラスに Resolver の情報を持たせる。
その上で [`scanResolverInfoForPotentialMatches`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt#L247) にて、オブジェクトのフィールドにマッチする `FieldResolver` を `ResolverInfo` を渡して生成し、`fieldResolversByType` へオブジェクトの型( `ObjectTypeDefinition` )をキーにして、`FieldDefinition` とともにセットする。`FieldResolver` を継承した各クラスは `DataFetcher` を生成するための [`createDataFetcher`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolver.kt#L15) メソッドを持っているため、これで各 Resolver をフィールドにマッピングすることができる。`FieldResolver` は [`MethodFieldResolver`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt), [`PropertyFieldResolver`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/PropertyFieldResolver.kt), [`PropertyMapResolver`](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/5.4.1/src/main/kotlin/com/coxautodev/graphql/tools/PropertyMapResolver.kt) と定義されており、適切なクラスがマッピングされる。

ちょっと理解がふわふわしているところもあるが、大まかな動きはこんな感じであろうと思う。                                                               

### graphql-spring-boot-starter と各ライブラリの関係

上記 graphql-java, graphql-java-servlet, graphql-java-tools を組み合わせたものを、Spring Boot っぽく DI の仕組みで使えるようにしたライブラリです。

[graphql-spring-boot-starter](https://github.com/graphql-java-kickstart/graphql-spring-boot/tree/v5.3.1/graphql-spring-boot-starter) の実体はほぼ同一リポジトリにある [graphql-spring-boot-autoconfigure](https://github.com/graphql-java-kickstart/graphql-spring-boot/tree/v5.3.1/graphql-spring-boot-autoconfigure) なので、このコードをもとに説明します。 
                                                                                                                           
graphql-spring-boot-autoconfigure には、Spring Framework の @Configuration アノテーションがついたクラスが存在していて、XxxAutoConfiguration という postfix がついています。WebSocket 向けの設定などもありますが、この記事では [`GraphQLJavaToolsAutoConfiguration`](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.java) と [`GraphQLWebAutoConfiguration`](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.java) について見ていきます。
                                                                                                  
`GraphQLJavaToolsAutoConfiguration` はその名の通り graphql-java-tools 向けの設定で、graphql-java の [`GraphQLSchema` を `Bean` として作るため](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.java#L115)の一連の設定となっています。`GraphQLSchema` を作るための `SchemaParser` に渡す Resolver は [`List<GraphQLResolver<?>> resolvers`](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.java#L61) として Constructor Injection で受け取るようにしており、使う側としては `GraphQLResolver` の各種 IF を定義したクラスを作るだけで、アプリ起動後に GraphQL の機能が使えるようになります。graphql-java-tools もこれがあるおかげで、その真価を発揮できているように感じます。
                                    
`GraphQLWebAutoConfiguration` は graphql-java-servlet 用の一連の設定となっており、[`ServletRegistrationBean<AbstractGraphQLHttpServlet>`](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.java#L238) を `@Bean` として定義することで、[`GraphQLHttpServlet`(デフォルトでは)](https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v5.3.1/graphql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.java#L227)を Servlet として登録しています。`ServletRegistrationBean` については[こちら](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/ServletRegistrationBean.html)。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 さんの資料見直している気がする。感謝 :pray:

### 関連記事、サンプル実装、他言語での情報とか

- フロントエンド向けの 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