SmallRye GraphQL 客户端

SmallRye GraphQL 客户端

本指南介绍如何在 Quarkus 应用中使用 GraphQL 客户端库。该客户端由 SmallRye GraphQL 项目实现。本文专注于客户端;如果需要了解 GraphQL 查询语言、基本概念及服务端开发,请先阅读 SmallRye GraphQL 指南。

我们将逐步开发并运行一个简单应用,使用两种受支持的 GraphQL 客户端,从远程的《星球大战》数据库中读取数据。可以先打开该服务的网页界面,手工编写和执行 GraphQL 查询。

先决条件

完成本指南需要:

  • 约15分钟。
  • 一个 IDE。
  • 安装 JDK 17 或更高版本,并正确配置 JAVA_HOME。
  • Apache Maven 3.9.16。
  • 如果希望使用命令行工具,可选安装 Quarkus CLI。
  • 如果希望构建原生可执行文件,可选安装 Mandrel 或 GraalVM 并正确配置;如果使用容器构建原生可执行文件,则需要 Docker。

GraphQL 客户端类型

目前支持两种 GraphQL 客户端。

类型安全客户端很像为 GraphQL 端点调整过的 MicroProfile REST Client。客户端实例实际上是一个代理,可像普通 Java 对象一样调用,但内部会把调用转换为 GraphQL 操作。它直接使用领域模型类,操作的输入和输出对象会与 GraphQL 查询语言中的表示互相转换。

动态客户端则更接近 jakarta.ws.rs.client 包中的 Jakarta REST 客户端。它不需要领域模型类,而是操作 GraphQL 文档的抽象表示。这些文档通过领域专用语言(DSL)构建。交换的数据以抽象的 JsonObject 表示;如果有合适的模型类,也可以按需转换为具体模型对象。

类型安全客户端层次较高、更偏声明式,注重易用性;动态客户端层次较低、更偏命令式,代码略显繁琐,但可以更精细地控制操作与响应。

完整示例

建议按照接下来的步骤逐步创建应用,也可以直接查看已完成的示例。

克隆 Git 仓库:

git clone https://github.com/quarkusio/quarkus-quickstarts.git

或者下载归档文件。完整示例位于 microprofile-graphql-client-quickstart 目录。

创建 Maven 项目

首先创建一个新项目。可以使用以下命令:

Quarkus CLI

quarkus create app org.acme:microprofile-graphql-client-quickstart \
    --extension='rest-jsonb,graphql-client' \
    --no-code
cd microprofile-graphql-client-quickstart

要创建 Gradle 项目,可增加 --gradle 或 --gradle-kotlin-dsl 选项。安装和使用说明参见 Quarkus CLI 指南。

Maven

mvn io.quarkus.platform:quarkus-maven-plugin:3.40.1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=microprofile-graphql-client-quickstart \
    -Dextensions='rest-jsonb,graphql-client' \
    -DnoCode
cd microprofile-graphql-client-quickstart

要创建 Gradle 项目,可增加 -DbuildTool=gradle 或 -DbuildTool=gradle-kotlin-dsl 选项。

该命令会生成项目并引入 smallrye-graphql-client 和 rest-jsonb 扩展。这里也需要后者,因为我们会把 REST 端点作为入口,手工触发 GraphQL 客户端执行操作。

如果已有配置好的 Quarkus 项目,可以在项目根目录执行以下命令,添加 smallrye-graphql-client 扩展:

Quarkus CLI

quarkus extension add graphql-client

Maven

./mvnw quarkus:add-extension -Dextensions='graphql-client'

Gradle

./gradlew addExtension --extensions='graphql-client'

构建文件中将加入以下内容:

pom.xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-graphql-client</artifactId>
</dependency>

build.gradle

implementation("io.quarkus:quarkus-smallrye-graphql-client")

应用要完成的工作

应用会使用两种 GraphQL 客户端连接 SWAPI,查询《星球大战》电影列表,以及每部电影中出现的行星名称。对应查询如下:

{
  allFilms {
    films {
      title
      planetConnection {
        planets {
          name
        }
      }
    }
  }
}

可以在服务网页上手工执行这个查询。

使用类型安全客户端

使用类型安全客户端需要与 schema 兼容的模型类。有两种获取方式。第一种是使用 SmallRye GraphQL 提供的客户端生成器,它根据 schema 文档与查询列表生成模型类。该生成器目前仍具有很强的实验性质,本例不展开介绍;感兴趣的话,可查看 Client Generator 及其文档。

本例将手工创建简化模型,只保留需要的字段。我们需要 Film 和 Planet 类。服务还使用 FilmConnection 和 PlanetConnection 包装类型;这里它们分别只负责容纳 Film 与 Planet 实例的列表。

创建这些模型类,并放入 org.acme.microprofile.graphql.client.model 包:

public class FilmConnection {

    private List<Film> films;

    public List<Film> getFilms() {
        return films;
    }

    public void setFilms(List<Film> films) {
        this.films = films;
    }
}

public class Film {

    private String title;

    private PlanetConnection planetConnection;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public PlanetConnection getPlanetConnection() {
        return planetConnection;
    }

    public void setPlanetConnection(PlanetConnection planetConnection) {
        this.planetConnection = planetConnection;
    }
}

public class PlanetConnection {

    private List<Planet> planets;

    public List<Planet> getPlanets() {
        return planets;
    }

    public void setPlanets(List<Planet> planets) {
        this.planets = planets;
    }

}

