Supersonic Subatomic GraphQL

Red Hat で Solution Architect として Quarkus を担当している伊藤ちひろです。

この記事は、Quarkus.ioのブログ記事、Supersonic Subatomic GraphQL の翻訳記事です。

MicroProfile GraphQL は、リリースされたばかりの Quarkus のバージョン 1.5.0 に含まれています。

GraphQL とは?

「 GraphQL は、API 用のオープンソースのデータクエリおよび操作言語です。また、既存のデータを使ってクエリを実行するためのランタイムです。GraphQL は、クライアントから文字列を解釈ます。そして、あらかじめ定義された方法で、わかりやすく、予測可能な方法でデータを返します。GraphQL は REST の代わりとなるものではありますが、必ずしもRESTを置き換えるものではありません。」

完全な GraphQL の仕様書を読む

なぜ GraphQL なのか?

GraphQL を使う主な理由は以下の通りです。

  • データのオーバーフェッチやアンダーフェッチを避ける。クライアントは 1 つのリクエストで複数のタイプのデータを取得できます。また、特定の基準に基づいて応答データを制限できます。
  • データモデルの進化を可能にする。スキーマへの変更は、既存のクライアントの変更を必要としないように行うことがでます。その逆も可能です。これは、アプリケーションの新しいバージョンを必要とせずに実行できます。
  • 先進的な開発者の経験:

    • スキーマは、データへのアクセス方法を定義し、クライアントとサーバー間の契約として機能します。双方の開発チームは、これ以上コミュニケーションを取らなくても仕事ができます。

    • ネイティブスキーマの内観は、ユーザーがAPIを発見し、クライアント側でクエリを洗練することを可能にします。この利点は、スムーズで簡単なAPIの発見が可能になGraphiQLやGraphQL Voyagerなどのグラフィカルなツールを増加することです。

    • クライアント側では、クエリ言語は柔軟性と効率性を提供します。これにより、開発者はサーバーへの繰返しを減らしながら、技術環境の制約に適応できます。

MicroProfile GraphQLとは?

「MicroProfile GraphQL仕様の意図は、"コードファースト"のAPIセットを提供することです。それは、ユーザーがJavaでポータブルなGraphQLベースのアプリケーションを迅速に開発できるます。この仕様のすべての実装には、主に2つの要件があります。

  • GraphQL Schemaを生成して利用可能にする。これは、ユーザーコードのアノテーションを参照することで行われます。また、すべての GraphQL クエリとミューテーション、およびクエリとミューテーションのレスポンス型または引数を介して暗黙的に定義されたすべてのエンティティを含まなければなりません。

  • GraphQLリクエストを実行する。これは、クエリまたはミューテーションのどちらかの形になります。最低限、仕様はHTTP経由でのこれらのリクエストの実行をサポートしなければなりません。

MicroProfile GraphQL 仕様書の全文を読む

これで code.quarkus.io を使って Quarkus を使い始め、SmallRye GraphQL 拡張機能を含むことができるようになりました。

https://quarkus.io/assets/images/posts/supersonic-subatomic-graphql/code_quarkus.png

これにより、以下の依存関係を持つ Quarkus スターターアプリケーションが作成されます。

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-junit5</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>rest-assured</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>

今のところ、作成した例のアプリケーションは JAX-RS アプリケーションです。拡張機能がカスタムサンプルアプリケーションを定義できるようにするためのいくつかの作業が進行中です。そのため、それまでは常に JAX-RS アプリケーションを取得します。JAX-RS は必要ないので、quarkus-resteasy の依存関係を削除できます。

最初の GraphQL エンドポイント。

ExampleResource Rest サービスを GraphQL エンドポイントに変更します。

  1. クラスのアノテーション Path("/hello")@GraphQLApi に置き換えます。
  2. メソッドのアノテーション @GET@Query に置き換えます。
  3. メソッドのアノテーション @Produces(MediaType.TEXT_PLAIN) と、すべてのJAX-RSインポートを削除します。

