为什么要学习GraphQL
/ / 点击 / 阅读耗时 16 分钟一、Why GraphQL?
现在有一个需求:根据文章ID来查找对应的 User
信息。
假设数据结构如下:
1 | Users: |
1 | Posts: |
1、传统方式的局限性
常见的方案是:定义两个接口,分别根据user id和post id 获取对应的 user
和 post
信息。
1 | > https://localhost:4000/users/:id |
整个过程如下:
1 | GET https://localhost:4000/posts/1 |
整个过程查询了两次数据库。
还没完,假如前端页面只用到了fname信息呢?
有人说从接口数据里只取 fname
就好啦。 但是你想过没有,接口返回给我们的是用户的所有字段: id, age, likes等。这些字段会随着请求量的增大,会消耗网络带宽,给我们的服务器带来压力。
又有人会说,那我重新定义一个接口专用于返回用户的 fname
, 如:
1 | https://localhost:4000/userByfname/:id |
这样确实能避免我们上面提到的问题,但是会带来一些新的问题: 第一,我们需要根据user的字段创建分别创建接口。第二,随着应用的变化,user的字段信息肯定会发生变化,我们需要同时维护好这些接口,给开发人员带来心智负担。
那么我们如果使用 GraphQL
会怎么样
2、GraphQL 的优势
在 GraphQL
中只需创建一个 query
语句:
1 | { |
如果你仅仅只需用到 age
字段,你可以修改 query
语句:
1 | { |
只需保证 user 字段在 GraphQL
的 schema
中都提前定义好就行。
GraphQL
最大的优势就是既不需要新建接口,也无需与后端服务多次通信,就能精确的获取到我们想要的字段信息
二、GraphQL 基础入门
先从最基本的 GraphQL
操作类型开始:
1、Query
顾名思义,Query用于查询数据。是以字符串的形式通过Post请求中body体中传给服务器。也就是所有的GraphQL类型都是post请求。
以下Query用于从数据库中取出所有 users 的 fname 和 age信息:
1 | query { |
理想情况下,服务器应返回的数据格式是这样的:
1 | data : { |
如果成功,会返回一个含有key 为 data的JSON对象;如果失败,返回的key就是error; 我们可以根据这个来处理错误。
2、Mutation
Mutation 用于向数据库写入数据。可以把它想象为 rest 中的 post 和 put请求:
1 | mutation createUser{ |
定义了一个名为 createUser 的 mutation, 根据传入的fname和age向数据库添加一个user; 同时定义了id作为返回值:
1 | data : { |
3、Subscription
subscription
赋予客户端实时监听数据变化的能力,利用的 websockets
来实现的。
1 | subscription listenLikes { |
上面定义了一个监听 users
的 fname
和 likes
两个字段,一旦其中任何一个字段发生变化,都会以下面的形式通知客户端:
1 | data: { |
假如有一个页面需要实时显示用户 likes
字段的变化,这个特性就变得非常有用。
以上就是 GraphQL
中最基本的操作类型, 虽然介绍的很简单,但是已经足够了。下面我就利用上面介绍的知识设计并且实现一个GraphQL
案例,来直观感受 GraphQL
的强大。
三、GraphQL 实战
首先创建一个 GraphQL
服务来分别响应 query
, mutation
和 subscriptions
这三种操作。
1、初始化工程
1 | > mkdir graphql-server-demo |
安装依赖:
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 | const users = [ |
现在准备工作已经就绪,让我们开始实现这个例子吧。
3、Schema
第一步是要设计我们的 Schema,它包含了两个相互依赖的对象:TypeDefs 和 Resolvers
3.1 TypeDefs
之前我们学会了GraphQL的操作类型。为了让服务器知道什么类型是可以识别处理的, 而TypeDefs的作用就是定义这些类型:
1 | const typeDefs = gql` |
首先定义了一个User,包含了 id
, fname
, age
, likes
, posts
五个属性,同时规定了这些属性的类型要么是String要么是Int。
在GraphQL中支持四种基本类型:String, Int, Float, Boolean。
加!
代表这个字段是非空。
另外我们还定义了Query
, Mutation
和 Subscription
等操作.
- 定义两个query, 其中users 接收一个id参数, 返回类型是User。 另一个query posts 也接收一个id参数,且返回类型是Post
1 | type Query { |
- Mutation 名叫:incrementLike, 接收一个参数fname, 并且返回一个User数组。
1 | type Mutation { |
- Subscription 名叫: listenLikes, 返回一个User数组。
1 | type Subscription { |
现在,我们只是定义了类型,服务器并不知道如何响应客户端请求,所以需要为我们定义的类型提供一些处理逻辑, 我们把这个叫做 Resolvers
3.2 Resolvers
让我们继续编写resolvers:
1 | const resolvers = { |
正如你所看到的,我们创建了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,那么我们的服务也就可以正常🚀了!
打开浏览器,并输入http://localhost:4000/ 回车:
Apollo GraphQL 提供了一个可交互的web应用,帮助我们测试query和mutations操作的结果:
至此,我们完成了一个简单的 GraphQL
服务器,并且能响应 Query
, Mutation
和 Subscription
这些请求操作。😁是不是感觉很酷呢?
四、总结
GraphQL
既是一种用于 API
的查询语言也是一个满足你数据查询的运行时。
GraphQL
对你的 API
中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API
更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
引用某位大神曾经说过的话:
GraphQL将会取代REST
让我们拭目以待。