超音速 亚原子 GraphQL
刚刚发布的 Quarkus 1.5.0 版本已包含 MicroProfile GraphQL。
什么是 GraphQL?
“GraphQL 是一种用于 API 的开源数据查询和操作语言,也是通过现有数据满足查询的运行时。GraphQL 解析来自客户端的字符串,并以一种可理解的、可预测的、预定义的方式返回数据。GraphQL 是 REST 的一种替代方案,但不一定是替代品。”
阅读完整的 GraphQL 规范
为什么选择 GraphQL?
使用 GraphQL 的主要原因包括:
-
避免过度或不足地获取数据。客户端可以在单个请求中检索多种类型的数据,或者根据特定标准限制响应数据。
-
支持数据模型演进。可以修改模式,而无需更改现有客户端,反之亦然——这可以在不更新应用程序版本的情况下完成。
-
高级开发人员体验
-
模式定义了如何访问数据,并作为客户端和服务器之间的契约。双方的开发团队都可以独立工作。
-
原生模式内省使用户能够发现 API 并优化客户端查询。借助 GraphiQL 和 GraphQL Voyager 等图形工具,可以更顺畅、更轻松地发现 API,从而进一步增强此优势。
-
在客户端,查询语言提供了灵活性和效率,使开发人员能够适应其技术环境的限制,同时减少服务器往返次数。
-
什么是 MicroProfile GraphQL?
“MicroProfile GraphQL 规范的目的是提供一组“代码优先”的 API,使用户能够快速开发可移植的基于 GraphQL 的 Java 应用程序。该规范的所有实现都有 2 个主要要求,即:
生成并提供 GraphQL 模式。这通过查看用户代码中的注释来完成,并且必须包含所有 GraphQL 查询和突变,以及通过查询和突变响应类型或参数隐式定义的所有实体。
执行 GraphQL 请求。这可以是查询或突变的形式。至少,该规范必须支持通过 HTTP 执行这些请求。”
阅读完整的 MicroProfile GraphQL 规范
您现在可以使用 code.quarkus.io 开始使用 Quarkus 并包含 SmallRye GraphQL 扩展。

这将创建一个 Quarkus 启动应用程序,其中包含以下依赖项:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
目前,创建的示例应用程序是一个 JAX-RS 应用程序。有一些 正在进行的工作允许扩展定义自定义示例应用程序,但在此之前,我们始终获得 JAX-RS 应用程序。您可以删除 |
您的第一个 GraphQL 端点。
让我们将 ExampleResource
REST 服务更改为 GraphQL 端点。
-
将
@Path("/hello")
类注解替换为@GraphQLApi
。 -
将
@GET
方法注解替换为@Query
。 -
删除
@Produces(MediaType.TEXT_PLAIN)
方法注解和所有 JAX-RS 导入。
就是这样!您的 ExampleResource
现在应该如下所示:
package org.acme;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Query;
@GraphQLApi
public class ExampleResource {
@Query
public String hello() {
return "hello";
}
}
您现在可以使用 Quarkus dev 模式运行该应用程序。
mvn quarkus:dev
现在访问 localhost:8080/graphql-ui/ 并运行以下查询:
{
hello
}
这将返回:
{
"data": {
"hello": "hello"
}
}
另请参阅 MicroProfile GraphQL 指南。
更详细的示例
让我们看一个更详细的示例,从 这个 GitHub 项目获取源代码。
这是一个多模块应用程序。首先编译所有模块。在根目录:
mvn clean install
现在浏览到 quarkus 示例。
cd quarkus-example
查看标记为 @GraphQLApi
的 ProfileGraphQLApi.java
。
@Query("person")
public Person getPerson(@Name("personId") int personId){
return personDB.getPerson(personId);
}
上面的方法将根据 personId
获取一个人。如您所见,该方法可以通过 @Query
注解使其可查询。您可以选择提供名称(在本例中为“person”),但默认情况下也将是“person”(方法名不带“get”)。您也可以选择命名参数,但默认情况下将是参数名(“personId”)。
Person 对象是一个 POJO,代表系统中的一个人(用户或成员)。它有许多字段,其中一些是其他复杂的 POJO。

但是,Query
注解使得精确查询我们感兴趣的字段成为可能。
运行示例应用程序。
mvn quarkus:dev
现在访问 localhost:8080/graphql-ui/ 并运行以下查询:
{
person(personId:1){
names
surname
scores{
name
value
}
}
}
请注意,编辑器中具有“代码提示”。这是因为 GraphQL 具有模式,并且还支持内省。
我们可以只请求我们感兴趣的字段,从而大大减小有效负载。

