【GraphQL】入门学习,并用GraphQL Spring Boot Starter搭建了GraphQL server

378 阅读8分钟

【GraphQL官网】

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. 具体的步骤【重要】

WechatIMG17.png

参考:blog.csdn.net/MoFengLian/…

从官网的介绍图上可以看到,GraphQL主要分三个步骤:

    1. 描述你的数据,即使用Interface Definition Language (IDL) 或 Schema Definition Language (SDL)来定义我们的schema。
    • 1.1 定义用户自定义类型。类型的每个字段都必须是已定义的,且最终都是 GraphQL 中定义的类型。
    • 1.2 定义根类型。每种根类型中包含了准备暴露给服务调用方的用户自定义类型。
    • 1.3 定义Schema。每一个 Schema 中允许出现三种根类型:query,mutation,subscription,其中至少要有 query。
    1. 查询你想要的,当我们完成步骤1的定义后,有了schema,并将Query类型添加到schema中后,这样我们就可以在GraphQL API当中使用我们的查询了。
    1. 获得结果

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相关的库:

再列举几个JavaScripts相关的:

7. GraphQL Spring Boot Starter

这里我使用Java的GraphQL Spring Boot Starter作为GraphQL的服务端。

参考:

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的后缀文件:

image.png

所以我们在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.javaUser.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】: image.png

我们尝试写下查询,查询id=1的用户,并返回字段:id和name: image.png

或是查询id=2的用户,但返回字段id和gender: image.png

【总结】 本章用Java GraphQL Spring Boot Starter作为GraphQL server,搭建了服务,并写了一个特别简单的sample,用工具GraphQL Playground作为客户端,向server端的/graphql接口发起查询请求。

只是作为入门的例子,所以只涉及简单的查询,关于修改等操作,需要用到另一个操作类型:mutation。

8. IntelliJ IDEA插件介绍

image.png