18

为什么要学习GraphQL [ 果糖酱的博客 ]

 4 years ago
source link: https://answer518.github.io/2020/01/30/what-the-heck-is-graphql/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、Why GraphQL?

现在有一个需求:根据文章ID来查找对应的 User 信息。

假设数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Users:
{
"id": 1,
"fname": "Richie",
"age": 27,
"likes": 8
},
{
"id": 2,
"fname": "Betty",
"age": 20,
"likes": 205
},
{
"id" : 3,
"fname": "Joe",
"age": 28,
"likes": 10
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Posts:
{
id: 1,
userId: 2,
body: "Hello how are you?"
},
{
id: 1,
userId: 3,
body: "What's up?"
},
{
id: 1,
userId: 1,
body: "Let's learn GraphQL"
}

1、传统方式的局限性

常见的方案是:定义两个接口,分别根据user id和post id 获取对应的 userpost 信息。

1
2
> https://localhost:4000/users/:id
> https://localhost:4000/posts/:id

整个过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET https://localhost:4000/posts/1
Response:
{
id: 1,
userId: 2,
body: "Hello how are you?"
}
我们现在拿到文章id为2的userId, 然后调用另一个接口获取用户信息
GET https://localhost:4000/users/2
Response:
{
"id": 2,
"fname": "Betty",
"age": 20,
"likes": 205
}

整个过程查询了两次数据库。

还没完,假如前端页面只用到了fname信息呢?

有人说从接口数据里只取 fname 就好啦。 但是你想过没有,接口返回给我们的是用户的所有字段: id, age, likes等。这些字段会随着请求量的增大,会消耗网络带宽,给我们的服务器带来压力。

又有人会说,那我重新定义一个接口专用于返回用户的 fname, 如:

1
https://localhost:4000/userByfname/:id

这样确实能避免我们上面提到的问题,但是会带来一些新的问题: 第一,我们需要根据user的字段创建分别创建接口。第二,随着应用的变化,user的字段信息肯定会发生变化,我们需要同时维护好这些接口,给开发人员带来心智负担。

那么我们如果使用 GraphQL 会怎么样

2、GraphQL 的优势

GraphQL 中只需创建一个 query 语句:

1
2
3
4
5
6
7
8
{
posts(id: 1) {
body
user {
fname
}
}
}

如果你仅仅只需用到 age 字段,你可以修改 query 语句:

1
2
3
4
5
6
7
8
{
posts(id: 1) {
body
user {
age
}
}
}

只需保证 user 字段在 GraphQLschema 中都提前定义好就行。

GraphQL 最大的优势就是既不需要新建接口,也无需与后端服务多次通信,就能精确的获取到我们想要的字段信息

二、GraphQL 基础入门

先从最基本的 GraphQL 操作类型开始:

1、Query

顾名思义,Query用于查询数据。是以字符串的形式通过Post请求中body体中传给服务器。也就是所有的GraphQL类型都是post请求。

以下Query用于从数据库中取出所有 users 的 fname 和 age信息:

1
2
3
4
5
6
query {
users {
fname
age
}
}

理想情况下,服务器应返回的数据格式是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
data : {
users [
{
"fname": "Joe",
"age": 23
},
{
"fname": "Betty",
"age": 29
}
]
}

如果成功,会返回一个含有key 为 data的JSON对象;如果失败,返回的key就是error; 我们可以根据这个来处理错误。

2、Mutation

Mutation 用于向数据库写入数据。可以把它想象为 rest 中的 post 和 put请求:

1
2
3
4
5
mutation createUser{
addUser(fname: "Richie", age: 22) {
id
}
}

定义了一个名为 createUser 的 mutation, 根据传入的fname和age向数据库添加一个user; 同时定义了id作为返回值:

1
2
3
data : {
addUser : "a36e4h"
}

3、Subscription

subscription 赋予客户端实时监听数据变化的能力,利用的 websockets 来实现的。

1
2
3
4
5
6
subscription listenLikes {
listenLikes {
fname
likes
}
}

上面定义了一个监听 usersfnamelikes 两个字段,一旦其中任何一个字段发生变化,都会以下面的形式通知客户端:

1
2
3
4
5
6
data: {
listenLikes: {
"fname": "Richie",
"likes": 245
}
}

假如有一个页面需要实时显示用户 likes 字段的变化,这个特性就变得非常有用。

以上就是 GraphQL 中最基本的操作类型, 虽然介绍的很简单,但是已经足够了。下面我就利用上面介绍的知识设计并且实现一个GraphQL 案例,来直观感受 GraphQL 的强大。

三、GraphQL 实战

首先创建一个 GraphQL 服务来分别响应 query, mutationsubscriptions 这三种操作。

1、初始化工程

1
2
3
> mkdir graphql-server-demo
> cd graphql-server-demo
> npm init -y

安装依赖:

1
> npm install apollo-server graphql nodemon --save

引入 apollo-server 是因为它是一个开源的且提供强大能力的 GraphQL 服务器。

在package.json中的scripts加入以下脚本:

1
"scripts": { "start": "nodemon -e js, json, graphql" }

2、定义数据

在这个例子中,我们将数据定义为一个JSON对象。

在真实项目中通常是使用数据库来存放数据。

我们把数据定义在index.js中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const users = [
{
id: 1,
fname: 'Richie',
age: 27,
likes: 0,
},
{
id: 2,
fname: 'Betty',
age: 20,
likes: 205,
},
{
id: 3,
fname: 'Joe',
age: 28,
likes: 10,
},
];

const posts = [
{
id: 1,
userId: 2,
body: "Hello how are you?"
},
{
id: 1,
userId: 3,
body: "What's up?"
},
{
id: 1,
userId: 1,
body: "Let's learn GraphQL"
},
]

现在准备工作已经就绪,让我们开始实现这个例子吧。

3、Schema

第一步是要设计我们的 Schema,它包含了两个相互依赖的对象:TypeDefsResolvers

3.1 TypeDefs

之前我们学会了GraphQL的操作类型。为了让服务器知道什么类型是可以识别处理的, 而TypeDefs的作用就是定义这些类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const typeDefs = gql`
type User {
id: Int
fname: String
age: Int
likes: Int
posts: [Post]
}
type Post {
id: Int
user: User
body: String
}
type Query {
users(id: Int!): User!
posts(id: Int!): Post!
}
type Mutation {
incrementLike(fname: String!) : [User!]
}
type Subscription {
listenLikes : [User]
}
`;

首先定义了一个User,包含了 id, fname, age, likes, posts 五个属性,同时规定了这些属性的类型要么是String要么是Int

在GraphQL中支持四种基本类型:String, Int, Float, Boolean
代表这个字段是非空。

另外我们还定义了Query, MutationSubscription 等操作.

  • 定义两个query, 其中users 接收一个id参数, 返回类型是User。 另一个query posts 也接收一个id参数,且返回类型是Post
1
2
3
4
type Query {
users(id: Int!): User!
posts(id: Int!): Post!
}
  • Mutation 名叫:incrementLike, 接收一个参数fname, 并且返回一个User数组。
1
2
3
type Mutation {
incrementLike(fname: String!) : [User!]
}
  • Subscription 名叫: listenLikes, 返回一个User数组。
1
2
3
type Subscription {
listenLikes : [User]
}

现在,我们只是定义了类型,服务器并不知道如何响应客户端请求,所以需要为我们定义的类型提供一些处理逻辑, 我们把这个叫做 Resolvers

3.2 Resolvers

让我们继续编写resolvers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const resolvers = {
Query: {
users(root, args) { return users.filter(user => user.id === args.id)[0] },
posts(root, args) { return posts.filter(post => post.id === args.id)[0] }
},

User: {
posts: (user) => {
return posts.filter(post => post.userId === user.id)
}
},

Post: {
user: (post) => {
return users.filter(user => user.id === post.userId)[0]
}
},

Mutation: {
incrementLike(parent, args) {
users.map((user) => {
if(user.fname === args.fname) user.likes++
return user
})
pubsub.publish('LIKES', {listenLikes: users});
return users
}
},

Subscription: {
listenLikes: {
subscribe: () => pubsub.asyncIterator(['LIKES'])
}
}
};

正如你所看到的,我们创建了6个resolvers函数:

1、users query 根据传入的id返回相对应的 User 对象。
2、posts query 根据传入的id返回对对应的 Post 对象。
3、因为User TypeDefs中包含一个 posts 字段,所以需指定一个resolver来根据user id匹配到该用户发布过的文章。
4、同上,因为Post TypeDefs中包含一个 user 字段,所以需要指定一个resolver根据userId匹配到文章的作者。
5、incrementLike 作为一个变更操作,用于更新用户的 likes 字段。并将更新后的数据通过消息订阅发布模式发送给LIKES事件。
6、listenLikes 作为一个订阅对象,用于监听LIKES事件,并对此作出响应。

什么是订阅发布?
它是由GraphQL实现的基于websocket的实时通讯系统,websocket将这一切变得非常简单。

现在我们已经定义好typedefs和resolver,那么我们的服务也就可以正常🚀了!

startup

打开浏览器,并输入http://localhost:4000/ 回车:

playground

Apollo GraphQL 提供了一个可交互的web应用,帮助我们测试query和mutations操作的结果:

testresult

至此,我们完成了一个简单的 GraphQL 服务器,并且能响应 Query , MutationSubscription 这些请求操作。😁是不是感觉很酷呢?

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。

GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

引用某位大神曾经说过的话:

GraphQL将会取代REST

让我们拭目以待。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK