Contents

GraphQL简介

1. 概述

GraphQL 是一种查询语言,由 Facebook 创建,旨在基于直观和灵活的语法构建客户端应用程序,用于描述其数据需求和交互。

传统 REST 调用的主要挑战之一是客户端无法请求自定义(有限或扩展)数据集。在大多数情况下,一旦客户端向服务器请求信息,它要么获取所有字段,要么不获取任何字段。

另一个困难是工作和维护多个端点。随着平台的发展,数量也会随之增加。因此,客户端经常需要从不同的端点请求数据。

在构建 GraphQL 服务器时,只需要一个 URL 用于所有数据获取和变异。因此,客户端可以通过向服务器发送一个描述他们想要什么的查询字符串来请求一组数据。

2. 基本 GraphQL 命名法

让我们看一下 GraphQL 的基本术语。

  • **Query:**是向 GraphQL 服务器请求的只读操作
  • **Mutation:**是向 GraphQL 服务器请求的读写操作
  • **Resolver:**在 GraphQL 中,Resolver负责映射操作和后端运行的代码,后端负责处理请求。它类似于 RESTFul 应用程序中的 MVC 后端
  • **Type:**Type 定义了可以从 GraphQL 服务器返回的响应数据的形状,包括作为其他Types边缘的字段
  • **Input:**类似于Type,但定义了发送到 GraphQL 服务器的输入数据的形状
  • **Scalar:**是原始Type,例如StringIntBooleanFloat
  • **Interface:**接口将存储字段的名称及其参数,因此 GraphQL 对象可以从中继承,确保使用特定字段
  • **Schema:**在 GraphQL 中,Schema 管理查询和突变,定义允许在 GraphQL 服务器中执行的内容

2.1. 模式加载

将模式加载到 GraphQL 服务器有两种方法:

  1. 通过使用 GraphQL 的接口定义语言 (IDL)
  2. 通过使用一种受支持的编程语言

让我们演示一个使用 IDL 的示例:

type User {
    firstName: String
}

现在,使用 Java 代码定义模式的示例:

GraphQLObjectType userType = newObject()
  .name("User")  
  .field(newFieldDefinition()
    .name("firstName")
    .type(GraphQLString))
  .build();

3.接口定义语言

接口定义语言 (IDL) 或模式定义语言 (SDL) 是指定 GraphQL 模式的最简洁方式。语法定义明确,将在官方 GraphQL 规范中采用。

例如,让我们为用户/电子邮件创建一个 GraphQL 模式,可以这样指定:

schema {
    query: QueryType
}
enum Gender {
    MALE
    FEMALE
}
type User {
    id: String!
    firstName: String!
    lastName: String!
    createdAt: DateTime!
    age: Int! @default(value: 0)
    gender: [Gender]!
    emails: [Email!]! @relation(name: "Emails")
}
type Email {
    id: String!
    email: String!
    default: Int! @default(value: 0)
    user: User @relation(name: "Emails")
}

4. GraphQL-java

GraphQL-java 是基于规范和JavaScript 参考实现 的实现。请注意,它至少需要 Java 8 才能正常运行。

4.1. GraphQL-java 注释

GraphQL 还可以使用Java 注释 来生成其模式定义,而无需使用传统 IDL 方法创建的所有样板代码。

4.2. 依赖项

为了创建我们的示例,让我们首先开始导入依赖于Graphql-java-annotations 模块的所需依赖项:

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-annotations</artifactId>
    <version>3.0.3</version>
</dependency>

我们还实现了一个 HTTP 库来简化我们应用程序中的设置。我们将使用Ratpack (尽管它也可以使用 Vert.x、Spark、Dropwizard、Spring Boot 等来实现)。

让我们也导入 Ratpack 依赖项:

<dependency>
    <groupId>io.ratpack</groupId>
    <artifactId>ratpack-core</artifactId>
    <version>1.4.6</version>
</dependency>

4.3. 执行

