LoginSignup
7
5

More than 3 years have passed since last update.

Java × Spring Boot × Apollo で GraphQL クライアントを実装する

Last updated at Posted at 2021-01-11

はじめに

現在、長い間 API 設計のデファクトとなっていた REST から、GraphQL や gRPC といった新しいものを採用する API が続々と登場しています。
GitHub API v4 が GraphQL を採用しているというのを知っている方も多いかと思います。

今後も、GraphQL のような新しい設計を採用する API が増えていくことが予想されますが、従来の REST API アプリケーションに GraphQL のデータソースとの接続を容易にできるかというのは気になる点です。
そこで、今回は GraphQL API をデータソースとして叩くような GraphQL クライアント機能を、よくある Spring Boot アプリケーションにて Apollo という GraphQL クライアントを用いながら実装してみたいと思います。
:warning: GraphQL API サーバー自体を実装するわけではありません)

GraphQL 初心者の方にも理解しやすい様に、用語の説明はリンクまたは文章にて記載しているので、入門向けでもあります。

サーバー側の構築

サーバー側を一から実装していくのは面倒なので、ありものを使うことにします。
今回はポケモンを題材にした GraphQL API の GraphQL Pokémon を使っていきたいと思います:zap:

他の有名どころだと先述した GitHub API v4 や スターウォーズ (SWAPI GraphQL) などがあります。
GitHub API でもよかったのですが、認証が絡んできて多少脱線しそうなため、見送っています。

GraphQL Pokémon のデプロイ

非常に簡単なので、サーバー側のデプロイ方法を先述しておきます

  • ローカル環境で構築します
    • 事前に Node.js & yarn の導入が必要です(こちらの導入手順は割愛します)
# GitHubからクローン
$ git clone git@github.com:lucasbento/graphql-pokemon.git
$ cd graphql-pokemon

# build & run
$ yarn
$ yarn run build
$ yarn start

GraphQL-Pokemon started on http://localhost:5000/

GraphiQL で簡単 API 仕様把握

