0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GraphQLをJavaから利用してみる

Posted at

はじめに

  • OSSなどでWebAPIを提供する際に、GraphQLが採用されるケースがしばしば見受けられるようになってきた。
  • REST後継とも目されるGraphQLを調査するとともにJava言語から利用する方法を調査する。
  • 本ドキュメントの想定購読者は以下とする。
    • Java言語で GraphQL Clientを構築したい。
    • スターウォーズよりもポケモン(第1世代:カントー地方)が好き。

GraphQLとは

  • Web APIのデファクトはREST(Restful API)であるが、その後継(の1つ)と見做されている GraphQL を紹介する。
    • Facebook(現Meta)が開発したクエリ言語で、現在はGraphQL財団に移譲されている。
    • リクエストに「必要なデータの構造」を定義でき、レスポンスは「定義されたデータ構造」が応答される。必要なデータのみ取得することで効率良く通信を実施できる。
    • 特徴
      • 型システム、クエリ言語、実行セマンティクス、静的な検証、型チェックから構成される。
      • データの問い合わせ (query)、書き換え (mutation)、購読 (subscription) をサポートする。
      • JSONに似たDSLで記述する(JSONとの完全な互換はなし)
  • 参考
GraphQL リクエスト例 (ポケモンの図鑑ナンバーのみを取得)
query { pokemon(name: "Mew") { number } }
GraphQL レスポンス例 (ミュウのの図鑑ナンバーのみを応答)
{ "data": { "pokemon": { "number": "151" }}}

GraphiQL / GraphQL Playground

  • GraphQLのAPIを簡単に実行・挙動確認するためのツールがオープンソースで開発されている。有名なものは以下となっている。
  • いずれもブラウザ上で利用できる統合開発環境で、シンタックスハイライト、入力補完などの機能が提供されている。

GraphQL Pokémonを利用したクエリ例

  • GraphQLでよく利用されているサンプルとして、以下を確認した。
  • 本ドキュメントでは、一番楽しそうな「ポケモン(第1世代)」をGraphQLで検索できる GraphQL Pokémon を利用して学習を行う(graphql-pokemonの方が色々と属性情報が多そうだが、習熟が目的なのでより簡易的なものを利用する方針とした)。
  • GraphQL Pokémonでは、上記のGraphiQLも提供されており、簡易にクエリを実行・動作確認ができる。

クエリ定義

  • 実行可能なクエリとして以下が確認できる(スキーマ定義)。
    • pokemons: 指定した「数」の「ポケモン一覧」を図鑑順で取得するクエリ。
    • pokemon: 「ポケモンID」および「名前」を指定して特定の「ポケモン」を取得するクエリ。
  pokemons(first: Int!): [Pokemon]
  pokemon(id: String, name: String): Pokemon

型定義

  • クエリ応答結果となるPokemon型が下記構成であることが確認できる(スキーマ定義)。

image.png

実行例 pokemons

クエリ

  • 引数first4 および属性情報としてnameを指定し、pokemonsクエリを実行した。
query {
  pokemons(first: 4) {
    name
  }
}

実行結果

  • 図鑑順にポケモン(Pokemons型)一覧が応答されていることが確認できる。
  • ポケモンの属性情報として、クエリで指定したnameのみが返されていることも確認できる。
    • 図鑑No.001: フシギダネ(Bulbasaur)
    • 図鑑No.002: フシギソウ(Ivysaur)
    • 図鑑No.003: フシギバナ(Venusaur)
    • 図鑑No.004: ヒトカゲ(Charmander)
{ "data": {
    "pokemons": [
      { "name": "Bulbasaur" },
      { "name": "Ivysaur" },
      { "name": "Venusaur" },
      { "name": "Charmander"}
    ]
}}

実行例 pokemon

クエリ

  • 引数namePikachu、属性情報にnumber,name,attacksを指定し、pokemonクエリを実行した。
query {
  pokemon(name: "Pikachu") {
    number
    name
    attacks {
      special {
        name
      }
    }
  }
}

実行結果

  • ポケモンの属性情報として、クエリで指定したnumber,name,attacksのみが返されていることが確認できる。
{ "data": {
    "pokemon": {
      "number": "025",
      "name": "Pikachu",
      "attacks": {
        "special": [
          { "name": "Discharge" },
          { "name": "Thunder" },
          { "name": "Thunderbolt" }
        ]
}}}}

JavaからGraphQLを利用する方法

  • GraphQLはhttpプロトコルで実現されているため、究極的にはHTTPクライアントさえあればGraphQLは利用はできる。
  • ただし、利用するシステムに対応した静的型付けされたクラスがないと汎用性・可読性が低くなり利用がしづらい。
  • そのため、スキーマ定義からJavaクラスを生成・利用する方法を調査する。