让我们创建我们的示例:一个为用户提供“CRUDL”(创建、检索、更新、删除和列表)的简单 API。首先,让我们创建我们的用户POJO:

@GraphQLName("user")
public class User {
    @GraphQLField
    private Long id;
 
    @GraphQLField
    private String name;
 
    @GraphQLField
    private String email;
  
    // getters, setters, constructors, and helper methods omitted
}

在这个 POJO 中,我们可以看到*@GraphQLName(“user”)注解,表明这个类是由 GraphQL 映射的,每个字段都用@GraphQLField* 注解。

接下来,我们将创建UserHandler类。此类从所选的 HTTP 连接器库(在我们的示例中为 Ratpack)继承一个处理程序方法,该方法将管理和调用 GraphQL 的解析器功能。因此,将请求(JSON 有效负载)重定向到正确的查询或变异操作:

@Override
public void handle(Context context) throws Exception {
    context.parse(Map.class)
      .then(payload -> {
          Map<String, Object> parameters = (Map<String, Object>)
            payload.get("parameters");
          ExecutionResult executionResult = graphql
            .execute(payload.get(SchemaUtils.QUERY)
              .toString(), null, this, parameters);
          Map<String, Object> result = new LinkedHashMap<>();
          if (executionResult.getErrors().isEmpty()) {
              result.put(SchemaUtils.DATA, executionResult.getData());
          } else {
              result.put(SchemaUtils.ERRORS, executionResult.getErrors());
              LOGGER.warning("Errors: " + executionResult.getErrors());
          }
          context.render(json(result));
      });
}

现在,将支持查询操作的类,即UserQuery。如前所述,从服务器检索数据到客户端的所有方法都由此类管理:

@GraphQLName("query")
public class UserQuery {
    @GraphQLField
    public static User retrieveUser(
     DataFetchingEnvironment env,
      @NotNull @GraphQLName("id") String id) {
        // return user
    }
    
    @GraphQLField
    public static List<User> listUsers(DataFetchingEnvironment env) {
        // return list of users
    }
}

UserQuery类似,现在我们创建UserMutation,它将管理所有打算更改存储在服务器端的给定数据的操作:

@GraphQLName("mutation")
public class UserMutation {
 
    @GraphQLField
    public static User createUser(
      DataFetchingEnvironment env,
      @NotNull @GraphQLName("name") String name,
      @NotNull @GraphQLName("email") String email) {
      //create user information
    }
}

值得注意的是UserQueryUserMutation类中的注解:@GraphQLName(“query”)@GraphQLName(“mutation”)。这些注释分别用于定义查询和变异操作。

由于 GraphQL-java 服务器能够运行查询和变异操作,我们可以使用以下 JSON 有效负载来测试客户端对服务器的请求:

  • 对于 CREATE 操作:
{
    "query": "mutation($name: String! $email: String!){
       createUser (name: $name email: $email) { id name email age } }",
    "parameters": {
        "name": "John",
        "email": "john@example.com"
     }
}

作为服务器对此操作的响应:

{
    "data": {
        "createUser": {
            "id": 1,
            "name": "John",
            "email": "john@example.com"
        }
    } 
}
  • 对于 RETRIEVE 操作:
{
    "query": "query($id: String!){ retrieveUser (id: $id) {name email} }",
    "parameters": {
        "id": 1
    }
}

作为服务器对此操作的响应:

{
    "data": {
        "retrieveUser": {
            "name": "John",
            "email": "john@example.com"
        }
    }
}

GraphQL 提供了客户端可以自定义响应的功能。因此,在用作示例的最后一个 RETRIEVE 操作中,我们可以仅返回电子邮件,而不是返回姓名和电子邮件:

{
    "query": "query($id: String!){ retrieveUser (id: $id) {email} }",
    "parameters": {
        "id": 1
    }
}

因此,来自 GraphQL 服务器的返回信息只会返回请求的数据:

{
    "data": {
        "retrieveUser": {
            "email": "john@example.com"
        }
    }
}