下記は、GraphiQL の画面キャプチャです。
右の Docs から型の要素情報を確認しつつ、欲しい query を簡単に作ることができます。
キャプチャの Query の例は、151番目までのカントー地方に生息するポケモンの図鑑番号・名前・タイプの情報を返却するものです。(英語名ですが :sweat_smile:
image.png

詳しい GraphQL クエリについての情報は公式ドキュメントを見てみることをオススメします。
Queries and Mutations | GraphQL

クライアント側の構築

JVM 環境で多く採用されている Spring Boot アプリケーション上で GraphQL サーバーを叩くような Web アプリケーションを作っていきます

  • Runtime
    • JVM
  • Language
    • Java 11
      • もちろん Kotlin や Scala などでも構いません
      • その場合、適宜コードは読み替えてください
  • Dependency Manager
    • Gradle
  • Framework
    • Spring Boot
      • JVM 上で動く Web アプリケーションを簡単に構築するために使用します
    • Apollo Android
      • 後ほど詳細を説明します

Project 作成

  • Spring Initializr を使って Spring Boot アプリケーションのひな形をつくっていきます
    • 最下部の Java バージョンはお使いの環境のものを利用してください
    • Dependencies に REST API を作るための Spring Web を追加してください

image.png

Apollo の導入

これから、Project を開いて実装を行っていくのですが、GraphQL API を叩くために必要な依存パッケージ(ライブラリ)を導入していきます

  • Gradle を用いて、GraphQL クライアントライブラリである apollo-android を導入します
    • apollo-android は文字通り Android 向けの GraphQL クライアントライブラリです
    • ただ、説明にもあるとおり JVM 上であれば利用することができるのでサーバーサイドでも用いることが可能です
      • 🤖 A strongly-typed, caching GraphQL client for Android and the JVM
  • build.gradle に Apollo Android 関連の依存ライブラリを追加します
% git diff
diff --git a/build.gradle b/build.gradle
index 1325946..269b7a7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ plugins {
        id 'org.springframework.boot' version '2.4.1'
        id 'io.spring.dependency-management' version '1.0.10.RELEASE'
        id 'java'
+       id 'com.apollographql.apollo' version '2.5.2'
 }

 group = 'com.example'
@@ -14,6 +15,9 @@ repositories {

 dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
+       implementation 'com.apollographql.apollo:apollo-runtime:2.5.2'
+       // 同期通信を簡単に書くために RxJava で書けるライブラリも import しています
+       implementation 'com.apollographql.apollo:apollo-rx3-support:2.5.2'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
 }

なぜ Apollo を使うの? :thinking:

Apollo を利用するのは、GraphQL のスキーマファーストという原理を大いに享受できるメリットがあるからです。

strongly-typed という説明にもあるとおり、Apollo はサーバ側のスキーマ情報を用いて API レスポンスの各言語のオブジェクトクラスを自動生成してくれます。
※ Apollo は、Java 版の apollo-android のほか、Node.js 版 (Web) や Swift 版 (iOS) など主要なクライアント言語に対応しています。

この自動生成が従来の REST とは大きく異なるところです。

従来は、サーバ側の API 仕様ドキュメントに基づいて、クライアント側で一からその定義通りに実装をしていました。
しかし、このレスポンスフィールドは必須のフィールドなのか?、型はなんなのか?、Null になり得るか?などの詳細を密に確認しながら進めなければなりませんでした。
さらに、API によって担当者が異なると、各々の流儀にしたがって作られた仕様書を読まなければなりませんでした。

場合によっては API ドキュメントは存在せず、クライアントエンジニアが実際のサーバ側のコード(言語)を読む必要があるかもしれません。
その場合も、言語仕様を熟知していないとミスリードが生まれてしまう危険が大いにあるといえます。

GraphQL は GraphQL 自体に型があり、Null 安全 (null safe) です。
利用できるリソースはすべてサーバ側のスキーマ情報に型とともに厳格に記述されているため、その型通りに生成されたオブジェクトを利用することは、すなわちコミュニケーションによる仕様のロストがないことを保証してくれます。

クライアントエンジニアは GraphQL のみを知っていれば、裏側が Java で書かれていようが Kotlin で書かれていようが、Ruby on Rails で書かれていようがその言語仕様を熟知する必要がないということです。
(逆に言えば、GraphQL を用いる場合は クライアント / サーバエンジニア 双方に GraphQL の学習コストはかかります。とはいえ、プログラミング言語を1つでも習得しているような人にとって、GraphQL の学習コストはそこまで大きくはないでしょう)

GraphQL API リクエスト実装

さて、ここからが本編です!

Apollo の公式ドキュメントを参考に実装していきますので、詳しい説明や補足などはこちらをご覧ください。
Get started with Java - Client (Android) - Apollo GraphQL Docs

schama.json の作成

サーバー側のスキーマ情報の定義ファイルをプロジェクトに追加します。
これがないと、自動生成の恩恵を受けることができないので必須の工程となります。
apollo-android の plugin に schema.json を生成してくれるための便利コマンドがあるので、それを実行して作っていきます。
先ほど、ローカルの5000番ポートに立ち上げておいた GraphQL Pokémon のエンドポイントを指定して schema.json を作成します。

# 先に出力先のフォルダを作っておく
$ mkdir -p src/main/graphql/graphqlpokemon

# schema.json作成
$ ./gradlew downloadApolloSchema \
  --endpoint="http://localhost:5000/graphql/endpoint" \
  --schema="src/main/graphql/graphqlpokemon/schema.json"

BUILD SUCCESSFUL in 616ms
1 actionable task: 1 executed

GraphQL Query を追加する

次に、必要なクエリ情報をまた別の定義ファイルに書いていきます。
これは、先ほどの GraphiQL で試した Query (151番目までのポケモンの情報)を使っていきましょう。

src/main/graphql/graphqlpokemon/KantoPokemons.graphql
query KantoPokemons {
  pokemons(first: 151) {
    number,
    name,
    types
  }
}

Apollo による Query モデルクラスの自動生成

上記まで出来たら、 strongly-typed の恩恵を受けるために、Apollo の機能を使って GraphQL Query に必要なモデルクラスを自動生成してもらいましょう!

$ ./gradlew generateApolloSources

BUILD SUCCESSFUL in 868ms
4 actionable tasks: 2 executed, 2 up-to-date

すると、下記の様に勝手に build ディレクトリ内に必要なモデルクラスが出来上がっているのがわかると思います。
また、上記の様に必ずしも手動で生成用の task を実行する必要はなく、 build タスクに依存して勝手に実行されるので、コンパイルすれば必ず最新の状態となります。すばらしい :clap:

はい、この3ステップ(実質自動生成なので2ステップ)で事前準備は完了です!とても簡単ですね。
Graph Query も GraphiQL で試したものを使っているので、基本的にコピペをミスらなければ確実に正しい構文で書いてあることが保証されているというのも大きなメリットかなと思います。

Repository の実装

やっとですが、Spring Boot アプリケーション側の実装となります。
GraphQL クライアント実装にフォーカスしているため、データソース取得が行えれば良いので、Spring Boot の Repository のみを実装していきます。
今回は上述のクエリの情報を返却する様な形で、カントー地方のポケモンの基本情報を取得するという機能を実装していきたいと思います。

src/main/java/com/example/graphqlclientspring/PokemonRepository.java
package com.example.graphqlclientspring;

import com.apollographql.apollo.ApolloClient;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.rx3.Rx3Apollo;
import graphqlpokemon.KantoPokemonsQuery;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class PokemonRepository {
    public List<KantoPokemonsQuery.Pokemon> fetchKantoPokemons() {
        final var apolloClient = ApolloClient.builder()
                .serverUrl("http://localhost:5000/graphql/endpoint")
                .build();

        final var query = KantoPokemonsQuery.builder().build();

        final var apolloQueryCall = apolloClient.query(query);

        // ブロッキング処理を簡単に書くため、Rx3Apollo を利用
        return Rx3Apollo.from(apolloQueryCall)
                .map(Response::getData)
                .map(KantoPokemonsQuery.Data::pokemons)
                .blockingFirst();
    }
}

上記のコードを見てもわかる通り、たった数行 Apollo の機能を使うだけで実装ができてしまいます。

Controller の実装

最後に、上記の Repository を叩くためのエントリーポイントを作ります。
特に難しいことはしておらず、Apollo が生成してくれるクラスはそのままではシリアライズができないので、 json 形式で返却できる様に POJO クラスを作成しそこへの詰め替えを行なっているというだけのシンプルな実装です。

src/main/java/com/example/graphqlclientspring/PokemonController.java
package com.example.graphqlclientspring;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
public class PokemonController {

    private final PokemonRepository pokemonRepository;

    public PokemonController(PokemonRepository pokemonRepository) {
        this.pokemonRepository = pokemonRepository;
    }

    @GetMapping("/pokemons")
    public ResponseEntity<List<PokemonResponseEntity>> kantoPokemons() {
        // 取得と出力用オブジェクトへの変換
        final var pokemons = pokemonRepository.fetchKantoPokemons()
                .stream()
                .map(pokemon -> new PokemonResponseEntity(pokemon.number(), pokemon.name(), pokemon.types()))
                .collect(Collectors.toList());

        return ResponseEntity.ok(pokemons);
    }

    // json 出力用 POJO
    public static class PokemonResponseEntity {
        public final String number;
        public final String name;
        public final List<String> types;

        public PokemonResponseEntity(String number, String name, List<String> types) {
            this.number = number;
            this.name = name;
            this.types = types;
        }
    }
}

動作確認

では、出来上がったコードを Run して動作を確認しましょう。

$ ./gradlew bootRun

別窓で実行 or ブラウザで開いてみましょう。(jq は json の整形用のコマンドです)
下記の様に151番目までのポケモンの情報が取得できたと思います :tada:

# Spring Boot アプリケーションのデフォルトポートは 8080
$ curl -s "http://localhost:8080/pokemons" | jq
[
  {
    "number": "001",
    "name": "Bulbasaur",
    "types": [
      "Grass",
      "Poison"
    ]
  },
  {
    "number": "002",
    "name": "Ivysaur",
    "types": [
      "Grass",
      "Poison"
    ]
  },
  {
    "number": "003",
    "name": "Venusaur",
    "types": [
      "Grass",
      "Poison"
    ]
  },
  {
    "number": "004",
    "name": "Charmander",
    "types": [
      "Fire"
    ]
  },
  {
    "number": "005",
    "name": "Charmeleon",
    "types": [
      "Fire"
    ]
  },

まとめ

だいぶ長くなってしまいましたが、以上が GraphQL API のデータソースを叩くクライアント処理の実装でした。
もし、役に立った場合は LGTM やコメントをいただけると励みになります :pray:
最後までお読みいただき、ありがとうございました :bow:

GitHub リポジトリ

下記に commit していますので、ご参考にどうぞ。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5