编辑此页面

SmallRye GraphQL 客户端

本指南演示了您的 Quarkus 应用程序如何使用 GraphQL 客户端库。该客户端由 SmallRye GraphQL 项目实现。本指南专门针对客户端,因此如果您需要 GraphQL 的通用介绍,请先参考 SmallRye GraphQL 指南,其中提供了 GraphQL 查询语言、通用概念和服务器端开发的介绍。

本指南将引导您开发和运行一个简单的应用程序,该应用程序使用两种支持的 GraphQL 客户端类型从远程资源(一个与 Star Wars 相关联的数据库)检索数据。如果您想手动尝试,可以在 此网页上找到它。Web UI 允许您编写和执行 GraphQL 查询。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

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 项目

首先,我们需要一个新项目。使用以下命令创建一个新项目

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 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4: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 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数括在双引号中,例如 "-DprojectArtifactId=microprofile-graphql-client-quickstart"

此命令将生成一个项目,导入 smallrye-graphql-client 扩展,以及 rest-jsonb 扩展,因为我们也将使用它——REST 端点将是入口点,允许您手动触发 GraphQL 客户端工作。

如果您已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令将 smallrye-graphql-client 扩展添加到您的项目中

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")

如果您使用 JSON-B 和 JSON-P,请确保不要使用 jakarta.json.Json 提供的快捷方法,例如 Json.createValue(…​)

目前,对这些方法的任何一次调用都将初始化一个新的 JsonProvider,这非常慢。Quarkus 通过 quarkus-jsonp 扩展的 JsonProviderHolder 类提供一个共享的 JsonProvider 实例。

您可以将其导入为静态导入以简化您的代码

import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider;

[...]

    public void method() {
        jsonProvider().createValue("value");
    }

[...]

应用程序

我们将构建的应用程序利用了两种类型的 GraphQL 客户端。在这两种情况下,它们都将连接到 SWAPI 上的 Star Wars 服务,并查询该服务以获取 Star Wars 电影列表,然后,对于每部电影,获取其中出现的行星的名称。

相应的 GraphQL 查询如下所示

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

您可以访问网页手动执行此查询。

使用类型安全客户端

要使用类型安全客户端,我们需要与架构兼容的相应模型类。有两种方法可以获得它们。第一种是使用 SmallRye GraphQL 提供的客户端生成器,它从架构文档和查询列表中生成类。此生成器目前被认为是高度实验性的,在本示例中未涵盖。如果您感兴趣,请参考客户端生成器及其文档。

在此示例中,我们将手动创建一个精简版模型类,只包含我们需要的字段,并忽略所有我们不需要的东西。我们将需要 FilmPlanet 的类。但是,该服务还使用名为 FilmConnectionPlanetConnection 的特定包装器,这些包装器对于我们的目的来说,将仅用于包含实际的 FilmPlanet 实例列表。

让我们创建所有模型类并将它们放入 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 的查询。我们也相应地命名了我们的方法 allFilms。如果我们命名了不同的方法,则需要用 @Query(value="allFilms") 注释它,以指定调用此方法时应执行的查询名称。

客户端还需要一些配置,即至少需要远程服务的 URL。我们可以通过设置 endpoint 参数在 @GraphQLClientApi 注释中指定它,或者将其移至配置文件 application.properties

quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/graphql
仅在测试期间,URL 是一个可选属性,如果未指定,Quarkus 将假定客户端的目标是正在测试的应用程序(通常是 https://:8081/graphql)。如果您应用程序包含 GraphQL 服务器端 API 以及用于测试该 API 的 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,代表所有返回的数据。

运行应用程序

使用以下命令在开发模式下启动应用程序

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

要执行查询,您需要向我们的 REST 端点发送 GET 请求

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

无论您使用动态还是类型安全客户端,结果都应相同。如果 JSON 文档难以阅读,您可能希望将其通过一个可以格式化它的工具进行处理,以便人类更容易阅读,例如通过将输出通过 jq 进行管道传输。

结论

本示例展示了如何使用动态和类型安全 GraphQL 客户端调用外部 GraphQL 服务,并解释了客户端类型之间的区别。

相关内容

关于相同主题