最近、GraphQL が気になっています。学習を兼ねて簡単なサンプルを作ってみようかと。今回、Spring Boot で Query を試してみます。
サンプルコードはこちら。
kntmr/playground/graphql-spring-examples - GitHub
GraphQL
2015-2016年頃に登場してしばしば名前を見かけることはあったのですが、自分の観測範囲ではあまり盛り上がりを感じられず...。ただ、REST API の煩雑なところをいい感じに解決してくれそうで、今さら魅力を感じるようになってきました。
よく言われる GraphQL の特徴は以下のあたりかと思われます。
とはいえ、レスポンスとして返して欲しいデータをリクエストで指定したり、Swagger などを使うことで同等のことは実現できるかと思います。ただ、当然、サーバー側でそれなりに実装したり、ツールを導入する手間はかかるわけで、そのあたりは GraphQL を使うメリットになりそう。あと、今回は試してないですが、コードを自動生成するツールもあるようです。
あと、AWS がマネージドサービスとして提供するようになったので、今後、適用事例も増えていくんではなかろうかと思っています。
以下、備忘録。
依存ライブラリ
GraphiQL を追加すると、/graphiql
で GraphiQL が使えるようになる。
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency>
schema / データクラス
resources/graphql/schema.graphqls
にスキーマを定義。データクラスでは、参照先のクラス側に ID のフィールドを定義する。今回のサンプルでは、あるグループに所属するユーザーの ToDo リストを返すことを想定。
type Group { id: ID! name: String! } type User { id: ID! name: String! group: Group! todos: [ToDo] } type ToDo { id: ID! content: String! completed: Boolean! }
DAO / Resolver
DAO は適当にダミーデータを返すように実装。Resolver は、Query の場合は GraphQLQueryResolver
インタフェースを実装する。
@Component public class UserQueryResolver implements GraphQLQueryResolver { private UserDao userDao; public UserQueryResolver(UserDao userDao) { this.userDao = userDao; } public User getUser(int id) throws Exception { return userDao.findById(id).orElseThrow(() -> new Exception("User not found.")); } }
データの関連を解決する場合は GraphQLResolver<T>
インタフェースを実装する。今回、グループとユーザーの関連を解決する場合は次の通り。
@Component public class GroupResolver implements GraphQLResolver<User> { private GroupDao groupDao; public GroupResolver(GroupDao groupDao) { this.groupDao = groupDao; } public Group group(User user) { return groupDao.findById(user.getGroupId()).orElse(new Group(0, "Undefined")); } }
ユーザーと ToDo の関連を解決する場合は次の通り。
@Component public class TodoResolver implements GraphQLResolver<User> { private TodoDao todoDao; public TodoResolver(TodoDao todoDao) { this.todoDao = todoDao; } public List<ToDo> todos(User user) { return todoDao.findByUser(user.getId()); } }
リクエスト
アプリを起動して、http://localhost:8080/graphiql
にアクセスする。以下の形式でリクエストする。指定したフィールドをレスポンスで返してくれる。
query { getUser(id: 1) { id name group { id name } todos { id content completed } } }
所感
今回は簡単なサンプルでしたが、スキーマが複雑になるほど必要なデータクラスや Resolver が多くなりそうです。ただ、このあたりは自動生成のツールを使うことで解決できるんだろうか。スキーマファーストというだけあって、やはりスキーマの設計がキモなんだろうと思われます。いかに無駄なくシンプルに設計できるか。
次は Mutation を試してみようかと。あと、全体的にまだ理解が浅いのでドキュメントを読んで勉強する。その他、自動生成などの開発ツール周りを調べたい。