Skip to main content

GraphQL

前面我们所有的程序都是基于 RESTful API 构建的,但是 RESTful API 最大的问题是,所有的 API 本质都是一个黑盒,在你拿到数据之前,你不知道这个 API 会返回什么数据,这导致了前后端工作的耦合。GraphQL 就是为了解决这个问题而生的。

GraphQL 是一个查询语言,它允许客户端查询自己需要的数据,而不是服务端返回固定的数据。这样,前后端只要约定好数据库的结构,就可以独立开发,不需要等待对方的接口。

GraphQL 基本概念

GraphQL 是替换 RESTful API 的一种方式。因此前端和后端的交互方式和 RESTful API 有很大的不同。

在前端,与 RESTful API 不同,GraphQL 有且只有一个入口,即/graphql。所有的请求都通过这个入口,而不是通过不同的 URL。

通过 POST 方法,前端在 body 里发送一个 GraphQL 查询语句,后端解析这个查询语句,返回查询结果。

GraphQL 查询语句是一种专门的语言,类似于 SQL 对数据库的查询。它有自己的语法,可以查询多个字段,多个对象,多个关联对象。我们会在后面的章节详细介绍。

在后端,GraphQL 语句的解析和数据的获取由框架自动完成,我们只需要完成数据提供的任务即可。

GraphQL 语法

GraphQL 语法是一种类似于 JSON 的语言,但是比 JSON 更强大。它可以查询多个对象,多个字段,多个关联对象。

类型

GraphQL 有一套独立于编程语言的类型系统。这个类型系统定义了所有的对象和字段的类型。

在 GraphQL 中,基本类型有,

  • Int:整数
  • Float:浮点数
  • String:UTF-8 字符串
  • Boolean:true 或 false
  • ID:唯一标识符,通常是字符串。

此外,还有一些装饰符,来从基础类型派生新的类型,

  • [type]:数组
  • type!:非空,注意,默认情况下所有的类型都是可空的。

如果要定义一个对象,可以使用type关键字,

type Country {
name: String
}

之后可以同基本类型一样使用这个对象。

Schema

Schema 定义了 GraphQL 允许的查询和变更。Schema 由 Query 和 Mutation 两部分组成。

Query

Query 是一个特殊的 type,定义名称为Query的对象,即可定义所有允许的查询。

例如,

type Country {
name: String
}

type Query {
countries: [Country]
}

如果要进行查询,可以使用query关键字,加上字段结构,

query {
countries {
name
}
}

这个的含义即是,从Query对象中查询countries字段,然后查询每个的name字段。注意,所有的字段都要归结到基本类型。

注意,这里如果在类型定义中返回列表,那么查询时也需要返回列表,后面的查询对象,即要求只返回name,是作用在列表的每个对象上的。如果返回一个对象,后面的查询对象就是作用在这个对象上的。

这样定义的查询是无参的,如果要传参,可以在countries后面加上参数,

type Query {
countries($code: String): Country
}

如果要传入参数,可以使用,

query {
country(code: "CN") {
name
}
}

如果要输入对象,需要使用input关键字,而不能使用之前的type

input CountryInput {
name: String
}

type Query {
countries($input: CountryInput): [Country]
}

这样,如果要传入参数,可以使用,

query {
countries(input: {name: "China"}) {
name
}
}

注意。所有的查询字段最后必须归结到基本类型。例如,

type Author {
name: String
books: [Book]
}

type Book{
title: String
}

type Query {
authors: [Author]
}

如果要查询所有的书名,应当是,

query {
authors {
books {
title
}
}
}

Mutation

Mutation 也是一个特殊的 type,定义名称为Mutation的对象,即可定义所有允许的变更。

例如,

type Mutation {
addCountry(name: String): Country
}

如果要进行变更,可以使用mutation关键字,

mutation {
addCountry(name: "China") {
name
}
}

其它的和 Query 类似。

GraphQL 后端搭建

现在我们开始搭建后端。首先,我们需要引入 GraphQL 的依赖,

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-graphql'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}

这里使用 Web 或者 WebFlux 都可以。

然后在resources/graphql目录下创建schema.gqls文件,定义 GraphQL 的 Schema,这个路径可以用spring.graphql.schema.locations配置,后缀名可以是.gqls.graphqls

type Book {
id: ID!
title: String!
}

type Author {
id: ID!
name: String!
books: [Book!]!
}

type Query {
authors: [Author!]!
author(id: ID!): Author!
books: [Book!]!
book(id: ID!): Book!
}

type Mutation {
createAuthor(name: String!): Author!
createBook(title: String!): Book!
addBookToAuthor(authorId: ID!, bookId: ID!): Author!
}

然后创建AuthorBook类,

@Data
public class Author {
private String id;
private String name;
private List<Book> books;
}

@Data
public class Book {
private String id;
private String title;
}

然后,我们创建一下 Service 类,这里我们使用 Map 当作数据库。

@Service
public class AuthorBookService {
private final Map<String, Author> authors = new HashMap<>();
private final Map<String, Book> books = new HashMap<>();

public Author createAuthor(String name) {
Author author = new Author();
author.setId(UUID.randomUUID().toString());
author.setName(name);
authors.put(author.getId(), author);
return author;
}

public Book createBook(String title) {
Book book = new Book();
book.setId(UUID.randomUUID().toString());
book.setTitle(title);
books.put(book.getId(), book);
return book;
}

public Author addBookToAuthor(String authorId, String bookId) {
Author author = authors.get(authorId);
Book book = books.get(bookId);
author.getBooks().add(book);
return author;
}

public List<Author> getAuthors() {
return new ArrayList<>(authors.values());
}

public Author getAuthor(String id) {
return authors.get(id);
}

public List<Book> getBooks() {
return new ArrayList<>(books.values());
}

public Book getBook(String id) {
return books.get(id);
}
}

最后,为每一个 Query 和 Mutation 创建一个 Resolver,即 Controller。注意,不是@RestController,而是@Controller

package io.github.fingerbone.demo;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

import java.util.List;

@Controller
public class AuthorBookController {
private final AuthorBookService service;

public AuthorBookController(AuthorBookService service) {
this.service = service;
}

@QueryMapping
public List<Author> authors() {
return service.getAuthors();
}

@QueryMapping
public Author author(@Argument String id) {
return service.getAuthor(id);
}

@QueryMapping
public List<Book> books() {
return service.getBooks();
}

@QueryMapping
public Book book(@Argument String id) {
return service.getBook(id);
}

@MutationMapping
public Author createAuthor(@Argument String name) {
return service.createAuthor(name);
}

@MutationMapping
public Book createBook(@Argument String title) {
return service.createBook(title);
}

@MutationMapping
public Author addBookToAuthor(@Argument String authorId, @Argument String bookId) {
return service.addBookToAuthor(authorId, bookId);
}
}

然后可以打开 graphiql,这是一个 GraphQL 的接口测试工具,可以在这里测试 GraphQL 的查询和变更。

spring.graphql.graphiql.enabled: true

然后访问/graphiql即可。现在我们就可以在这里测试 GraphQL 的查询和变更了。当然,Postman 也支持 GraphQL。