GraphQLにおけるスキーマ定義

  • GraphQLではスキーマを定義する方法として以下の2種類が存在している(内容はほぼ同一だが、構造に差分あり)
    • SDL
      • QraphQLのスキーマ定義ファイル。
      • OSSなどであれば公開されていることが多い。
      • 拡張子は .graphql
    • Introspection Result
      • GraphQLではサポートしているオペレーションを確認する仕様(Introspection)が規定されているため、必ず公開されている。
      • Introspection queryの応答としてスキーマ定義情報(Introspection result)を取得できる。
        (参考: martinheld氏が公開しているGraphQL introspection query via curlが完璧)
      • 拡張子は .json

スキーマ定義からJavaクラスの生成

  • 調査した結果、スキーマ定義からJavaクラスを生成できるツールが色々存在していることが確認できた。
    本資料では、 graphql-java-codegen を利用する方針とする。

graphql-java-codegenを利用したJavaファイル生成

  • graphqlSchemasで SDLファイルを指定する(ここでは、GraphQL PokémonのSDLを指定)
  • graphql-java-codegenのオプションはこちらを参照。
pom.xml
<build><plugins><plugin>
    <groupId>io.github.kobylynskyi</groupId>
    <artifactId>graphql-codegen-maven-plugin</artifactId>
    <version>${graphql-java-codegen.version}</version>
    <executions>
      <execution>
        <id>generate-sources-product-client</id>
        <goals><goal>generate</goal></goals>
        <configuration>
          <graphqlSchemas>
            <includePattern>schema.graphqls</includePattern>
          </graphqlSchemas>
          <outputDir>${project.build.directory}/generated-sources/client</outputDir>
          <modelPackageName>com.github.nomunomu5678.pokemon.model</modelPackageName>
          <generateClient>true</generateClient>
        </configuration>
      </execution>
    </executions>
</plugin></plugins></build>
  • SDLファイルはリソースディレクトリに配置する。
.
├── src
│   └── main
│       └── resources
│           └── schema.graphqls
└── pom.xml
  • mvn compileでJavaファイルが生成される。
    • *Request.java: GraphQLのリクエスト(パラメータ)
    • *ResponseProjection.java: GraphQLのリクエスト(取得対象の項目定義)
    • *Response.java: GraphQLのレスポンス
    • その他: GraphQLのデータモデル
$ mvn compile

$ tree ./target/generated-sources/client 
./target/generated-sources/client
├── Attack.java
├── AttackResponseProjection.java
├── Pokemon.java
├── PokemonAttack.java
├── PokemonAttackResponseProjection.java
├── PokemonDimension.java
├── PokemonDimensionResponseProjection.java
├── PokemonEvolutionRequirement.java
├── PokemonEvolutionRequirementResponseProjection.java
├── PokemonQueryRequest.java
├── PokemonQueryResponse.java
├── PokemonResponseProjection.java
├── PokemonsQueryRequest.java
├── PokemonsQueryResponse.java
├── QueryQueryRequest.java
└── QueryQueryResponse.java

GraphQLリクエストの生成/シリアライズ

  • GraphQLRequestのコンストラクタに、*Request*ResponseProjectionを指定する。
    • *Request.java: GraphQLのリクエスト(パラメータ)
    • *ResponseProjection.java: GraphQLのリクエスト(取得対象の項目定義)
  public static void main(String[] args) {
    PokemonQueryRequest req = PokemonQueryRequest.builder()
        .setName("Pikachu")
        .build();
    PokemonResponseProjection resp = new PokemonResponseProjection()
        .number()
        .name();
    GraphQLRequest graphqlReq = new GraphQLRequest(req, resp);
    System.out.println(graphqlReq.toHttpJsonBody());
  }
応答結果
{"query":"query pokemon { pokemon: pokemon(name: \"Pikachu\"){ number name } }"}

GraphQLレスポンス/デシリアライズ

  • JSONをオブジェクトマッパーを利用してデシリアライズする。
  public static void main(String[] args) throws Exception {
    String graphqlRes = "{\"data\": {\"pokemon\": {\"id\": \"UG9rZW1vbjowMjU=\"}}}";
    ObjectMapper objectMapper = new ObjectMapper();
    PokemonQueryResponse res = objectMapper.readValue(graphqlRes, PokemonQueryResponse.class);
    System.out.println(res.getData());
  }
応答結果
{pokemon={ id: "UG9rZW1vbjowMjU=" }}

生成したコードサンプル

まとめ

  • GraphQLをJavaから利用する方法の調査を行った。
  • 実現方式として、GraphQLのスキーマ定義から生成したJavaクラスを利用する方式を調査した。
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?