これだけです!あなたの ExampleResource は現在こうになっているはずです。

package org.acme;

import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Query;

@GraphQLApi
public class ExampleResource {

    @Query
    public String hello() {
        return "hello";
    }
}

これで、Quarkus の dev モードを使用してアプリケーションを実行できるようになりました。

mvn quarkus:dev 

ここで localhost:8080/graphql-ui/ を参照し、以下のクエリを実行します。

{
  hello
}

これは以下を返すでしょう。

{
  "data": {
    "hello": "hello"
  }
}

MicroProfile GraphQL ガイドも参照してください。

より詳細な例

GitHub プロジェクトからソースを取得して、より詳細な例を見てみましょう。

これはマルチモジュールのアプリケーションです。まず、すべてのモジュールをコンパイルします。ルートディレクトリで、

mvn clean install 

今すぐ Quarkus の例を閲覧してください。

cd quarkus-example 

ProfileGraphQLApi.java を見てください。@GraphQLApi とマークされています。

    @Query("person")
    public Person getPerson(@Name("personId") int personId){
        return personDB.getPerson(personId);
    }

上記のメソッドは personId で人物を取得します。ご覧のように、メソッドは @Query アノテーションで問い合せ可能になっています。オプションで名前(この場合は"person")を指定できます。しかし、デフォルトはいずれにせよ"person"です(メソッド名に"get"を含まない)。オプションでパラメータに名前を付けられますが、デフォルトはパラメータ名 ("personId") になります。

Person オブジェクトは、システム内の Person (User または Member) を表す POJO です。それは多くのフィールドを持っており、いくつかは他の複雑な POJO です。

https://quarkus.io/assets/images/posts/supersonic-subatomic-graphql/person.png

しかし、Query アノテーションを使用すると、興味のあるフィールドを正確に問い合せ可能になります。

サンプルアプリケーションを実行します。

mvn quarkus:dev 

ここで localhost:8080/graphql-ui/ を参照し、以下のクエリを実行します。

{
  person(personId:1){
    names
    surname
    scores{
      name
      value
    }
  }
}

エディタに「code insight」があることに注目してください。それは、GraphQLがスキーマを持っていて、内観にも対応しているからです。

興味のある分野だけをリクエストすることができ、ペイロードをかなり小さくできます。

https://quarkus.io/assets/images/posts/supersonic-subatomic-graphql/graphiql.png

クエリを組み合わせることもできます。例えば、上記のように人物 1 のフィールドを取得し、人物 2 の名前と苗字を取得したい場合は、以下のようにします。

{
  person1: person(personId:1){
    names
    surname
    scores{
      name
      value
    }
  }
  person2: person(personId:2){
    names
    surname
  }
}

これは以下を返します。

{
  "data": {
    "person1": {
      "names": [
        "Christine",
        "Fabian"
      ],
      "surname": "O'Reilly",
      "scores": [
        {
          "name": "Driving",
          "value": 15
        },
        {
          "name": "Fitness",
          "value": 94
        },
        {
          "name": "Activity",
          "value": 63
        },
        {
          "name": "Financial",
          "value": 22
        }
      ]
    },
    "person2": {
      "names": [
        "Masako",
        "Errol"
      ],
      "surname": "Zemlak"
    }
  }
}

ソースフィールド

クエリをよく見ると、人物の score フィールドを尋ねたことがわかります。ですが、Person POJOには score フィールドが含まれていません。人物に @Source フィールドを追加して score フィールドを追加しました。

    @Query("person")
    public Person getPerson(@Name("personId") int personId){
        return personDB.getPerson(personId);
    }

    public List<Score> getScores(@Source Person person) {
        return scoreDB.getScores(person.getIdNumber());
    }

そこで、レスポンス型に一致する @Source パラメータを追加することで、出力にマージするフィールドを追加できます。

部分的な結果

上記の例では 2 つの異なるデータソースを統合していますが、 score システムがダウンしているとします。そうすると、まだ持っているデータと、score のエラーが返ってきます。

