1. Introduction
1.导言
GraphQL is a powerful query language for APIs and provides a flexible and efficient way to interact with our data. When dealing with mutations, it’s typical to perform updates or additions to data on the server. However, in some scenarios, we might need to mutate without returning any data.
GraphQL是一种功能强大的 API 查询语言,为我们提供了一种灵活高效的数据交互方式。在处理突变时,通常是在服务器上进行数据更新或添加。但是,在某些场景中,我们可能需要在不返回任何数据的情况下进行突变。
In GraphQL, the default behavior is to enforce non-nullability for fields in the schema, meaning that a field must always return a value and cannot be null unless explicitly marked as nullable. While this strictness contributes to the clarity and predictability of the API, there are instances where returning null might be necessary. However, it’s generally considered a best practice to avoid returning null values.
在 GraphQL 中,默认行为是对模式中的字段强制执行非空性,这意味着字段必须始终返回一个值,除非明确标记为可空,否则不能为空。虽然这种严格性有助于提高 API 的清晰度和可预测性,但在某些情况下,返回空值可能是必要的。不过,避免返回空值通常被认为是一种最佳做法。
In this article, we’ll explore techniques for implementing GraphQL mutations without retrieving or returning specific information.
在本文中,我们将探讨在不检索或返回特定信息的情况下实现 GraphQL 突变的技术。
2. Prerequisites
2.先决条件
For our example, we’ll need the following dependencies:
在我们的示例中,我们需要以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The Spring Boot GraphQL Starter provides an excellent solution for quickly setting up a GraphQL server. By leveraging auto-configuration and adopting an annotation-based programming approach, we only need to focus on writing the essential code for our service.
Spring Boot GraphQL Starter 为快速设置 GraphQL 服务器提供了出色的解决方案。通过利用自动配置和采用基于注解的编程方法,我们只需专注于编写服务的基本代码。
We’ve included the web starter in our config because GraphQL is transport-agnostic. This utilizes Spring MVC to expose the GraphQL API over HTTP. We can access this via the default /graphql endpoint. We can also use other starters, like Spring Webflux, for different underlying implementations.
我们在配置中包含了 web starter,因为 GraphQL 与传输无关。它利用 Spring MVC 通过 HTTP 公开 GraphQL API。我们可以通过默认的 /graphql 端点访问它。我们还可以使用其他启动器,如 Spring Webflux 来实现不同的底层实现。
3. Using Nullable Type
3.使用可归零类型
Unlike some programming languages, GraphQL mandates an explicit declaration of nullability for each field in the schema. This approach enhances clarity, allowing us to convey when a field may lack value.
与某些编程语言不同,GraphQL 规定模式中的每个字段都要明确声明可空性。这种方法提高了清晰度,使我们能够表达字段何时可能缺值。
3.1. Writing the Schema
3.1.编写模式
The Spring Boot GraphQL starter automatically locates GraphQL Schema files under the src/main/resources/graphql/** location. It builds the correct structure based on them, and wires special beans to this structure.
Spring Boot GraphQL 启动程序会自动定位 src/main/resources/graphql/** 位置下的 GraphQL 模式文件。它会根据这些文件构建正确的结构,并将特殊 Bean 连接到此结构。
We’ll start by creating the schema.graphqls file, and defining the schema for our example:
首先,我们将创建 schema.graphqls 文件,并为我们的示例定义模式:
type Post {
id: ID
title: String
text: String
category: String
author: String
}
type Mutation {
createPostReturnNullableType(title: String!, text: String!, category: String!, authorId: String!) : Int
}
We’ll have a Post entity and a mutation to create a new post. Also, for our schema to pass validation, it must have a query. So, we’ll implement a dummy query that returns a list of posts:
我们将有一个 Post 实体和一个用于创建新帖的突变。此外,为了使我们的模式通过验证,它必须有一个查询。因此,我们将实现一个虚拟查询,返回一个帖子列表:
type Query {
recentPosts(count: Int, offset: Int): [Post]!
}
3.2. Using Beans to Represent Types
3.2.使用 Bean 表示类型
In the GraphQL server, every complex type is associated with a Java bean. These associations are established based on the object and property names. That being said, we’ll create a POJO class for our posts:
在 GraphQL 服务器中,每个复杂类型都与一个 Java Bean 关联。这些关联是根据对象和属性名称建立的。也就是说,我们将为我们的帖子创建一个 POJO 类:
public class Post {
private String id;
private String title;
private String text;
private String category;
private String author;
// getters, setters, constructor
}
Unmapped fields or methods on the Java bean are overlooked within the GraphQL schema, posing no issues.
Java Bean 上未映射的字段或方法会在 GraphQL 模式中被忽略,不会造成任何问题。
3.3. Creating the Mutation Resolver
3.3.创建突变解析器
We must mark the handler functions with the @MutationMapping tag. These methods should be placed within regular @Controller components in our application, registering the classes as data-modifying components in our GraphQL application:
我们必须使用 @MutationMapping 标记处理函数。这些方法应放置在应用程序中的常规 @Controller 组件中,将这些类注册为 GraphQL 应用程序中的数据修改组件:
@Controller
public class PostController {
List<Post> posts = new ArrayList<>();
@MutationMapping
public Integer createPost(@Argument String title, @Argument String text, @Argument String category, @Argument String author) {
Post post = new Post();
post.setId(UUID.randomUUID().toString());
post.setTitle(title);
post.setText(text);
post.setCategory(category);
post.setAuthor(author);
posts.add(post);
return null;
}
}
We must annotate the parameters of the method with @Argument according to the properties from the schema. When declaring the schema, we determined that our mutation would return an Int type, without the exclamation mark. This allowed the return value to be null.
我们必须根据模式中的属性用 @Argument 对方法的参数进行注解。在声明模式时,我们确定我们的突变将返回一个 Int 类型,不带感叹号。这样,返回值就可以是 空。
4. Creating Custom Scalar
4.创建自定义标量
4.1. Scalars and Extended Scalars
4.1.标量和扩展标量
According to the GraphQL specification, all implementations must include the following scalar types: String, Boolean, Int, Float, or ID. Besides that, graphql-java-extended-scalars adds more custom-made scalars like Long, BigDecimal, or LocalDate. However, neither the original nor the extended set of scalars have a special one for the null value. So, we’ll build our scalar in this section.
根据 GraphQL 规范,所有实现都必须包含以下标量类型:String, Boolean, Int, Float, 或 ID.除此之外,graphql-java-extended-scalars 还添加了更多自定义标量,如 Long、BigDecimal 或 LocalDate。然而,原始标量和扩展标量集都没有为 null 值提供特殊标量。 因此,我们将在本节中创建我们的标量。
4.2. Creating the Custom Scalar
4.2.创建自定义标量
To create a custom scalar, we should initialize a GraphQLScalarType singleton instance. We’ll utilize the Builder design pattern to create our scalar:
要创建自定义标量,我们应初始化一个 GraphQLScalarType 单例实例。我们将使用 Builder 设计模式来创建标量:
public class GraphQLVoidScalar {
public static final GraphQLScalarType Void = GraphQLScalarType.newScalar()
.name("Void")
.description("A custom scalar that represents the null value")
.coercing(new Coercing() {
@Override
public Object serialize(Object dataFetcherResult) {
return null;
}
@Override
public Object parseValue(Object input) {
return null;
}
@Override
public Object parseLiteral(Object input) {
return null;
}
})
.build();
}
The key components of the scalar are name, description, and coercing. Although the name and description are self-explanatory, the hard part of creating a custom scalar is graphql.schema.Coercing implementation. This class is responsible for three functions:
虽然名称和描述不言自明,但创建自定义标量的难点在于 graphql.schema.Coercing 的实现。该类负责三个功能:
- parseValue(): accepts a variable input object and transforms it into the corresponding Java runtime representation
- parseLiteral(): receives an AST literal graphql.language.Value as input and transform it into the Java runtime representation
- serialize(): accepts a Java object and converts it into the output shape for that scalar
Although the implementation of coercing can be quite complicated for a complex object, in our case, we’ll return null for each method.
虽然对于复杂对象来说,强制的实现可能相当复杂,但在我们的例子中,我们将为每个方法返回 null 。
4.3. Register the Custom Scalar
4.3.注册自定义标量
We’ll start by creating a configuration class where we register our scalar:
我们先创建一个配置类,在其中注册我们的标量:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> wiringBuilder.scalar(GraphQLVoidScalar.Void);
}
}
We create a RuntimeWiringConfigurer bean where we configure the runtime wiring for our GraphQL schema. In this bean, we use the scalar() method provided by the RuntimeWiring class to register our custom type.
我们创建一个 RuntimeWiringConfigurer bean 来配置 GraphQL 模式的运行时布线。在该 Bean 中,我们使用 RuntimeWiring 类提供的 scalar() 方法来注册自定义类型。
4.4. Integrate the Custom Scalar
4.4.整合自定义标量
The final step is to integrate the custom scalar into our GraphQL schema by referencing it using the defined name. In this case, we use the scalar in the schema by simply declaring scalar Void.
最后一步是使用定义的名称将自定义标量引用到我们的 GraphQL 模式中。在本例中,我们只需声明 scalar Void,即可在模式中使用标量。
This step ensures that the GraphQL engine recognizes and utilizes our custom scalar throughout the schema. Now, we can integrate the scalar into our mutation:
这一步可确保 GraphQL 引擎在整个模式中识别并使用我们的自定义标量。现在,我们可以将标量集成到我们的突变中:
scalar Void
type Mutation {
createPostReturnCustomScalar(title: String!, text: String!, category: String!, authorId: String!) : Void
}
Also, we’ll update the mapped method signature to return our scalar:
此外,我们还将更新映射方法的签名,以返回标量:
public Void createPostReturnCustomScalar(@Argument String title, @Argument String text, @Argument String category, @Argument String author)
5. Conclusion
5.结论
In this article, we explored implementing GraphQL mutations without returning specific data. We demonstrated setting up a server quickly with the Spring Boot GraphQL Starter. Furthermore, we introduced a custom Void scalar to handle null values, showcasing how to extend GraphQL’s capabilities.
在本文中,我们探讨了如何在不返回特定数据的情况下实现 GraphQL 突变。我们演示了如何使用 Spring Boot GraphQL Starter 快速设置服务器。此外,我们还引入了自定义 Void 标量来处理 null 值,展示了如何扩展 GraphQL 的功能。
As always, the complete code snippets are available over on GitHub.
一如既往,完整的代码片段可在 GitHub 上获取。