1. 概要

GraphQL は、RESTの代替としてWebサービスを構築するための比較的新しい概念です。 最近、GraphQLサービスを作成および呼び出すためのJavaライブラリがいくつか登場しました。

このチュートリアルでは、GraphQLスキーマ、クエリ、およびミューテーションについて説明します。 プレーンなJavaで単純なGraphQLサーバーを作成してモックする方法を見ていきます。 次に、よく知られているHTTPライブラリを使用してGraphQLサービスを呼び出す方法について説明します。

最後に、GraphQLサービス呼び出しを行うために利用可能なサードパーティライブラリについても説明します。

2. GraphQL

GraphQLは、Webサービス用のクエリ言語であり、型システムを使用してクエリを実行するためのサーバー側ランタイムです。

GraphQLサーバーは、GraphQLスキーマを使用してAPI機能を指定します。 これにより、GraphQLクライアントはAPIから取得するデータを正確に指定できます。 これには、1つのリクエストに子リソースと複数のクエリが含まれる場合があります。

2.1. GraphQLスキーマ

GraphQLサーバーは、一連のタイプでサービスを定義します。 これらのタイプは、サービスを使用して照会できる可能なデータのセットを記述します。

GraphQLサービスは、任意の言語で記述できます。 ただし、GraphQLスキーマは、GraphQLスキーマ言語と呼ばれるDSLを使用して定義する必要があります。

この例のGraphQLスキーマでは、2つのタイプ(BookAuthor)と、すべての本をフェッチする単一のクエリ操作( allBooks )を定義します。

type Book {
    title: String!
    author: Author
}

type Author {
    name: String!
    surname: String!
}

type Query {
    allBooks: [Book]
}

schema {
    query: Query
}

Query タイプは、GraphQLクエリのエントリポイントを定義するため、特別です。

2.2. クエリとミューテーション

GraphQLサービスは、タイプとフィールドを定義し、さまざまなフィールドに関数を提供することによって作成されます。

最も単純な形式では、GraphQLはオブジェクトの特定のフィールドを要求することを目的としています。 たとえば、すべての本のタイトルを取得するようにクエリを実行できます。

{
    "allBooks" {
        "title"
    }
}

見た目は似ていますが、これはJSONではありません。 これは、引数、エイリアス、変数などをサポートする特別なGraphQLクエリ形式です。

GraphQLサービスは、次のようなJSON形式の応答で上記のクエリに応答します。

{
    "data": {
        "allBooks": [
            {
                "title": "Title 1"
            },
            {
                "title": "Title 2"
            }
        ]
    }
}

このチュートリアルでは、クエリを使用したデータフェッチに焦点を当てます。 ただし、GraphQL内の別の特別な概念であるミューテーションについて言及することが重要です。

変更を引き起こす可能性のある操作はすべて、ミューテーションタイプを使用して送信されます。

3. GraphQLサーバー

上で定義したスキーマを使用して、Javaで単純なGraphQLサーバーを作成しましょう。 GraphQLサーバーの実装には、GraphQLJavaライブラリを利用します。

まず、GraphQLクエリを定義し、サンプルのGraphQLスキーマで指定されているallBooksメソッドを実装します。

public class GraphQLQuery implements GraphQLQueryResolver {

    private BookRepository repository;

    public GraphQLQuery(BookRepository repository) {
        this.repository = repository;
    }

    public List<Book> allBooks() {
        return repository.getAllBooks();
    }

}

次に、GraphQLエンドポイントを公開するために、Webサーブレットを作成します。

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends HttpServlet {

    private SimpleGraphQLHttpServlet graphQLServlet;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
        graphQLServlet.service(req, resp);
    }

    @Override
    public void init() {
        GraphQLSchema schema = SchemaParser.newParser()
          .resolvers(new GraphQLQuery(new BookRepository()))
          .file("schema.graphqls")
          .build()
          .makeExecutableSchema();
        graphQLServlet = SimpleGraphQLHttpServlet
          .newBuilder(schema)
          .build();
    }
}

サーブレットinitメソッドでは、resourcesフォルダーにあるGraphQLスキーマを解析します。 最後に、解析されたスキーマを使用して、SimpleGraphQLHttpServletのインスタンスを作成できます。

maven-war-plugin を使用してアプリケーションをパッケージ化し、jetty-maven-pluginを使用してアプリケーションを実行します。

mvn jetty:run

これで、以下にリクエストを送信して、GraphQLサービスを実行およびテストする準備が整いました。

http://localhost:8080/graphql?query={allBooks{title}}

4. HTTPクライアント

RESTサービスと同様に、GraphQLサービスはHTTPプロトコルを介して公開されます。 したがって、任意のJava HTTPクライアントを使用して、GraphQLサービスを呼び出すことができます。

4.1. リクエストの送信

前のセクションで作成したGraphQLサービスにリクエストを送信してみましょう。

public static HttpResponse callGraphQLService(String url, String query) 
  throws URISyntaxException, IOException {
    HttpClient client = HttpClientBuilder.create().build();
    HttpGet request = new HttpGet(url);
    URI uri = new URIBuilder(request.getURI())
      .addParameter("query", query)
      .build();
    request.setURI(uri);
    return client.execute(request);
}

この例では、 ApacheHttpClientを使用しました。 ただし、任意のJavaHTTPクライアントを使用できます。

4.2. 応答の解析