{
  "errors": [
    {
      "message": "Scores for person [797-95-4822] is not available",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ],
      "path": [
        "person",
        "scores2"
      ],
      "extensions": {
        "exception": "com.github.phillipkruger.user.graphql.ScoresNotAvailableException",
        "classification": "DataFetchingException"
      }
    }
  ],
  "data": {
    "person": {
      "names": [
        "Christine",
        "Fabian"
      ],
      "surname": "O'Reilly",
      "scores2": null
    }
  }
} 

ネイティブモード

この例をネイティブモード( graalvm-ce-java11-19.3.2 を使用)で実行してみましょう。

mvn -Pnative clean install 

これでネイティブの実行ファイルが作成され、アプリケーションが非常に早く起動するようになります。

./target/quarkus-example-1.0.0-SNAPSHOT-runner
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2020-06-11 17:02:55,041 INFO  [io.quarkus] (main) quarkus-example 1.0.0-SNAPSHOT native (powered by Quarkus 1.5.0.Final) started in 0.026s. Listening on: http://0.0.0.0:8080
2020-06-11 17:02:55,041 INFO  [io.quarkus] (main) Profile prod activated.
2020-06-11 17:02:55,041 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, smallrye-graphql, smallrye-openapi, swagger-ui]

パイプラインの中

これはMicroProfile GraphQL Specの最初のバージョンで、パイプラインにはいろいろなものがあります。そのうちの一つがクライアントです。私たちは2種類のクライアントを提案しています。

ダイナミック

ダイナミッククライアントを使用すると、ビルダーを使用してクエリを構築できます。

// Building of the graphql document.
Document myDocument = document(
                operation(Operation.Type.QUERY,
                        field("people",
                                field("id"),
                                field("name")
                        )));

// Serialization of the document into a string, ready to be sent.
String graphqlRequest = myDocument.toString();

詳細は以下を参照してください:github.com/worldline/dynaql

型安全

型安全クライアントはMicroProfile RESTClientに近いものになります。上記と同じ例を見て、どのようにそれを使用することができるかを見てみましょう。プロジェクトのルートから、quarkus-client フォルダを参照します。この例では、Quarkus コマンドモードを使用してクエリを作成しています。

クライアントがまだ Quarkus 拡張機能ではないので、こんな感じでプロジェクトに追加しています。

<dependency>
    <groupId>io.smallrye</groupId>
    <artifactId>smallrye-graphql-client</artifactId>
    <version>${smallrye-graphql.version}</version>
</dependency>

これで、興味のあるフィールドだけを含む POJO を作成できるようになりました。クライアントモジュールの PersonScore を見てみると、サーバー側の定義よりもかなり小さくなっています。

https://quarkus.io/assets/images/posts/supersonic-subatomic-graphql/client.png

あとは、興味のあるクエリを定義するインターフェースを追加するだけです。

@GraphQlClientApi
public interface PersonGraphQLClient {

    public Person person(int personId);

}

そして、これを使えるようになりました。

    //@Inject
    //PersonGraphQLClient personClient; or
    PersonGraphQLClient personClient = GraphQlClientBuilder.newBuilder().build(PersonGraphQLClient.class);

    // ...
    Person person = personClient.person(id);

Quarkus クライアントアプリケーションを実行すると、サーバーへの呼び出しを行い(これがまだ実行されていることを確認してください)、レスポンスを出力できるようになりました。

java -jar target/quarkus-client-1.0.0-SNAPSHOT-runner.jar 2
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=lcd
=========================
|  Masako Zemlak        |
|                       |
|        Driving        |
|        48             |
|                       |
|        Fitness        |
|        73             |
|                       |
|        Activity       |
|        62             |
|                       |
|        Financial      |
|        54             |
|                       |
=========================

(2) の数字は、この例では personId です。

概要

これは Quarkus で利用できるようになった MicroProfile GraphQL の簡単な紹介です。まだまだ多くの機能があり、さらに多くの機能が計画されていますので、ご期待ください。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。