超音速 亚原子 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 扩展

Code

这将创建一个 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 应用程序。您可以删除 quarkus-resteasy 依赖项,因为我们不需要 JAX-RS。

您的第一个 GraphQL 端点。

让我们将 ExampleResource REST 服务更改为 GraphQL 端点。

  1. @Path("/hello") 类注解替换为 @GraphQLApi

  2. @GET 方法注解替换为 @Query

  3. 删除 @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

查看标记为 @GraphQLApiProfileGraphQLApi.java

    @Query("person")
    public Person getPerson(@Name("personId") int personId){
        return personDB.getPerson(personId);
    }

上面的方法将根据 personId 获取一个人。如您所见,该方法可以通过 @Query 注解使其可查询。您可以选择提供名称(在本例中为“person”),但默认情况下也将是“person”(方法名不带“get”)。您也可以选择命名参数,但默认情况下将是参数名(“personId”)。

Person 对象是一个 POJO,代表系统中的一个人(用户或成员)。它有许多字段,其中一些是其他复杂的 POJO。

Person

但是,Query 注解使得精确查询我们感兴趣的字段成为可能。

运行示例应用程序。

mvn quarkus:dev

现在访问 localhost:8080/graphql-ui/ 并运行以下查询:

{
  person(personId:1){
    names
    surname
    scores{
      name
      value
    }
  }
}

请注意,编辑器中具有“代码提示”。这是因为 GraphQL 具有模式,并且还支持内省。

我们可以只请求我们感兴趣的字段,从而大大减小有效负载。

GraphiQL

我们还可以组合查询,例如,假设我们要获取上面所示的 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。查看客户端模块中的 PersonScore,它比服务器端的定义小得多。

Client

现在我们只需要添加一个定义我们感兴趣的查询的接口。

@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

总结

这是 MicroProfile GraphQL 的一个简短快速的介绍,它现在可以在 Quarkus 中使用。还有许多 功能,并且还在 计划中,所以敬请关注。