次に、GraphQLサービスからの応答を解析してみましょう。 GraphQLサービスは、RESTサービスと同じようにJSON形式の応答を送信します。

HttpResponse httpResponse = callGraphQLService(serviceUrl, "{allBooks{title}}");
String actualResponse = IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8.name());
Response parsedResponse = objectMapper.readValue(actualResponse, Response.class);
assertThat(parsedResponse.getData().getAllBooks()).hasSize(2);

この例では、人気のあるJacksonライブラリのObjectMapperを使用しました。 ただし、JSONのシリアル化/逆シリアル化には任意のJavaライブラリを使用できます。

4.3. 嘲笑の反応

HTTPを介して公開される他のサービスと同様に、テスト目的でGraphQLサーバーの応答をモックすることができます

MockServer ライブラリを利用して、外部のGraphQLHTTPサービスをスタブ化できます。

String requestQuery = "{allBooks{title}}";
String responseJson = "{\"data\":{\"allBooks\":[{\"title\":\"Title 1\"},{\"title\":\"Title 2\"}]}}";

new MockServerClient(SERVER_ADDRESS, serverPort)
    .when(
      request()
        .withPath(PATH)
        .withQueryStringParameter("query", requestQuery),
      exactly(1)
    )
    .respond(
      response()
        .withStatusCode(HttpStatusCode.OK_200.code())
        .withBody(responseJson)
    );

この例のモックサーバーは、パラメーターとしてGraphQLクエリを受け入れ、本文でJSON応答で応答します。

5. 外部ライブラリ

最近、さらに単純なGraphQLサービス呼び出しを可能にするJavaGraphQLライブラリがいくつか登場しました。

5.1. AmericanExpressノード

Nodes は、標準モデル定義からクエリを構築するために設計されたAmericanExpressのGraphQLクライアントです。 使用を開始するには、最初に必要な依存関係を追加する必要があります。

<dependency>
    <groupId>com.github.americanexpress.nodes</groupId>
    <artifactId>nodes</artifactId>
    <version>0.5.0</version>>
</dependency>

ライブラリは現在JitPackでホストされており、Mavenインストールリポジトリにも追加する必要があります。

<repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
</repository>

依存関係が解決されたら、 GraphQLTemplate を使用してクエリを作成し、GraphQLサービスを呼び出すことができます。

public static GraphQLResponseEntity<Data> callGraphQLService(String url, String query)
  throws IOException {
    GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

    GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
      .url(StringUtils.join(url, "?query=", query))
      .request(Data.class)
      .build();

    return graphQLTemplate.query(requestEntity, Data.class);
}

Nodes は、指定したクラスを使用してGraphQLサービスからの応答を解析します。

GraphQLResponseEntity<Data> responseEntity = callGraphQLService(serviceUrl, "{allBooks{title}}");
assertThat(responseEntity.getResponse().getAllBooks()).hasSize(2);

Nodes では、応答を解析するために独自のDTOクラスを構築する必要があることに注意してください。

5.2. GraphQL Java Generator

GraphQL Java Generator ライブラリは、機能を利用してGraphQLスキーマに基づいてJavaコードを生成します。

このアプローチは、SOAPサービスで使用されるWSDLコードジェネレーターに似ています。 使用を開始するには、最初に必要な依存関係を追加する必要があります。

<dependency>
    <groupId>com.graphql-java-generator</groupId>
    <artifactId>graphql-java-runtime</artifactId>
    <version>1.18</version>
</dependency>

次に、generateClientCodeゴールを実行するようにgraphql-maven-pluginを構成できます。

<plugin>
    <groupId>com.graphql-java-generator</groupId>
    <artifactId>graphql-maven-plugin</artifactId>
    <version>1.18</version>
    <executions>
        <execution>
            <goals>
                <goal>generateClientCode</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <packageName>com.baeldung.graphql.generated</packageName>
        <copyRuntimeSources>false</copyRuntimeSources>
        <generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
        <separateUtilityClasses>true</separateUtilityClasses>
    </configuration>
</plugin>

Mavenビルドコマンドを実行すると、プラグインはGraphQLサービスの呼び出しに必要なDTOとユーティリティクラスの両方を生成します。

生成されたQueryExecutorコンポーネントには、GraphQLサービスを呼び出してその応答を解析するためのメソッドが含まれます。

public List<Book> allBooks(String queryResponseDef, Object... paramsAndValues)
  throws GraphQLRequestExecutionException, GraphQLRequestPreparationException {
    logger.debug("Executing query 'allBooks': {} ", queryResponseDef);
    ObjectResponse objectResponse = getAllBooksResponseBuilder()
      .withQueryResponseDef(queryResponseDef).build();
    return allBooksWithBindValues(objectResponse, 
      graphqlClientUtils.generatesBindVariableValuesMap(paramsAndValues));
}

ただし、Springフレームワークで使用するために構築されています。

6. 結論

この記事では、JavaアプリケーションからGraphQLサービスを呼び出す方法について説明しました。

単純なGraphQLサーバーを作成してモックする方法を学びました。 次に、標準のHTTPライブラリを使用してGraphQLサーバーからリクエストを送信してレスポンスを取得する方法を説明しました。 また、JSONからJavaオブジェクトへのGraphQLサービス応答を解析する方法も確認しました。

最後に、GraphQLサービス呼び出しを行うために利用可能な2つのサードパーティライブラリ、NodesGraphQLJavaGeneratorを調べました。

いつものように、完全なソースコードはGitHubから入手できます。