public class Planet {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

模型类准备好后,可以创建接口,表示需要在远程 GraphQL 服务上调用的操作:

@GraphQLClientApi(configKey = "star-wars-typesafe")
public interface StarWarsClientApi {

    FilmConnection allFilms();

}

为简单起见,我们仅调用名为 allFilms 的查询,对应 Java 方法也命名为 allFilms。如果方法采用其他名称,就需要添加 @Query(value="allFilms") 注解,指定调用该方法时应执行的查询名称。

客户端至少还需要配置远程服务的 URL。可以通过 @GraphQLClientApi 注解的 endpoint 参数指定,也可以写入 application.properties:

quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/graphql

如果需要添加认证请求头或其他自定义 HTTP 请求头(本例不需要),也可以在配置文件中设置:

quarkus.smallrye-graphql-client.star-wars-typesafe.header.HEADER-KEY=HEADER-VALUE

star-wars-typesafe 是客户端实例的配置名称,对应 @GraphQLClientApi 注解中的 configKey。如果不需要自定义名称,可以省略 configKey,改用接口的完全限定名称引用该客户端。

客户端配置完成后,还需要在应用启动后触发它工作。这里通过一个 REST 端点实现:用户访问该端点时,端点获取客户端实例并执行查询。

@Path("/")
public class StarWarsResource {
    @Inject
    StarWarsClientApi typesafeClient;

    @GET
    @Path("/typesafe")
    @Produces(MediaType.APPLICATION_JSON)
    @Blocking
    public List<Film> getAllFilmsUsingTypesafeClient() {
        return typesafeClient.allFilms().getFilms();
    }
}

将该 REST 端点加入应用后,向 /typesafe 发送 GET 请求即可。应用会使用注入的类型安全客户端调用远程服务,取得电影和行星数据,再把结果列表以 JSON 形式返回。

日志

为了调试,可以把 io.smallrye.graphql.client 类别的日志级别改为 TRACE,记录类型安全客户端生成的请求及服务器返回的响应。日志配置详情参见日志指南。

在 application.properties 中加入:

quarkus.log.category."io.smallrye.graphql.client".level=TRACE
quarkus.log.category."io.smallrye.graphql.client".min-level=TRACE

使用动态客户端

动态客户端可以操作 GraphQL 类型与文档的抽象表示,因此模型类是可选的,也完全不需要客户端 API 接口。

仍需配置客户端 URL,在 application.properties 中加入:

quarkus.smallrye-graphql-client.star-wars-dynamic.url=https://swapi-graphql.netlify.app/graphql

这里把客户端命名为 star-wars-dynamic,注入动态客户端时会用这个名称限定注入点。

如果需要认证请求头或其他自定义 HTTP 请求头(本例不需要),可以这样设置:

quarkus.smallrye-graphql-client.star-wars-dynamic.header.HEADER-KEY=HEADER-VALUE

把以下代码加入前面创建的 StarWarsResource:

import static io.smallrye.graphql.client.core.Document.document;
import static io.smallrye.graphql.client.core.Field.field;
import static io.smallrye.graphql.client.core.Operation.operation;

// ....

@Inject
@GraphQLClient("star-wars-dynamic")    // (1)
DynamicGraphQLClient dynamicClient;

@GET
@Path("/dynamic")
@Produces(MediaType.APPLICATION_JSON)
@Blocking
public List<Film> getAllFilmsUsingDynamicClient() throws Exception {
    Document query = document(   // (2)
        operation(
            field("allFilms",
                field("films",
                    field("title"),
                    field("planetConnection",
                        field("planets",
                            field("name")
                        )
                    )
                )
            )
        )
    );
    Response response = dynamicClient.executeSync(query);   // (3)
    return response.getObject(FilmConnection.class, "allFilms").getFilms();  // (4)
}
编号 说明
1 限定注入点,明确这里应注入哪个命名客户端。
2 使用提供的 DSL 构建代表 GraphQL 查询的文档。静态导入提高了可读性;DSL 的设计使其看起来很接近直接书写 GraphQL 查询字符串。
3 执行查询并阻塞等待响应。另有返回 Uni<Response> 的异步版本。
4 这里已有模型类,因此选择把响应转换为模型实例。这一步不是必需的;如果没有模型类或不想使用,只需调用 response.getData(),即可得到表示全部返回数据的 JsonObject。

运行应用

使用开发模式启动应用:

Quarkus CLI

quarkus dev

Maven

./mvnw quarkus:dev

Gradle

./gradlew --console=plain quarkusDev

向 REST 端点发送 GET 请求以执行查询:

curl -s http://localhost:8080/dynamic # to use the dynamic client
curl -s http://localhost:8080/typesafe # to use the typesafe client

无论使用动态客户端还是类型安全客户端,结果都应相同。如果 JSON 文档不便阅读,可以使用格式化工具,例如通过管道把输出交给 jq。

结语

本例展示了如何使用动态和类型安全两种 GraphQL 客户端调用外部 GraphQL 服务,并说明了两种客户端的区别。

参考资料

来源:SmallRye GraphQL Client,Quarkus 社区。本文为中文翻译,保留官方页面示例的 Quarkus 3.40.1、Maven 3.9.16 与 JDK 17+ 要求。页面标示 CC BY 3.0;源代码仓库采用 Apache License 2.0。代码中的原注释编号改用合法 Java 注释呈现。

Apache License 2.0 原文
                                 Apache License
                           Version 2.0, January 2004
                        https://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       https://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容