query bookDetails {
bookById(id: "book-1") {
id
name
pageCount
author {
firstName
lastName
}
}
}
构建 GraphQL 服务
Spring for GraphQL 为基于 GraphQL Java 构建的 Spring 应用程序提供支持。
本指南将引导你完成使用 Spring for GraphQL 在 Java 中创建 GraphQL 服务的过程。
你将构建什么
你将构建一个将在 https://127.0.0.1:8080/graphql
接受 GraphQL 请求的服务。
你需要什么
-
大约 15 分钟
-
一个偏爱的文本编辑器或 IDE
-
Java 17 或更高版本
-
你还可以直接将代码导入你的 IDE
如何完成本指南
与大多数 Spring 入门指南 一样,你可以从头开始完成每一步,也可以跳过你已经熟悉的设置步骤。无论哪种方式,你最终都会得到可用的代码。
要从头开始,请转到 从 Spring Initializr 开始。
要跳过基础知识,请执行以下操作
-
下载并解压缩本指南的源代码库,或使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-graphql-server.git
-
cd 到
gs-graphql-server/initial
-
跳转至 GraphQL 简介。
完成后,可以将结果与 gs-graphql-server/complete
中的代码进行对比。
从 Spring Initializr 开始
手动初始化项目
-
导航至 https://start.spring.io。此服务会提取应用程序所需的所有依赖项,并为您完成大部分设置。
-
选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。
-
点击 依赖项,然后选择 Spring for GraphQL 和 Spring Web。
-
点击 生成。
-
下载生成的 ZIP 文件,它是已根据您的选择配置的 GraphQL 应用程序的存档。
如果您的 IDE 集成了 Spring Initializr,则可以从 IDE 完成此过程。 |
您还可以从 GitHub 分叉项目,然后在 IDE 或其他编辑器中打开它。 |
GraphQL 简介
GraphQL 是一种从服务器检索数据的查询语言。它是 REST、SOAP 或 gRPC 的替代方案。在本教程中,我们将从在线商店后端查询特定图书的详细信息。
以下是可以发送到 GraphQL 服务器以检索图书详细信息的示例请求
此 GraphQL 请求表示
-
对 ID 为“book-1”的图书执行查询
-
对于图书,返回 ID、名称、页数和作者
-
对于作者,返回名字和姓氏
响应为 JSON。例如
{
"bookById": {
"id":"book-1",
"name":"Effective Java",
"pageCount":416,
"author": {
"firstName":"Joshua",
"lastName":"Bloch"
}
}
}
GraphQL 的一个重要功能是它定义了一种模式语言,并且它具有静态类型。服务器确切地知道请求可以查询的对象类型以及这些对象包含哪些字段。此外,客户端可以内省服务器以询问模式详细信息。
本教程中的模式一词指的是“GraphQL 模式”,它与“JSON 模式”或“数据库模式”等其他模式无关。 |
上述查询的模式为
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
本教程将重点介绍如何在 Java 中使用此模式实现 GraphQL 服务器。
我们仅粗略介绍了 GraphQL 的可能性。更多信息请参阅官方 GraphQL 页面。
我们的示例 API:获取书籍详细信息
以下是在 Spring for GraphQL 中创建服务器的主要步骤
-
定义 GraphQL 模式
-
实现获取查询实际数据的逻辑
我们的示例应用程序将是一个简单的 API,用于获取特定书籍的详细信息。它并非旨在成为一个全面的 API。
模式
在您之前准备的 Spring for GraphQL 应用程序中,将新文件 schema.graphqls
添加到 src/main/resources/graphql
文件夹,内容如下
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
每个 GraphQL 模式都有一个顶级 Query
类型,其下的字段是应用程序公开的查询操作。此处模式定义了一个名为 bookById
的查询,它返回特定书籍的详细信息。
它还定义了类型 Book
,其中包含字段 id
、name
、pageCount
和 author
,以及类型 Author
,其中包含字段 firstName
和 lastName
。
上面用于描述模式的特定领域语言称为模式定义语言或 SDL。有关更多详细信息,请参阅GraphQL 文档。 |
数据来源
GraphQL 的一个主要优势在于数据可以从任何地方获取。数据可以来自数据库、外部服务或静态内存中列表。
为了简化教程,书籍和作者数据将来自其各自类中的静态列表。
创建 Book 和 Author 数据源
现在,让我们在主应用程序包中创建 Book
和 Author
类,紧挨着 GraphQlServerApplication
。使用以下内容作为其内容
package com.example.graphqlserver;
import java.util.Arrays;
import java.util.List;
public record Book (String id, String name, int pageCount, String authorId) {
private static List<Book> books = Arrays.asList(
new Book("book-1", "Effective Java", 416, "author-1"),
new Book("book-2", "Hitchhiker's Guide to the Galaxy", 208, "author-2"),
new Book("book-3", "Down Under", 436, "author-3")
);
public static Book getById(String id) {
return books.stream()
.filter(book -> book.id().equals(id))
.findFirst()
.orElse(null);
}
}
package com.example.graphqlserver;
import java.util.Arrays;
import java.util.List;
public record Author (String id, String firstName, String lastName) {
private static List<Author> authors = Arrays.asList(
new Author("author-1", "Joshua", "Bloch"),
new Author("author-2", "Douglas", "Adams"),
new Author("author-3", "Bill", "Bryson")
);
public static Author getById(String id) {
return authors.stream()
.filter(author -> author.id().equals(id))
.findFirst()
.orElse(null);
}
}
添加用于获取数据的代码
Spring for GraphQL 提供了一个基于注释的编程模型。使用控制器注释的方法,我们可以声明如何获取特定 GraphQL 字段的数据。
将以下内容添加到主应用程序包中的 BookController.java
,紧挨着 Book
和 Author
package com.example.graphqlserver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument String id) {
return Book.getById(id);
}
@SchemaMapping
public Author author(Book book) {
return Author.getById(book.authorId());
}
}
通过定义一个名为 bookById
的方法并用 @QueryMapping
注释,此控制器声明如何获取在 Query 类型下定义的 Book
。查询字段由方法名确定,但也可以在注释本身中声明。
Spring for GraphQL 使用 RuntimeWiring.Builder ,它将每个此类控制器方法注册为 GraphQL Java graphql.schema.DataFetcher 。DataFetcher 提供获取查询或任何模式字段数据的逻辑。GraphQL 的 Spring Boot starter 具有自动配置,可自动执行此注册。 |
在 GraphQL Java 引擎中,DataFetchingEnvironment
提供对字段特定参数值的映射的访问。使用 @Argument
注释将参数绑定到目标对象并注入到控制器方法中。默认情况下,方法参数名称用于查找参数,但也可以在注释本身中指定。
此 bookById
方法定义了如何获取特定 Book
,但不会获取相关的 Author
。如果请求要求提供作者信息,GraphQL Java 将需要获取此字段。
@SchemaMapping
注解将处理程序方法映射到 GraphQL 模式中的字段,并声明其为该字段的 DataFetcher
。字段名称默认为方法名称,类型名称默认为注入到方法中的源/父对象的简单类名称。在此示例中,字段默认为 author
,类型默认为 Book
。
更多信息,请参阅 Spring for GraphQL 注释控制器功能的文档。
这就是我们需要的全部代码!
让我们运行第一个查询。
运行我们的第一个查询
启用 GraphiQL Playground
GraphiQL 是一个有用的可视化界面,用于编写和执行查询,以及更多操作。通过将此配置添加到 application.properties
文件中来启用 GraphiQL。
spring.graphql.graphiql.enabled=true
启动应用程序
启动 Spring 应用程序。导航到 https://127.0.0.1:8080/graphiql。
运行查询
键入查询,然后单击窗口顶部的播放按钮。
query bookDetails {
bookById(id: "book-1") {
id
name
pageCount
author {
id
firstName
lastName
}
}
}
你应该会看到类似这样的响应。
恭喜,你已经构建了一个 GraphQL 服务并执行了你的第一个查询!借助 Spring for GraphQL,你只需几行代码即可实现此目的。
测试
Spring for GraphQL 在 spring-graphql-test
工件中提供了 GraphQL 测试帮助程序。我们已经将此工件包含在 Spring Initializr 生成的项目中。
彻底测试 GraphQL 服务需要不同范围的测试。在本教程中,我们将编写一个 @GraphQlTest
切片测试,该测试专注于单个控制器。还有其他帮助程序可用于协助完整的端到端集成测试和重点关注服务器端的测试。有关完整详细信息,请参阅 Spring for GraphQL 测试文档 和 Spring Boot 文档中自动配置的 Spring for GraphQL 测试。
让我们编写一个控制器切片测试,以验证几分钟前在 GraphiQL playground 中请求的相同 bookDetails
查询。
将以下内容添加到测试文件 BookControllerTests.java
中。将此文件保存在 src/test/java/com/example/graphqlserver/
文件夹中的某个位置。
package com.example.graphqlserver;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;
@GraphQlTest(BookController.class)
public class BookControllerTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void shouldGetFirstBook() {
this.graphQlTester
.documentName("bookDetails")
.variable("id", "book-1")
.execute()
.path("bookById")
.matchesJson("""
{
"id": "book-1",
"name": "Effective Java",
"pageCount": 416,
"author": {
"firstName": "Joshua",
"lastName": "Bloch"
}
}
""");
}
}
此测试引用类似于我们在 GraphiQL Playground 中使用的 GraphQL 查询。它使用 $id
参数化,以便可重复使用。将此查询添加到位于 src/test/resources/graphql-test
中的 bookDetails.graphql
文件中。
query bookDetails($id: ID) {
bookById(id: $id) {
id
name
pageCount
author {
id
firstName
lastName
}
}
}
运行测试并验证结果与在 GraphiQL Playground 中手动请求的 GraphQL 查询相同。
@GraphQlTest
注解对于编写控制器切片测试非常有用,这些测试专注于单个控制器。@GraphQlTest
自动配置 GraphQL 基础架构的 Spring,而不涉及任何传输或服务器。自动配置使我们能够通过跳过样板代码来更快地编写测试。由于这是一个专注的切片测试,因此仅扫描有限数量的 bean,包括 @Controller
和 RuntimeWiringConfigurer
。有关扫描的 bean 列表,请参阅 文档。
GraphQlTester
是一个契约,它声明了一个用于测试 GraphQL 请求的通用工作流,与传输无关。在我们的测试中,我们使用所需变量提供一个带有 documentName
的文档,然后 execute
请求。然后,我们使用其 JSON 路径选择响应的一部分,并断言此位置的 JSON 与预期结果匹配。
恭喜!在本教程中,您构建了一个 GraphQL 服务,运行了您的第一个查询,并编写了您的第一个 GraphQL 测试!
延伸阅读
示例源代码
本指南已与 GraphQL Java 团队合作编写。非常感谢 Donna Zhou、Brad Baker 和 Andreas Marek!本教程的源代码可以在 GitHub 上找到。
Stack Overflow 问题
您可以在 Stack Overflow 上使用 spring-graphql 标签提出问题。
想编写新指南或为现有指南做出贡献?查看我们的 贡献指南。
所有指南都随代码一起发布 ASLv2 许可证,以及随写作一起发布 署名,禁止派生创作共用许可证。 |