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 服务,并说明了两种客户端的区别。
参考资料
暂无评论内容