构建 GraphQL 服务

Spring for GraphQL 为基于 GraphQL Java 构建的 Spring 应用程序提供支持。

本指南将引导您完成使用 Spring for GraphQL 在 Java 中创建 GraphQL 服务的过程。

您将构建什么

您将构建一个服务,该服务将在 https://:8080/graphql 接受 GraphQL 请求。

你需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以跳过您已熟悉的基本设置步骤。无论哪种方式,您最终都会得到可工作的代码。

从头开始,请转到从 Spring Initializr 开始

跳过基础知识,请执行以下操作

完成时,您可以对照 gs-graphql-server/complete 中的代码检查您的结果。

从 Spring Initializr 开始

如果您愿意,可以使用此预填充的 Spring Initializr 链接来加载正确的设置。否则,请继续手动设置 Initializr。

手动初始化项目

  1. 导航到 https://start.spring.io。此服务会为您拉取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。

  3. 单击 Dependencies 并选择 Spring for GraphQLSpring Web

  4. 单击生成

  5. 下载生成的 ZIP 文件,它是根据您的选择配置的 GraphQL 应用程序的归档文件。

如果您的 IDE 集成了 Spring Initializr,您可以从 IDE 中完成此过程。
您还可以从 GitHub 分叉该项目,并在您的 IDE 或其他编辑器中打开它。

GraphQL 简短介绍

GraphQL 是一种用于从服务器检索数据的查询语言。它是 REST、SOAP 或 gRPC 的替代方案。在本教程中,我们将查询在线商店后端中特定书籍的详细信息。

这是一个您可以发送到 GraphQL 服务器以检索书籍详细信息的请求示例

query bookDetails {
  bookById(id: "book-1") {
    id
    name
    pageCount
    author {
      firstName
      lastName
    }
  }
}

此 GraphQL 请求表示

  • 执行查询以查找 ID 为“book-1”的书籍

  • 对于该书籍,返回 id、name、pageCount 和 author

  • 对于作者,返回 firstName 和 lastName

响应是 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 创建服务器的主要步骤

  1. 定义 GraphQL 模式

  2. 实现获取查询实际数据的逻辑

我们的示例应用程序将是一个简单的 API,用于获取特定书籍的详细信息。它不旨在成为一个全面的 API。

模式

在您之前准备好的 Spring for GraphQL 应用程序中,向 src/main/resources/graphql 文件夹添加一个新文件 schema.graphqls,其内容如下

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 的查询,它返回特定书籍的详细信息。

它还定义了带有字段 idnamepageCountauthorBook 类型,以及带有字段 firstNamelastNameAuthor 类型。

上面用于描述模式的领域特定语言称为模式定义语言或 SDL。有关更多详细信息,请参阅 GraphQL 文档

数据来源

GraphQL 的一个关键优势是数据可以来自任何地方。数据可以来自数据库、外部服务或静态内存列表。

为了简化教程,书籍和作者数据将来自其各自类中的静态列表。

创建 Book 和 Author 数据源

现在让我们在主应用程序包中,紧邻 GraphQlServerApplication 创建 BookAuthor 类。使用以下内容作为它们的内容

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 字段的数据。

将以下内容添加到主应用程序包中 BookAuthor 旁边的 BookController.java

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.DataFetcherDataFetcher 提供获取查询或任何模式字段数据的逻辑。GraphQL 的 Spring Boot 启动器具有自动配置,可自动执行此注册。

在 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://:8080/graphiql

运行查询

输入查询并单击窗口顶部的播放按钮。

query bookDetails {
  bookById(id: "book-1") {
    id
    name
    pageCount
    author {
      id
      firstName
      lastName
    }
  }
}

您应该会看到如下响应。

GraphQL response

恭喜,您已构建了一个 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.graphql.test.autoconfigure.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-testbookDetails.graphql 文件中。

query bookDetails($id: ID) {
    bookById(id: $id) {
        id
        name
        pageCount
        author {
            id
            firstName
            lastName
        }
    }
}

运行测试并验证结果是否与在 GraphiQL Playground 中手动请求的 GraphQL 查询相同。

@GraphQlTest 注解对于编写控制器片段测试非常有用,这些测试专注于单个控制器。@GraphQlTest 自动配置 Spring for GraphQL 基础设施,不涉及任何传输或服务器。自动配置使我们能够通过跳过样板代码来更快地编写测试。由于这是一个有重点的片段测试,因此只扫描有限数量的 bean,包括 @ControllerRuntimeWiringConfigurer。有关扫描的 bean 列表,请参阅文档

GraphQlTester 是一个契约,它声明了测试 GraphQL 请求的通用工作流,独立于传输。在我们的测试中,我们提供了一个带有 documentName 和所需变量的文档,然后 execute 请求。然后,我们使用其 JSON 路径选择响应的一部分,并断言此位置的 JSON 与预期结果匹配。

恭喜!在本教程中,您构建了一个 GraphQL 服务,运行了您的第一个查询,并编写了您的第一个 GraphQL 测试!

延伸阅读

示例源代码

本指南与 GraphQL Java 团队合作编写。非常感谢 Donna ZhouBrad BakerAndreas Marek!本教程的源代码可以在 GitHub 上找到。

文档

GraphQL Java 是为 Spring for GraphQL 提供支持的 GraphQL 引擎。阅读 GraphQL Java 文档

更多 Spring for GraphQL 示例

请参阅专用仓库中的更多示例。

Stack Overflow 问题

您可以在 Stack Overflow 上使用 spring-graphql 标签提出问题。

想写新指南或为现有指南做贡献吗?请查看我们的贡献指南

所有指南的代码均采用 ASLv2 许可,文字内容采用署名-禁止演绎知识共享许可

获取代码