GraphQL を Spring Boot で試してみる 1

最近、GraphQL が気になっています。学習を兼ねて簡単なサンプルを作ってみようかと。今回、Spring Boot で Query を試してみます。

サンプルコードはこちら。

kntmr/playground/graphql-spring-examples - GitHub

GraphQL

graphql.org

2015-2016年頃に登場してしばしば名前を見かけることはあったのですが、自分の観測範囲ではあまり盛り上がりを感じられず...。ただ、REST API の煩雑なところをいい感じに解決してくれそうで、今さら魅力を感じるようになってきました。

よく言われる GraphQL の特徴は以下のあたりかと思われます。

  • エンドポイントを1つにまとめることができる
  • 1リクエストで必要なデータを取得できる
  • スキーマファースト (ドキュメントファースト)

とはいえ、レスポンスとして返して欲しいデータをリクエストで指定したり、Swagger などを使うことで同等のことは実現できるかと思います。ただ、当然、サーバー側でそれなりに実装したり、ツールを導入する手間はかかるわけで、そのあたりは GraphQL を使うメリットになりそう。あと、今回は試してないですが、コードを自動生成するツールもあるようです。

あと、AWS がマネージドサービスとして提供するようになったので、今後、適用事例も増えていくんではなかろうかと思っています。

aws.amazon.com


以下、備忘録。

依存ライブラリ

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 を試してみようかと。あと、全体的にまだ理解が浅いのでドキュメントを読んで勉強する。その他、自動生成などの開発ツール周りを調べたい。

参考