1. 引言
GraphQL 是由 Facebook 开源的一种用于从服务器检索数据的查询语言,可替代 REST、SOAP 或 gRPC。
在本文中,我们将学习如何使用 Spring Boot 设置 GraphQL 服务器,以便我们可以将其添加到现有应用程序或在新应用程序中使用它。
2. 什么是 GraphQL?
GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。
传统的 REST API 是围绕管理服务器管理的资源而构建的,通过标准 HTTP 谓词进行操作。虽然在此框架内有效,但当偏离该框架或客户端同时需要来自多个资源的数据时,它们会遇到困难,这通常会导致由于不必要的数据而导致更大的响应大小。
GraphQL 通过使客户端能够在单个查询中精确请求所需的数据、促进子资源的导航和一次启用多个查询来应对这些挑战。这种方法类似于 RPC(远程过程调用),强调命名查询和突变,使 API 开发人员和使用者能够有效地控制功能和预期结果。
例如,博客可能允许以下查询:
query {
recentPosts(count: 10, offset: 0) {
id
title
category
author {
id
name
thumbnail
}
}
}
此查询将:
-
请求最近的十个帖子
-
对于每个帖子,请求 ID、标题和类别
-
对于每个帖子,请求作者,返回 ID、姓名和缩略图
在传统的 REST API 中,这要么需要 11 个请求,一个用于帖子,10 个用于作者,要么需要在帖子详细信息中包含作者详细信息。
2.1. GraphQL Schemas
GraphQL 服务器公开描述 API 的 Schema。此 Schema 由类型定义组成。每种类型都有一个或多个字段,每个字段采用零个或多个参数并返回特定类型。
博客的示例 GraphQL Schema 可能包含以下定义,用于描述帖子、帖子的作者以及用于获取博客上最新帖子的根查询:
type Post {
id: ID!
title: String!
text: String!
category: String
author: Author!
}
type Author {
id: ID!
name: String!
thumbnail: String
posts: [Post]!
}
# The Root Query for the application
type Query {
recentPosts(count: Int, offset: Int): [Post]!
}
# The Root Mutation for the application
type Mutation {
createPost(title: String!, text: String!, category: String, authorId: String!) : Post!
}
某些名称末尾的“!”表示它是不可为 null 的类型。任何没有此功能的类型都可以在服务器的响应中为 null。GraphQL 服务可以正确处理这些字段,从而允许我们安全地请求可为 null 类型的子字段。
GraphQL 服务还使用一组标准字段公开 Schema ,允许任何客户端提前查询 Schema 定义。
这允许客户端自动检测 Schema 何时更改,并允许客户端动态适应 Schema 的工作方式。一个非常有用的例子是 GraphiQL 工具,它允许我们与任何 GraphQL API 进行交互。
3. 介绍 GraphQL Spring Boot Starter
Spring Boot GraphQL Starter 提供了一种在很短的时间内运行 GraphQL 服务器的绝佳方式。使用自动配置和基于注释的编程方法,我们只需要编写服务所需的代码。
3.1. 设置服务
我们只需要正确的依赖项就可以了:
<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>
由于 GraphQL 与传输无关,因此我们在配置中包含了 Web 启动器。这将在默认的 /graphql 端点上使用 Spring MVC 公开 GraphQL API over HTTP。其他启动器可用于其他底层实现,例如 Spring Webflux。
如有必要,我们还可以在 application.properties 文件中自定义此端点。
4. 编写 Schema
GraphQL Boot Starter的工作原理是处理 GraphQL Schema 文件以构建正确的 Schema,然后将特殊 Bean 连接到此 Schema。Spring Boot GraphQL 启动器会自动查找这些 Schema 文件。
我们需要将这些“.graphqls”或“.gqls” Schema 文件保存在 src/main/resources/graphql/** 位置下,Spring Boot 会自动加载它们。像往常一样,我们可以使用 spring.graphql.schema.locations 自定义位置,并使用 spring.graphql.schema.file-extensions 配置属性自定义文件扩展名。
一个要求是必须只有一个根查询(Root Query)和最多一个根变更(Root Mutation)。与架构的其余部分不同,我们无法将其拆分到文件之间。这是 GraphQL Schema 定义的限制,而不是 Java 实现的限制。
5. 实现我们的 Schema
一旦我们编写了 Schema ,我们就需要能够在代码中实现它。这包括我们可以实现的三种不同类型的代码:
- Root Query Resolver – 这些解析程序用于解析 Schema 中顶级查询的值。
- Field Resolver – 这些用于解析嵌套在响应中的值。
- Mutations – 这些用于从我们的模式实现突变。
所有这些都是通过在我们的应用程序中编写 bean 并根据需要对其进行注释来实现的。
5.1. Root Query Resolver
根查询(Root Query Resolver)需要具有专门注释的方法来处理此根查询中的各种字段。
我们需要用@QueryMapping来注释处理程序方法,并将它们放在应用程序的标准@Controller组件中。这会将带注释的类注册为 GraphQL 应用程序中的数据获取组件:
@Controller
public class PostController {
private PostDao postDao;
@QueryMapping
public List<Post> recentPosts(@Argument int count, @Argument int offset) {
return postDao.getRecentPosts(count, offset);
}
}
上面定义了 recentPosts 方法,我们将使用它来处理前面定义的 Schema 中 recentPosts 字段的任何 GraphQL 查询。此外,该方法必须具有用与 Schema 中的相应参数对应的@Argument批注的参数。
它还可以选择采用其他与 GraphQL 相关的参数,例如 GraphQLContext、DataFetchingEnvironment 等,以访问底层上下文和环境。
该方法还必须返回 GraphQL 方案中类型的正确返回类型,正如我们将要看到的。我们可以将任何简单的类型(String、Int、List 等)与等效的 Java 类型一起使用,系统会自动映射它们。
5.2. 使用 Bean 来表示类型
GraphQL 服务器中的每个复杂类型都由 Java Bean 表示,无论是从根查询还是从结构中的其他任何位置加载。相同的 Java 类必须始终表示相同的 GraphQL 类型,但不需要类的名称。
Java Bean 中的字段将根据字段名称直接映射到 GraphQL 响应中的字段:
public class Post {
private String id;
private String title;
private String category;
private String authorId;
}
Java Bean 上任何未映射到 GraphQL 模式的字段或方法都将被忽略,但不会导致问题。这对于字段解析器的工作非常重要。
例如,在这里,字段 authorId 与我们之前定义的架构中的任何内容都不对应,但它可用于下一步。
5.3. 复杂值的字段解析器
有时,字段的值加载起来并不容易。这可能涉及数据库查找、复杂计算或其他任何内容。@SchemaMapping批注将处理程序方法映射到 Schema 中具有相同名称的字段,并将其用作该字段的 DataFetcher。
@SchemaMapping
public Author author(Post post) {
return authorDao.getAuthor(post.getAuthorId());
}
重要的是,如果客户端不请求字段,则 GraphQL 服务器将不会执行检索该字段的工作。这意味着,如果客户端检索 Post 并且不要求提供 author 字段,则不会执行上述 author() 方法,也不会进行 DAO 调用。
或者,我们也可以在注释中指定父类型名称和字段名称:
@SchemaMapping(typeName="Post", field="author")
public Author getAuthor(Post post) {
return authorDao.getAuthor(post.getAuthorId());
}
在这里,注释属性用于将其声明为架构中作者字段的处理程序。
5.4. 可为空的值
GraphQL Schema 的概念是,某些类型可为 null,而其他类型则不可。
我们在 Java 代码中直接使用 null 值来处理这个问题。相反,我们可以直接将 Java 8 中的新 Optional 类型用于可为 null 的类型,系统将对值执行正确的操作。
5.5. 变更
到目前为止,我们所做的一切都是为了从服务器中检索数据。GraphQL 还能够通过 Mutations(变更) 更新存储在服务器上的数据。
与 Query 类似,通过使用 @MutationMapping 注释处理程序方法在控制器中定义突变。然后,Mutation 字段的返回值与 Query 字段的返回值完全相同,从而允许检索嵌套值:
@MutationMapping
public Post createPost(@Argument String title, @Argument String text,
@Argument String category, @Argument String authorId) {
Post post = new Post();
post.setId(UUID.randomUUID().toString());
post.setTitle(title);
post.setText(text);
post.setCategory(category);
post.setAuthorId(authorId);
postDao.savePost(post);
return post;
}
6. GraphiQL
GraphQL 还有一个名为 GraphiQL 的配套工具。此 UI 工具可以与任何 GraphQL Server 通信,并有助于针对 GraphQL API 进行使用和开发。它的可下载版本作为 Electron 应用程序存在,可以从这里检索。
Spring GraphQL 内置了 GraphiQL。默认情况下,这是关闭的,但我们可以通过向application.yml添加以下内容来打开它:
spring:
graphql:
graphiql:
enabled: true
完成此操作后,我们可以导航到应用程序上的 /graphiql 端点,并获取完整的 GraphiQL 用户界面,以便与我们的 API 进行交互。这提供了一个非常有用的浏览器内工具来编写和测试查询,尤其是在开发和测试期间:
7. 总结
Spring Boot GraphQL Starter 使将此技术添加到任何新的或现有的 Spring Boot 应用程序变得非常容易。