【GraphQL官网】
- 英文:graphql.org/
- 中文:graphql.js.cool/
1. GraphQL的诞生
当今流行的数据传输规范是REST(即Representational State Transfer),Rest API对于每个Web开发人员都非常熟悉了,例如一个学习(student)的增删改查的api可能是这样的:
- List students: GET /students
- Get single student: GET /students/{studentId}
- Add student: POST /students
- Update student: PUT /students/{studentId}
- Delete student: DELETE /students/{studentId}
Rest API有很多优点,但同时也有一些缺点,如:
- 过量获取:即如果student有10个字段,某个api只想要2个字段呢?
- 获取不足:即如果想要获取student别的统计信息,如某个学习参加过的所有课程列表,那么则需要另外设计一个api,/students/{studentId}/courses
- 接口文档难以管理。
而GraphQL诞生,源自于2012年,facebook内部为了改进将数据发送到客户端应用的方式,着手构建的一种查询语言,它可以描述其所需数据模式的功能和需求,为公司的客户端和服务端提供便利,于2015年开源。
简而言之,GraphQL 是一种 API 查询语言,是客户端和服务端之间通信的规范。一个比较短的介绍GraphQL的视频:www.youtube.com/watch?v=eIQ…
和REST的单个API的请求不同,GraphQL的理念是,客户端需要先描述出想要的数据,然后服务端根据一次性返回所有的数据。
用上述的student的例子,例如一个页面想要列出所有的学生,以及学习订阅的课程列表。
- 使用
REST可以先获取所有的学生列表,再根据每个学生的id,获取到学习的课程。假如列表中有5个学生,那么总共可能需要请求6次。 - 使用
GraphQL的话,可以先在客户端定义想要的数据:
query {
students {
id
name
courses {
name
}
}
}
可以将上述的query发送给GraphQL的服务端,接口也很简单,即:/graphql,然后服务端就会把想要的数据以json的形式返回给客户端。假如student有10个字段,因为你在定义的时候只需要id和name,那么服务端也只会返回student的这两个字段,当然还会额外的加上courses列表,在同一个接口中。是不是很方便!
2. 具体的步骤【重要】
从官网的介绍图上可以看到,GraphQL主要分三个步骤:
-
描述你的数据,即使用Interface Definition Language (IDL) 或 Schema Definition Language (SDL)来定义我们的schema。
- 1.1
定义用户自定义类型。类型的每个字段都必须是已定义的,且最终都是 GraphQL 中定义的类型。 - 1.2
定义根类型。每种根类型中包含了准备暴露给服务调用方的用户自定义类型。 - 1.3
定义Schema。每一个 Schema 中允许出现三种根类型:query,mutation,subscription,其中至少要有 query。
-
查询你想要的,当我们完成步骤1的定义后,有了schema,并将Query类型添加到schema中后,这样我们就可以在GraphQL API当中使用我们的查询了。
-
获得结果
3. 描述你的数据
通过特定的语法进行GraphQL的定义,我们可以将这些定义作为字符串写到JavaScript文件里,或是定义在Java Code里,但最常用的还是以文本形式写到以.graphql为扩展名的文件里。
每次调用 GraphQL 服务,需要明确指定调用 Schema 中的哪个根类型(默认是 query),然后指定这个根类型下的哪几个字段(每个字段对应一个用户自定义类型),然后指定这些字段中的那些子字段的哪几个。一直到所有的字段都没有子字段为止。
Schema 明确了服务端有哪些字段(用户自定义类型)可以用,每个字段的类型和子字段。每次查询时,服务器就会根据 Schema 验证并执行查询。
【例设】我们的应用有两种主要类型:User和Photo。现在我们开始为整个应用设计schema。
3.1 定义用户自定义类型
scalar DateTime
enum PhotoCategory {
HUMAN
OTHER
}
type Photo {
id: ID!
name: String!
description: String
created: DateTime!
category: PhotoCategory!
}
- 首先我们定义了GraphQL
对象类型:Photot。 内置标量类型(Scalar),有Int Float String Boolean ID,这里的ID指唯一的标识符。感叹号表示非空字段,即id和name为非空,description可以为空。自定义标量类型,如DateTime,它没有字段,但是在实现GraphQL服务的时候,指定何如验证自定义标量类型。自定义枚举类型:PhotoCategory。
3.2 定义用户自定义类型:更为复杂的连接
type User { id: ID! name: String postedPhotos: [Photo!]! } type Photo { ... postedBy: User! }
- 添加了新的
自定义类型:User。 - User中有postedPhotos,用[]表示数组,即
一对多连接,1个用户可以发布多张照片。这里的[Photo!]!表示不可空的Photo的非空列表。 - Photo的其它字段略,这里的postedBy为
一对一连接。
3.3 定义根类型
按照#2的步骤,在定义完用户的自定义类型:Photo和User后,想要在查询中使用Photo或User,接下来我们需要定义根类型。
即如何将新的自定义类型添加到Query根类型中:
type Query {
totalPhotots: Int!
allPhotos: [Photo!]!
totalUsers: Int!
allUsers: [User!]!
}
schema {
query: Query
}
- 我们为Photo和User两种类型分别添加了两个查询,总数和列表。
- 我们做了上述#2中的步骤#1.3,即将Query类型作为文件添加到schema中,这样我们可以在GraphQL API当中使用我们的查询。
4. 查询你想要的
在#1中我们介绍,REST的缺点之一就是返回过多的字段,如果某个页面只想要用户所有照片的个数,以及id和name,那么我们就可以按需请求,这样即使photo原本除了id, name还有description, createdTime等字段,也不会返回给客户端:
query {
totalPhotos
allPhotos {
id
name
}
}
5. 如何在schema中定义参数
在第#3章学习了如何定义schema,并且在第#4章学习了如何自定义查询你想要的数据。
但定义的都是十分简单的结构,更为复杂的诸如多对多关系,或是使用接口(Interface)定义都没有介绍。除此之外我们还需要思考的是,假如需要传入参数呢?即只想返回某个特定的用户的信息。
所以我们在定义根查询的时候,可以定义为:
type Query {
...
User(id: ID!): User!
Photo(id: ID!): Photo!
}
【如何查询?】 在定义好根查询后,我们就可以向GraphQL server请求数据了,在请求数据时,需要告诉server我想要的,以下的例子即需要返回user.id = u01的用户,返回的字段包含用户的id, name即可。
query {
User(id: "u01") {
id
name
}
}
GraphQL server返回的数据sample:
{
"data": {
"user": {
"id": "u01"
"name": "R2-D2"
}
}
}
6. GraphQL的支持
在简单的介绍了GraphQL的创建步骤后(主要是定义schema以及如何查询),那么我们就可以自己创建一个GraphQL API了。
因为GraphQL是一种规范,所以它本身与开发语言无关,目前支持很多种语言,如JavaScript, GO, PHP, Java, C#等等。具体参考:graphql.js.cool/code/
主要分: 服务端库,客户端库 以及一些小工具。
我作为Java开发,这里列的也是Java相关的库:
- 服务端库:
graphql-java:用于构建GraphQL API的Java库。Github地址:github.com/graphql-jav…GraphQL Spring Boot:和Spring Boot集成,将项目变成GraphQL server。基于的是上述的graphql-java,需要jdk8+以及spring boot 2.x+。文档:www.graphql-java-kickstart.com/spring-boot…
- 客户端库:
Nodes:JVM的客户端,由American Express公司写的。Github地址:github.com/americanexp…
再列举几个JavaScripts相关的:
- 服务端库:
GraphQL.js:专为在 Node.js 环境中运行 GraphQL 而设计的。文档:graphql.org/graphql-js/Apollo Server:官网:www.apollographql.com/docs/apollo…
- 客户端库:
Apollo Client:官方文档:apollographql.com/client/
- 工具:
GraphiQL:基于浏览器的交互式的IDE。Github:github.com/graphql/gra…
7. GraphQL Spring Boot Starter
这里我使用Java的GraphQL Spring Boot Starter作为GraphQL的服务端。
参考:
- 官方文档:www.graphql-java-kickstart.com/spring-boot…
- 教程:www.youtube.com/watch?v=nju…
- Github: github.com/graphql-jav…
7.1 依赖
我的环境:
- jdk17
- Spring boot 2.7.0
加上依赖:
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>\
<version>14.0.0</version>
</dependency>
Repository:
<repositories>
<repository>
<id>osshr-snapshots</id>
<name>osshr-sonatype-snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
默认情况下,GraphQL服务端会暴露/graphql作为接口,会接受POST的请求。可以在application.properties中重写。
7.2 定义graphql文件
默认情况下,Spring boot会加载resources下的所有graphqls的后缀文件:
所以我们在resources下新建一个文件夹叫graphql,用来存放schema定义文件。
新建query.graphqls文件:
这里的注解,到时候会在GraphiQL等工具中显示,所以可以在这里写一些必要的解释性注解。
# My first graphql querys
type Query {
user(id: ID!): User!
}
schema {
query: Query
}
IDE提示User类型不存在,所以我们还需要额外定义User类型,在graphql文件夹下再新建user文件夹,新建user.graphqls文件:
type User {
id: ID!
name: String
gender: Gender!
}
因为用到了枚举类Gender,所以额外再定义一个文件:gender.graphqls:
enum Gender {
MALE
FEMALE
}
7.3 创建Resolver
在创建Resolver之前,先把domain新建下:
首先是Gender.java和User.java:
public enum Gender {
MALE, FEMALE
}
@Data
@AllArgsConstructor
public class User {
private int id;
private String name;
private Gender gender;
}
再是UserResolver.java:
@Slf4j
@Component
public class UserResolver implements GraphQLQueryResolver {
public User user(int id) {
log.info("Received id = {}", id);
return new User(id, "test user", Gender.FEMALE);
}
}
7.4 Enable GraphQL UI工具:GraphQL Playgound
在application.yaml中加上:
graphql:
playground:
mapping: /playground
enabled: true
打开浏览器输入:http://localhost:8080/playground
可以看到出现的页面如下,在左边有两个tab:【DOCS】和【SCHEMA】:
我们尝试写下查询,查询id=1的用户,并返回字段:id和name:
或是查询id=2的用户,但返回字段id和gender:
【总结】 本章用Java GraphQL Spring Boot Starter作为GraphQL server,搭建了服务,并写了一个特别简单的sample,用工具GraphQL Playground作为客户端,向server端的/graphql接口发起查询请求。
只是作为入门的例子,所以只涉及简单的查询,关于修改等操作,需要用到另一个操作类型:mutation。