我们还可以组合查询,例如,假设我们要获取上面所示的 person 1 的字段,以及 person 2 的姓名和姓氏,我们可以执行以下操作:
{
person1: person(personId:1){
names
surname
scores{
name
value
}
}
person2: person(personId:2){
names
surname
}
}
这将返回:
{
"data": {
"person1": {
"names": [
"Christine",
"Fabian"
],
"surname": "O'Reilly",
"scores": [
{
"name": "Driving",
"value": 15
},
{
"name": "Fitness",
"value": 94
},
{
"name": "Activity",
"value": 63
},
{
"name": "Financial",
"value": 22
}
]
},
"person2": {
"names": [
"Masako",
"Errol"
],
"surname": "Zemlak"
}
}
}
源字段
仔细查看我们的查询,您会发现我们询问了 person 的 scores
字段,但是 Person
POJO 不包含 scores
字段。我们通过向 person 添加 @Source
字段来添加 scores
字段。
@Query("person")
public Person getPerson(@Name("personId") int personId){
return personDB.getPerson(personId);
}
public List<Score> getScores(@Source Person person) {
return scoreDB.getScores(person.getIdNumber());
}
因此,我们可以通过添加与响应类型匹配的 @Source
参数来添加合并到输出中的字段。
部分结果
上面的示例合并了两个不同的数据源,但假设分数系统已关闭。我们仍然会返回我们拥有的数据,并为分数返回一个错误。
{
"errors": [
{
"message": "Scores for person [797-95-4822] is not available",
"locations": [
{
"line": 5,
"column": 5
}
],
"path": [
"person",
"scores2"
],
"extensions": {
"exception": "com.github.phillipkruger.user.graphql.ScoresNotAvailableException",
"classification": "DataFetchingException"
}
}
],
"data": {
"person": {
"names": [
"Christine",
"Fabian"
],
"surname": "O'Reilly",
"scores2": null
}
}
}
原生模式
让我们在原生模式下运行此示例(使用 graalvm-ce-java11-19.3.2)。
mvn -Pnative clean install
这将创建一个原生可执行文件,然后非常快速地启动应用程序。
./target/quarkus-example-1.0.0-SNAPSHOT-runner
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) quarkus-example 1.0.0-SNAPSHOT native (powered by Quarkus 1.5.0.Final) started in 0.026s. Listening on: http://0.0.0.0:8080
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) Profile prod activated.
2020-06-11 17:02:55,041 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, smallrye-graphql, smallrye-openapi, swagger-ui]
正在计划中
这是 MicroProfile GraphQL 规范的第一个版本,并且有很多正在计划中。其中之一是客户端。我们正在提出两种类型的客户端:
动态
动态客户端允许您使用生成器构建查询。
// Building of the graphql document.
Document myDocument = document(
operation(Operation.Type.QUERY,
field("people",
field("id"),
field("name")
)));
// Serialization of the document into a string, ready to be sent.
String graphqlRequest = myDocument.toString();
更多详情请参阅:github.com/worldline/dynaql
类型安全
类型安全的客户端将更接近 MicroProfile RESTClient。以与上面相同的示例为例,看看我们如何使用它。从项目根目录,浏览到 quarkus-client
文件夹。此示例使用 Quarkus Command Mode 来执行查询。
客户端还不是 Quarkus 扩展,因此我们将其添加到项目中:
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-client</artifactId>
<version>${smallrye-graphql.version}</version>
</dependency>
现在我们可以创建一个只包含我们感兴趣的字段的 POJO。查看客户端模块中的 Person
和 Score
,它比服务器端的定义小得多。

现在我们只需要添加一个定义我们感兴趣的查询的接口。
@GraphQlClientApi
public interface PersonGraphQLClient {
public Person person(int personId);
}
现在我们可以使用它。
//@Inject
//PersonGraphQLClient personClient; or
PersonGraphQLClient personClient = GraphQlClientBuilder.newBuilder().build(PersonGraphQLClient.class);
// ...
Person person = personClient.person(id);
运行 Quarkus 客户端应用程序,我们现在可以调用服务器(确保它仍在运行)并打印响应。
java -jar target/quarkus-client-1.0.0-SNAPSHOT-runner.jar 2
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=lcd
=========================
| Masako Zemlak |
| |
| Driving |
| 48 |
| |
| Fitness |
| 73 |
| |
| Activity |
| 62 |
| |
| Financial |
| 54 |
| |
=========================
数字(2)是我们示例中的 personId
。