尝试在Java中使用GraphQL

首先

    • 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との完全な互換はなし)

参考

https://www.howtographql.com/basics/1-graphql-is-the-better-rest/
https://techtarget.itmedia.co.jp/tt/news/2002/10/news04.html

query { pokemon(name: "Mew") { number } }
{ "data": { "pokemon": { "number": "151" }}}

GraphiQL / GraphQL游乐场

    • GraphQLのAPIを簡単に実行・挙動確認するためのツールがオープンソースで開発されている。有名なものは以下となっている。

GraphiQL
GraphQL Playground

いずれもブラウザ上で利用できる統合開発環境で、シンタックスハイライト、入力補完などの機能が提供されている。

使用GraphQL查询Pokemon的示例

    • GraphQLでよく利用されているサンプルとして、以下を確認した。

The Star Wars API
GitHub GraphQL API
GraphQL
graphql-pokemon

本ドキュメントでは、一番楽しそうな「ポケモン(第1世代)」をGraphQLで検索できる GraphQL を利用して学習を行う(graphql-pokemonの方が色々と属性情報が多そうだが、習熟が目的なのでより簡易的なものを利用する方針とした)。

GraphQL では、上記のGraphiQLも提供されており、簡易にクエリを実行・動作確認ができる。

查询定义。

    • 実行可能なクエリとして以下が確認できる(スキーマ定義)。

pokemons: 指定した「数」の「ポケモン一覧」を図鑑順で取得するクエリ。

pokemon: 「ポケモンID」および「名前」を指定して特定の「ポケモン」を取得するクエリ。

  pokemons(first: Int!): [Pokemon]
  pokemon(id: String, name: String): Pokemon

定义变量

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

以下是实际执行的宝可梦例子。

查询

    引数firstに 4 および属性情報として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"}
    ]
}}

精灵宝可梦的实际案例

查询

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

结果执行

    • ポケモンの属性情報として、クエリで指定したnumber,name,attacksのみが返されていることが確認できる。

Discharge (ほうでん)
Thunder (かみなり)
Thunderbolt (10まんボルト)

{ "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 を利用する方針とする。

SDLからJavaクラスを生成できるツール

graphql-java-codegen
GraphQL Java

Introspection resultからJavaクラスを生成できるツール

graphql_java_gen
graphql-java-generator

使用graphql-java-codegen来生成Java文件

graphqlSchemasで SDLファイルを指定する(ここでは、GraphQL のSDLを指定)

graphql-java-codegenのオプションはこちらを参照。

<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=" }}

生成的代码示例

    https://github.com/nomunomu5678/graphql_pokemon_java/tree/main

总结

    • GraphQLをJavaから利用する方法の調査を行った。

 

    実現方式として、GraphQLのスキーマ定義から生成したJavaクラスを利用する方式を調査した。
广告
将在 10 秒后关闭
bannerAds