SmallRye GraphQL Client
本指南展示了 Quarkus 应用程序如何使用 GraphQL 客户端库。客户端由 SmallRye GraphQL项目实现。本指南专门针对客户端,因此如果您需要了解 GraphQL 的一般知识,请首先参考SmallRye GraphQL guide,其中介绍了 GraphQL 查询语言、一般概念和服务器端开发。 本指南将指导您开发和运行一个简单的应用程序,该应用程序使用支持的两种 GraphQL 客户端类型从远程资源(与星球大战相关的数据库)中检索数据。如果您想手动对其进行试验,可以在 this webpage处找到它。Web UI 允许您针对它编写和执行 GraphQL 查询。
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
GraphQL client types introduction
支持两种类型的 GraphQL 客户端。
*typesafe*客户端的工作方式非常像针对调用 GraphQL 端点而调整的 MicroProfile REST Client。客户端实例基本上是一个代理,您可以像常规 Java 对象一样调用它,但在底层,调用将被转换为 GraphQL 操作。它直接与域类一起使用。用于该操作的任何输入和输出对象都将被转换为/从其在 GraphQL 查询语言中的表示形式。
另一方面,*dynamic*客户端的工作方式更像是来自`jakarta.ws.rs.client`包的 Jakarta REST 客户端的等效项。它不需要域类就能工作,而是使用 GraphQL 文档的抽象表示形式。文档使用特定于领域的语言 (DSL) 构建。交换的对象被视为抽象的`JsonObject`,但是,在必要时,可以将它们转换为具体的模型对象(如果存在合适的模型类)。
类型安全客户端可以被视为设计为易于使用的相当高级别的声明方法,而动态客户端则处于更低级别,更命令式,使用起来有些冗长,但允许对操作和响应进行更细粒度的控制。
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
解决方案位于`microprofile-graphql-client-quickstart` directory中。
Creating the Maven Project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个导入`smallrye-graphql-client`扩展的项目,也导入`rest-jsonb`扩展,因为我们也将使用它 - REST 端点将成为允许您手动触发 GraphQL 客户端来完成工作的入口点。
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令向项目添加`smallrye-graphql-client`扩展:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-graphql-client")
The application
我们将构建的应用程序使用这两种类型的 GraphQL 客户端。在这两种情况下,它们都将连接至 SWAPI处的星球大战服务并查询星球大战电影列表以及每部电影中出现的星球名称。
对应的 GraphQL 查询如下所示:
{
allFilms {
films {
title
planetConnection {
planets {
name
}
}
}
}
}
您可以访问 the webpage 手动执行此查询。
Using the Typesafe client
要使用类型安全的客户端,我们需要与模式兼容的相应模型类。获取它们的方法有两种。首先是使用 SmallRye GraphQL 提供的客户端生成器,该生成器从模式文档和查询列表生成类。此生成器目前被认为是高度实验性的,本示例中不介绍该生成器。如果您感兴趣,请参阅 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
的查询。我们也为相应的方法命名为 allFilms
。如果我们给方法命名不同,则需要使用 @Query(value="allFilms")
对其进行注释,以指定在调用此方法时应执行的查询的名称。
客户端还需要一些配置,即至少需要远程服务的 URL。我们可以指定在 @GraphQLClientApi
注释中(通过设置 endpoint
参数)指定该 URL,或将其移到配置文件 application.properties
中:
quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/.netlify/functions/index
在 tests only 期间,URL 是一个可选属性,如果未指定该属性,则 Quarkus 将假设客户端的目标是正在测试的应用程序(通常为 |
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 表示形式。
Logging
出于调试目的,可以通过将 io.smallrye.graphql.client
类别的日志级别更改为 TRACE
来记录类型安全客户端生成的请求和服务器发回的响应(有关如何配置日志记录的更多详细信息,请参阅 Logging guide)。
这可以通过向 application.properties
中添加以下行来实现:
quarkus.log.category."io.smallrye.graphql.client".level=TRACE
quarkus.log.category."io.smallrye.graphql.client".min-level=TRACE
Using the Dynamic client
对于动态客户端,模型类是可选的,因为我们可以使用 GraphQL 类型和文档的抽象表示。根本不需要客户端 API 接口。
我们仍然需要为客户端配置 URL,因此,让我们将此值放入 application.properties
中:
quarkus.smallrye-graphql-client.star-wars-dynamic.url=https://swapi-graphql.netlify.app/.netlify/functions/index
我们决定将客户端命名为 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 。 |
Running the application
使用以下命令在 dev 模式下启动应用程序:
quarkus dev
./mvnw quarkus:dev
./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
进行格式化,以使其更易于人类阅读。