graphql

https://github.com/graphql-go/graphql
简单的说,GraphQL 是一种描述请求数据方法的语法,通常用于客户端从服务端加载数据。GraphQL 有以下三个主要特征:



它允许客户端指定具体所需的数据。
它让从多个数据源汇总取数据变得更简单。
它使用了类型系统来描述数据。

GraphQL 是由 Facebook 开发的,用于解决他们巨大、老旧的架构的数据请求问题。但是即使是比 Facebook 小很多的 app,也同样会碰上一些传统 REST API 的局限性问题。



例如,假设你要展示一个文章(posts)列表,在每篇文章的下面显示喜欢这篇文章的用户列表(likes),其中包括用户名和用户头像。这个需求很容易解决,你只需要调整你的 posts API 请求,在其中嵌入包括用户对象的 likes 列表,如下所示:

但是现在你是在开发移动 app,加载所有的数据明显会降低 app 的速度。所以你得请求两个接口(API),一个包含了 likes 的信息,另一个不含这些信息(只含有文章信息)。



现在我们再掺入另一种情况:posts 数据是由 MySQL 数据库存储的,而 likes 数据却是由 Redis 存储的。现在你该怎么办?



按着这个剧本想一想 Facebook 的客户端有多少个数据源和 API 需要管理,你就知道为什么现在评价很好的 REST API 所体现出的局限性了。



解决的方案
Facebook 提出了一个概念很简单的解决方案:不再使用多个“愚蠢”的节点,而是换成用一个“聪明”的节点来进行复杂的查询,将数据按照客户端的要求传回。



实际上,GraphQL 层处于客户端与一个或多个数据源之间,它接收客户端的请求然后根据你的设定取出需要的数据。



之前的 REST 模型就好像你预定了一块披萨,然后又要叫便利店送一些日用品上门,接着打电话给干洗店去取衣服。这有三个商店,你就得打三次电话



GraphQL 从某方面来说就像是一个私人助理:你只需要给它这三个店的地址,然后简单地告诉它你需要什么 (“把我放在干洗店的衣服拿来,然后带一块大号披萨,顺便带两个鸡蛋”),然后坐着等他回来就行了。



换句话说,为了让你能和这个神奇的私人助手沟通,GraphQL 建立了一套标准的语言



理论上,一个 GraphQL API 主要由三个部分组成:schema(类型),queries(查询) 以及 resolvers(解析器)。



查询(Queries)
你向你的 GraphQL 私人助理提出的请求就是 query ,query 的形式如下所示:



query {
stuff
}
在这里,我们用 query 关键字定义了一个新的查询,它将取出名叫 stuff 的字段。GraphQL 查询(Queries)最棒之处就是它支持多个字段嵌套查询,我们可以在上面的基础上加深一个层级:



query{
stuff {
eggs
shirt
pizza
}
}
正如你所见,客户端在查询的时候不需要关心数据是来自于哪一个“商店”的。你只需要请求你要的数据,GraphQL 服务端将会完成其它所有的工作。



还有一点值得注意,query 字段也可以指向一个数组。例如,以下是一个查询一个文章列表的常用模式:



query {
posts { # this is an array
title
body
author { # we can go deeper!
name
avatarUrl
profileUrl
}
}
}
Query 字段也支持使用参数。如果我想展示一篇特别的文章,我可以将 id 参数放在 post 字段中:



query {
post(id: “123foo”){
title
body
author{
name
avatarUrl
profileUrl
}
}
}
最后,如果我想让 id 参数能动态改变,我可以定义一个变量,然后在 query 字段中重用它。(请注意,我们在 query 字段处也要定义一次这个变量的名字)



query getMyPost($id: String) {
post(id: $id){
title
body
author{
name
avatarUrl
profileUrl
}
}
}
有个很好的方式来实践这些方法:使用 GitHub’s GraphQL API Explorer 。 https://developer.github.com/v4/explorer/ 例如,你可以尝试下面的查询:



query {
repository(owner: “graphql”, name: “graphql-js”){
name
description
}
}



The Anatomy of a GraphQL Query



你可以读读这篇超棒的文章《Anatomy of a GraphQL Query》,了解更多 GraphQL 查询的知识。



解释器(Resolvers)
除非你给他们地址,否则即使是这个世界上最好的私人助理也不能去拿到干洗衣物。



同样的,GraphQL 服务端并不知道要对一个即将到来的查询做什么处理,除非你使用 resolver 来告诉他。



一个 resolver 会告诉 GraphQL 在哪里以及如何去取到对应字段的数据。例如,下面是之前我们取出 post 字段例子的 resolver(使用了 Apollo 的 GraphQL-Tools ):



Query: {
post(root, args) {
return Posts.find({ id: args.id });
}
}
在这个例子中,我们将 resolver 放在 Query 中,因为我们想要直接在根层级查询 post。但你也可以将 resolver 放在子字段中,例如查询 post(文章)的 author(作者)字段可以按照下面的形式:



Query: {
post(root, args) {
return Posts.find({ id: args.id });
}
},
Post: {
author(post) {
return Users.find({ id: post.authorId})
}
}
还有,resolver 不仅仅只能返回数据库里的内容,例如,如果你想为你的 Post 类型加上一个 commentsCount(评论数量)属性,可以这么做:



Post: {
author(post) {
return Users.find({ id: post.authorId})
},
commentsCount(post) {
return Comments.find({ postId: post.id}).count()
}
}
理解这里的关键在于:对于 GraphQL,你的 API 结构与你的数据库结构是解耦的。换一种说法,我们的数据库中可能根本就没有 author 和 commentsCount 这两个字段,但是我们可以通过 resolver 的力量将它们“模拟”出来。



正如你所见,我们可以在 resolver 中写任何你想写的代码。因此,你可以通过改变 resolver 任意地修改数据库中的内容,这种形式也被称为 mutation resolver。



类型(Schema)
GraphQL 的类型结构系统可以让很多事情都变得可行。我今天的目标仅仅是给你做一个快速的概述而不是详细的介绍,所以我不会在这个内容上继续深入。



话虽如此,如果你想了解更多这方面的信息,我建议你阅读 GraphQL 官方文档。
GraphQL 与图形数据库有什么关系?
它们真的没有关系,GraphQL 与诸如 Neo4j 之类的图形数据库没有任何关系。名称中的 “Graph” 是来自于 GraphQL 使用字段与子字段来遍历你的 API 图谱;“QL” 的意思是“查询语言”(query language)。



我用 REST 用的很开心,为什么我要切换成 GraphQL 呢?
如果你使用 REST 还没有碰上 GraphQL 所解决的那些痛点,那当然是件好事啦!



但是使用 GraphQL 来代替 REST 基本不会对你 app 的用户体验产生任何影响,所以“切换”这件事并不是所谓“生或死”的抉择。话虽如此,我还是建议你如果有机会的话,先在项目里小范围地尝试一下 GraphQL 吧。



如果我不用 React、Relay 等框架,我能使用 GraphQL 吗?
当然能!因为 GraphQL 仅仅是一个标准,你可以在任何平台、任何框架中使用它,甚至在客户端中也同样能应用它(例如,Apollo 有针对 web、iOS、Angular 等环境的 GraphQL 客户端)。你也可以自己去做一个 GraphQL 服务端。



GraphQL 是 Facebook 做的,但是我不信任 Facebook
再强调一次,GraphQL 只是一个标准,这意味着你可以在不用 Facebook 一行代码的情况下实现 GraphQL。



并且,有 Facebook 的支持对于 GraphQL 生态系统来说是一件好事。关于这块,我相信 GraphQL 的社区足够繁荣,即使 Facebook 停止使用 GraphQL,GraphQL 依然能够茁壮成长。



“让客户端自己请求需要的数据”这整件事情听起来似乎不怎么安全……
你得自己写自己的 resolver,因此在这个层面上是否会出现安全问题完全取决于你。



例如,为了防止客户端一遍又一遍地请求查询记录造成 DDOS 攻击,你可以让客户端指定了一个 limit 参数去控制它接受数据的数量。



那么我如何上手 GraphQL?
通常来说,一个 GraphQL 驱动的 app 起码需要以下两个组件:



一个 GraphQL 服务端 来为你的 API 提供服务。
一个 GraphQL 客户端 来连接你的节点。



现在你应该对 GraphQL 有了一个恰当的认识,下面让我们来介绍一下 GraphQL 的主要平台与产品。



GraphQL 服务端
万丈高楼平地起,盖起这栋楼的第一块砖就是一个 GraphQL 服务端。 GraphQL 它本身仅仅是一个标准,因此它敞开大门接受各种各样的实现。



GraphQL-JS (Node)
它是 GraphQL 的最初的实现。你可以将它和 express-graphql 一起使用,创建你自己的 API 服务 。



GraphQL-Server (Node)
Apollo 团队也有他们自己的一站式 GraphQL 服务端实现。它虽然还没有像 GraphQL-JS 一样被广泛使用,但是它的文档、支持都做得很棒,使用它能快速取得进展。



其它平台
GraphQL.org 列了一个清单: GraphQL 在其它平台下的实现清单 (包括 PHP、Ruby 等)。



GraphQL 客户端
虽然你不使用客户端类库也可以很好地查询 GraphQL API,但是一个相对应的客户端类库将会让你的开发更加轻松。



Relay
Relay 是 Facebook 的 GraphQL 工具。我还没用过它,但是我听说它主要是为了 Facebook 自己的需求量身定做的,可能对大多数的用户来说不是那么人性化。



Apollo Client
在这个领域的最新参赛者是 Apollo,它正在迅速发展。典型的 Apollo 客户端技术栈由以下两部分组成:



Apollo-client,它能让你在浏览器中运行 GraphQL 查询,并存储数据。(它还有自己的开发者插件)。
与你用的前端框架的连接件(例如 React-Apollo、Angular-Apollo 等)。
另外,在默认的情况下 Apollo 客户端使用 Redux 存储数据。这点很棒,Redux 本身是一个有着丰富生态系统的超棒的状态管理类库。



pollo 在 Chrome 开发者工具中的插件



开源 App
虽然 GraphQL 还属于新鲜事物,但是它已经被一些开源 app 使用了。



VulcanJS



[
image
](https://link.juejin.im?target=http%3A%2F%2Fvulcanjs.org)


首先我得声明一下,我是 VulcanJS 的主要维护者。我创建 VulcanJS 是为了让人们在不用写太多样板代码的情况下充分享受 React、GraphQL 技术栈的好处。你可以把它看成是“现代 web 生态系统的 Rails”,让你可以在短短几个小时内做出你的 CRUD(增删查改)型 app。(例如 Instagram clone)



Gatsby
Gatsby 是一个 React 静态网站生成器,它现在是基于 GraphQL 1.0 版本 开发。它一眼看上去像个奇怪的大杂脍,但其实它的功能十分强大。Gatsby 在构建过程中,可以从多个 GraphQL API 取得数据,然后用它们创建出一个全静态的无后端 React app。



其它的 GraphQL 工具
GraphiQL
GraphiQL 是一个非常好用的基于浏览器的 IDE,它可以方便你进行 GraphQL 端点查询。



[
image
](https://link.juejin.im?target=https%3A%2F%2Fgithub.com%2Fgraphql%2Fgraphiql)


GraphiQL



DataLoader
由于 GraphQL 的查询通常是嵌套的,一个查询可能会调用很多个数据库请求。为了避免影响性能,你可以使用一些批量出入库框架和缓存库,例如 Facebook 开发的 DataLoader。



Create GraphQL Server
Create GraphQL Server 是一个简单的命令行工具,它能快速地帮你搭建好基于 Node 服务端与 Mongo 数据库的 GraphQL 服务端。



GraphQL 服务
最后,这儿列了一些 GraphQL BAAS(后台即服务)公司,它们已经为你准备好了服务端的所有东西。这可能是一个让你尝试一下 GraphQL 生态系统的很好的方式。



GraphCool
一个由 GraphQL 和 AWS Lambda 组成的一个弹性后端平台服务,它提供了开发者免费计划。



Scaphold
另一个 GraphQL BAAS 平台,它也提供了免费计划。与 GraphCool 相比,它提供了更多的功能。(例如定制用户角色、常规操作的回调钩子等)




image




下面是一些能让你学习 GraphQL 的资源。



GraphQL.org
GraphQL 的官方网站,有许多很好的文档供你学习。



LearnGraphQL
LearnGraphQL 是由 Kadira 员工共同制作的课程。



LearnApollo
LearnApollo 是由 GraphCool 制作的免费课程,是对于 LearnGraphQL 课程的一个很好的补充。



Apollo 博客
Apollo 的博客有成吨的干货,有很多关于 Apollo 和 GraphQL 的超棒的文章。



GraphQL 周报
由 Graphcool 团队策划的一个简报,其内容包括任何有关 GraphQL 的信息。



Hashbang 周报
另一个不错的简报,除了 GraphQL 的内容外,还涵盖了 React、Meteor。



Awesome GraphQL
一个关于 GraphQL 的链接和资源的很全面的清单。




image




你如何实践你刚学到的 GraphQL 的知识呢?你可以尝试下面这些方式:



Apollo + Graphcool + Next.js
如果你对 Next.js 与 React 很熟悉,这个例子将会帮助你使用 Graphcool 很快的搭建好你的 GraphQL 端点,并在客户端使用 Apollo 进行查询。



VulcanJS
Vulcan 教程将会引导你创建一个简单的 GraphQL 数据层,既有服务端部分也有客户端部分。因为 Vulcan 是一个一站式平台,所以这种无需任何配置的方式是一种很好的上手途径。如果你需要帮助,请访问我们的 Slack 栏目!



GraphQL & React 教程
Chroma 博客有一篇《分为六部的教程》,讲述了如何按照组件驱动的开发方式来构建一个 React/GraphQL app。



总结
当你刚开始接触 GraphQL 可能会觉得它非常复杂,因为它横跨了现代软件开发的众多领域。但是,如果你稍微花点时间去明白它的原理,我认为你可以找到它很多的可取之处。



所以不管你最后会不会用上它,我相信多了解了解 GraphQL 是值得的。越来越多的公司与框架开始接受它,过几年它可能会成为 web 开发的又一个重要组成部分。



https://graphql.org/



GraphQL和RESTful的比较
引言
在2017年5月,Github也发布了它第四版的API,采用的正是GraphQL,并且推荐集成商在GitHub App中使用最新版本的GraphQL API v4。



思考
GitHub的REST API已经非常完善,设计得很优秀,很多公司开发自己的REST API时都会参考GitHub的实现。



那GitHub为什么选择GraphQL?



RESTful的一些不足
扩展性,单个RESTful接口返回数据越来越臃肿
比如获取用户信息/users/:id,最初可能只有id、昵称,但随着需求的变化,用户所包含的字段可能会越来越多,年龄、性别、头像、经验、等级,等等等等。



而具体到某个前端页面,可能只需要其中一小部分数据,这样就会增加网络传输量,前端获取了大量不必要的数据。



某个前端展现,实际需要调用多个独立的RESTful API才能获取到足够的数据
比如一个文章详情页,最初可能只需要文章内容,那么前端就调用/articles/:aid获取到文章内容来展现就行了



但随着需求的演进,产品可能会希望加上作者信息(昵称、头像等),这时前端又需要在获取文章详情后,根据其中的作者id字段继续获取作者相关的信息,/user/:uid



然后,需求又变化了,产品希望在加上这篇文章的评论,这时前端需要继续调用/comment/:aid来拉取评论列表



对于Web前端而言,由于ajax技术的存在,这种的请求数据方式,也就开发上稍微麻烦些,并不会造成太大的问题;但对于App来说,渲染的方式不同,必须要拉取的全部的数据之后,才能绘制界面,就会导致这个界面必须要等到所有3个RESTful接口的返回数据都拿到,才能进行绘制。



GraphQL的一些优点
所见即所得



查询的返回结果就是输入的查询结构的精确映射



查询:



{
user(uid:1) {
uid
name
}
}
返回:



{
“data”: {
“user”: {
“uid”: “1”,
“name”: “xxx”
}
}
}
减少网络请求次数



如果设计的数据结构是从属的(例如,上文中文章的作者信息),直接就能在查询语句中指定



{
article(aid:1) {
title
content
author {
uid
name
}
}
}
即使数据结构是独立的,也可以在查询语句中指定上下文,只需要一次网络请求,就能获得资源和子资源的数据(例如,上文中文章的评论信息)



{
article(aid:1) {
title
content
author {
uid
name
}
},
comment {
content,
author {
uid
name
}
}
}
代码即文档
GraphQL会把schema定义和相关的注释生成可视化的文档,从而使得代码的变更,直接就反映到最新的文档上,避免RESTful中手工维护可能会造成代码、文档不一致的问题。



参数类型强校验
RESTful方案本身没有对参数的类型做规定,往往都需要自行实现参数的校验机制,以确保安全。



但GraphQL提供了强类型的schema机制,从而天然确保了参数类型的合法性。



使用心得
从Facebook最初开发GraphQL的目的,和笔者实际使用的情况而言,GraphQL还是存在一些缺点的,完全替代RESTful作为一种新的接口规范还有些为时过早.



GraphQL作为RESTful的一种辅助工具,尤其是针对前端App在复杂页面,本来要调用有上下文关系的多次RESTful请求时,采用GraphQL,只需要一次请求,就可以拿回所需的全部数据(有点JSON直出的意思),还是可以起到非常好的效果,大大提升App的性能。



RESTful API不足
前端和后端对于接口的控制权是交叉冲突的,往往一方改动不算,前端改动一个字段,连带着后端也需要改动,反之亦是
前端对于真正用到的字段是没有直观映像的,仅仅通过url地址,无法预测也无法回忆返回的字段数目和字段是否有效,接口返回50个字段,但却只用5个字段,造成字段冗余,扩展性差,单个RESTful接口返回数据越来越臃肿。
API聚合问题,某个前端展现,实际需要调用多个独立的RESTful API才能获取到足够的数据
前后端字段频繁改动,导致类型不一致,错误的数据类型可能会导致网站出错
尤其是在业务多变的场景中,很难在保证工程质量的同时快速满足业务需求
案例1:比如获取用户信息/users/:id,最初可能只有id、昵称,但随着需求的变化,用户所包含的字段可能会越来越多,年龄、性别、头像、经验、等级,等等等等。而具体到某个前端页面,可能只需要其中一小部分数据,这样就会增加网络传输量,前端获取了大量不必要的数据。



案例2:比如一个文章详情页,最初可能只需要文章内容,那么前端就调用/articles/:aid获取到文章内容来展现就行了



但随着需求的演进,产品可能会希望加上作者信息(昵称、头像等),这时前端又需要在获取文章详情后,根据其中的作者id字段继续获取作者相关的信息,/user/:uid



然后,需求又变化了,产品希望在加上这篇文章的评论,这时前端需要继续调用/comment/:aid来拉取评论列表



对于Web前端而言,由于ajax技术的存在,这种的请求数据方式,也就开发上稍微麻烦些,并不会造成太大的问题;但对于App来说,渲染的方式不同,必须要拉取的全部的数据之后,才能绘制界面,就会导致这个界面必须要等到所有3个RESTful接口的返回数据都拿到,才能进行绘制。



关于RESTful可参考以下文章:



理解RESTful架构
RESTful API 设计指南
GraphQL优点
GraphQL是Facebook开源的API查询语言,类似于数据库中的SQL。作为比较,RESTful API依赖于后端隐式的被动的数据约定,GraphQL更加显式,在获取数据和更新数据时更加主动,所见即所得。
从调用者的角度看,GraphQL更加依赖于前端,相当于是把后端的部分SQL能力转移到了前端。GraphQL可以通过查询规则,而不是通过特定的url地址来对后端的数据源进行调用,并且可以选择需要用到的字段,后端也只返回这些字段。相当于数据库SQL,但是SQL的查询对象只能是数据库,而GraphQL的查询对象是数据源,这个数据源可以是HTTP接口、数据库查询集合、静态json文件、另外一个api的数据源,特别的灵活。
GraphQL更强大的一点是可以实现对多个数据源的调用,合并成一份完整的数据给前端使用。
策略1:所见即所得



查询的返回结果就是输入的查询结构的精确映射



// 查询
{
user(uid:1) {
uid
name
}
}
// 结果
{
“data”: {
“user”: {
“uid”: “1”,
“name”: “xxx”
}
}
}
策略2:减少网络请求次数



如果设计的数据结构是从属的(例如,上文中文章的作者信息),直接就能在查询语句中指定



{
article(aid:1) {
title
content
author {
uid
name
}
}
}
即使数据结构是独立的,也可以在查询语句中指定上下文,只需要一次网络请求,就能获得资源和子资源的数据(例如,上文中文章的评论信息)



{
article(aid:1) {
title
content
author {
uid
name
}
},
comment {
content,
author {
uid
name
}
}
}
策略3:代码即文档



GraphQL会把schema定义和相关的注释生成可视化的文档,从而使得代码的变更,直接就反映到最新的文档上,避免RESTful中手工维护可能会造成代码、文档不一致的问题。



策略4:参数类型强校验



RESTful方案本身没有对参数的类型做规定,往往都需要自行实现参数的校验机制,以确保安全。



但GraphQL提供了强类型的schema机制,从而天然确保了参数类型的合法性。



总结
从Facebook最初开发GraphQL的目的,和笔者实际使用的情况而言,GraphQL还是存在一些缺点的,完全替代RESTful作为一种新的接口规范还有些为时过早.



GraphQL作为RESTful的一种辅助工具,尤其是针对前端App在复杂页面,本来要调用有上下文关系的多次RESTful请求时,采用GraphQL,只需要一次请求,就可以拿回所需的全部数据(有点JSON直出的意思),还是可以起到非常好的效果,大大提升App的性能。



什么是GraphQL?
GraphQL是Facebook开源的API查询语言,类似于数据库中的SQL。作为比较,RESTful API依赖于后端隐式的被动的数据约定,GraphQL更加显式,在获取数据和更新数据时更加主动,所见即所得。GraphQL官方网址



RESTful的一些不足



  1. 扩展性,单个RESTful接口返回数据越来越臃肿



比如获取用户信息/users/:id,最初可能只有id、昵称,但随着需求的变化,用户所包含的字段可能会越来越多,年龄、性别、头像、经验、等级,等等。



而具体到某个前端页面,可能只需要其中一小部分数据,这样就会增加网络传输量,前端获取了大量不必要的数据。




  1. 某个前端展现,实际需要调用多个独立的RESTful API才能获取到足够的数据



比如一个文章详情页,最初可能只需要文章内容,那么前端就调用/articles/:aid获取到文章内容来展现就行了



但随着需求的演进,产品可能会希望加上作者信息(昵称、头像等),这时前端又需要在获取文章详情后,根据其中的作者id字段继续获取作者相关的信息,/user/:uid



然后,需求又变化了,产品希望在加上这篇文章的评论,这时前端需要继续调用/comment/:aid来拉取评论列表



对于Web前端而言,由于ajax技术的存在,这种的请求数据方式,也就开发上稍微麻烦些,并不会造成太大的问题;但对于App来说,渲染的方式不同,必须要拉取的全部的数据之后,才能绘制界面,就会导致这个界面必须要等到所有3个RESTful接口的返回数据都拿到,才能进行绘制。
GraphQL优点



  1. 所见即所得



查询的返回结果就是输入的查询结构的精确映射




  1. 减少网络请求次数



如果设计的数据结构是从属的,直接就能在查询语句中指定;即使数据结构是独立的,也可以在查询语句中指定上下文,只需要一次网络请求,就能获得资源和子资源的数据。




  1. 代码即文档



GraphQL会把schema定义和相关的注释生成可视化的文档,从而使得代码的变更,直接就反映到最新的文档上,避免RESTful中手工维护可能会造成代码、文档不一致的问题。




  1. 参数类型强校验



RESTful方案本身没有对参数的类型做规定,往往都需要自行实现参数的校验机制,以确保安全。



但GraphQL提供了强类型的schema机制,从而天然确保了参数类型的合法性。
GraphQL适用场景
从Facebook最初开发GraphQL的目的,和笔者实际使用的情况而言,GraphQL还是存在一些缺点的,完全替代RESTful作为一种新的接口规范还有些为时过早。



GraphQL作为RESTful的一种辅助工具,尤其是针对前端App在复杂页面,本来要调用有上下文关系的多次RESTful请求时,采用GraphQL,只需要一次请求,就可以拿回所需的全部数据(有点JSON直出的意思),还是可以起到非常好的效果,大大提升App的性能



GraphQL目前被认为是革命性的API工具,因为它可以让客户端在请求中指定希望得到的数据,而不像传统的REST那样只能呆板地在服务端进行预定义。这样它就让前、后端团队的协作变得比以往更加的通畅,从而能够让组织更好地运作。而实际上,GraphQL与REST都是基于HTTP进行数据的请求与接收,而且GraphQL也内置了很多REST模型的元素在里面。



那么在技术层面上,GraphQL和REST这两种API模型到底有什么异同呢?我的观点是,他们归根到底其实没多大区别,只不过GraphQL做了一些小改进,使得开发体验产生了较大的改变。



我会从API的各个组件分别来讨论GraphQL和REST都分别是如何处理的。



资源(Resources)
REST的核心思想就是资源,每个资源都能用一个URL来表示,你能通过一个GET请求访问该URL从而获取该资源。根据当今大多数API的定义,你很有可能会得到一份JSON格式的数据响应,整个过程大概是这样:



GET /books/1
{
“title”: “Black Hole Blues”,
“author”: {
“firstName”: “Janna”,
“lastName”: “Levin”
}
// … more fields here
}
注:上面的例子里的”author”也会作为一个单独的资源在其他REST API中被用到



需要注意的是,在REST中,一个资源的种类与你获取它的方式是耦合的,比如上面这个例子中的API就可以称之为“book端点”(book endpoint)。



在这一点上GraphQL就大为不同,因为在GraphQL里这两个概念是完全分离的。比如说在你的schema定义中,你可能会有Book和Author两个类型(type):



type Book {
id: ID
title: String
published: Date
price: String
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
books: [Book]
}
注意这里我们虽然定义了数据类型,但却不知道该如何获取这些数据。这是REST与GraphQL的一个核心差异:资源的描述信息与其获取方式相分离。



如果要去访问某个特定的book或者author资源,我们需要在schema中创建一个Query类型:



type Query {
book(id: ID!): Book
author(id: ID!): Author
}
然后我们就可以像REST那样发送请求了:



GET /graphql?query={ book(id: “1”) { title, author { firstName } } }
{
“title”: “Black Hole Blues”,
“author”: {
“firstName”: “Janna”,
}
}
虽然都是通过请求某个URL来得到相同的响应,但这里我们已经看到GraphQL与REST的差异之处了。



首先,我们看到GraphQL的URL请求里面指定了我们所需要的资源以及在该资源中我们所关心的字段。另外,我们是主动请求得到与book相关的author数据的,而不是服务端替我们决定的。



最重要的是,在请求中我们不需要关心资源的主键和资源之间的关系定义,我们可以通过除id以外的其他字段来获取到相同的Book资源。



小结
现在我们知道的异同点有:
相同点:都有资源这个概念,而且都能通过ID去获取资源。
相同点:都可以通过HTTP GET方式来获取资源
相同点:都可以使用JSON作为响应格式
差异点:在REST中,你所访问的路径就是该资源的唯一标识(ID);在GraphQL中,该标识与访问方式并不相关
差异点:在REST中,资源的返回结构与返回数量是由服务端决定;在GraphQL,服务端只负责定义哪些资源是可用的,由客户端自己决定需要得到什么资源



如果你已经用过GraphQL和REST,以上这些对你来说肯定相当简单。如果你之前没有用过GraphQL,你可以在到这里来实际体验一下。



路由(URL Route) vs. GraphQL Schema
一个具有可预见性的API才是好的API。因为你通常会把一个API当做程序的一部分来使用,所以你必须要知道它需要接收什么参数并预期能够获取到什么样的结果。



这时候,对API的访问描述信息就显得很重要。通常我们会通过阅读API文档来获取信息,但通过GraphQL的Introspection机制、以及Swagger这样的REST API工具,这些信息就能可以自动获取到。



如今的REST API通常会由一系列的URL端点组成:



GET /books/:id
GET /authors/:id
GET /books/:id/comments
POST /books/:id/comments
你可以把这种API的形态称之为线性结构——因为这就是一个列表嘛。当你要获取数据时,第一个事情就是搞清楚你要访问的是哪个端点。



而在GraphQL中——其实在上一节里你也看到了——可以通过查看GraphQL schema获得相关信息:



type Query {
book(id: ID!): Book
author(id: ID!): Author
}
type Mutation {
addComment(input: AddCommentInput): Comment
}
type Book { … }
type Author { … }
type Comment { … }
input AddCommentInput { … }
REST会使用类似GET、POST这样的动词去请求相同的URL来表示这到底是一个读操作还是写操作,而GraphQL会使用不同的预定义类型:Mutation和Query。在GraphQL请求中,你可以通过不同的关键字进行不同的操作:



query { … }
mutation { … }
如果你想知道更多关于query的用法,请看我之前写的文章“The Anatomy of a GraphQL Query”.



这里的Query类型定义与上面的REST路由是完全契合的,同样表示了数据的访问入口,因此这是GraphQL中最能与REST的URL端点所对应的概念。



如果是对资源的简单查询,GraphQL与REST是类似的,都是通过指定资源的名称以及相关参数来取得,但不同的是,在GraphQL中,你可以根据资源之间的关联关系来发起一个复杂请求,而在REST中你只能定义一些特殊的URL参数来获取到特殊的响应,或者是通过发起多个请求、再自行把响应得到的数据进行组装才行。



小结
REST对数据的描述形式是一连串的URL端点,而GraphQL则是由相互之间有所关联的schema组成。
相同点:REST API的URL端点列表与GraphQL的Query/Mutation中的字段类似,都表示数据的访问入口。
相同点:都能用不同的方式描述一个API请求到底是读操作还是写操作。
差异点:GraphQL让你可以通过一个资源入口访问到关联的其他资源,只要事先在schema中定义好资源之间的关系即可;而REST则提供了多个URL端点来获取相关的资源。
差异点:在GraphQL中,Query类型可以在一个请求的根节点中被访问,除此以外它跟其他类型没有区别,比如你也可以对一个query中的字段添加参数。而在REST中,即使响应结果是嵌套关系,但在请求中并没有嵌套的概念。
差异点:REST使用POST这样的HTTP方法名称来定义写操作,GraphQL则是查询结构中的关键字。



正因为上述的第一个点,人们通常会把Query类型中的字段称为GraphQL中的“端点”或“查询条件”。虽然这是一个合理的解释,但同时也会对其他人造成误导,让人以为Query类型是一个非常特殊的类型。



路由处理器(Route Handlers)vs. 解析器(Resolvers)
想象一下,当你调用一个API的时候,实际上会发生什么事情?嗯,应该是在服务器上面执行了一些代码来处理这个请求,可能是进行了一些计算,可能从数据库中加载了一些数据,也可能是再次调用了一个别的API。虽然总的来说,作为调用方你并不需要知道内部发生了什么事情,不过由于REST和GraphQL都提供了标准的API实现方法,我们可以通过对比来感受一下两者之间的差异。



因为我比较熟悉JavaScript语言,所以在这个章节中我会使用它来做例子,但你也可以使用其他主流编程语言来实现REST或者GraphQL的API。为了突出重点,我会忽略掉一些构建服务用的过程代码。



首先使用Express实现一个hello world:



app.get(‘/hello’, function (req, res) {
res.send(‘Hello World!’)
})
这里我们得到了一个可以返回“Hello World!”这个字符串的/hello端点。从这个例子我们可以看到一个REST API请求的的生命周期:



服务器收到请求并提取出HTTP方法名(比如这里就是GET方法)与URL路径
API框架找到提前注册好的、请求路径与请求方法都匹配的代码
该段代码被执行,并得到相应结果
API框架对结果进行序列化,添加上适当的状态码与响应头后,返回给客户端
GraphQL差不多也是这样工作的,我们来看下这个对应的hello world例子:



const resolvers = {
Query: {
hello: () => {
return ‘Hello world!’;
},
},
};
我们看到,这里并没有针对某个URL路径提供函数,而是把Query类型中的hello字段映射到一个函数上了。在GraphQL中这样的函数我们称之为解析器(Resolver)。



然后我们就可以这样发起一个查询:



query {
hello
}
至此,总结一下服务器对一个GraphQL请求的执行过程:



服务器收到HTTP请求,取出其中的GraphQL查询
遍历查询语句,调用里面每个字段所对应的Resolver。在这个例子里,只有Query这个类型中的一个字段hello
Resolver函数被执行并返回相应结果
GraphQL框架把结果根据查询语句的要求进行组装
因此我们将会得到如下响应:



{ “hello”: “Hello, world!” }
这里有个小技巧:我们其实可以多次调用同一个Resolver:



query {
hello
secondHello: hello
}
在这个例子中的生命周期跟上面的是类似的,但因为我们通过别名来两次请求了同一个字段,所以对应Resolver函数hello也会被执行两次。虽然这个例子举得不是很好,不过这里主要想表达的是在一个请求中可以解析多个字段,即使是相同的字段也可以在查询的不同地方被多次访问。



再来看下“嵌套”解析器是怎样的:



{
Query: {
author: (root, { id }) => find(authors, { id: id }),
},
Author: {
posts: (author) => filter(posts, { authorId: author.id }),
},
}
这样的解析器可以处理如下查询请求:



query {
author(id: 1) {
firstName
posts {
title
}
}
}
即使解析器的结构是扁平的,但由于它们被不同的类型所引用,所以你还是可以利用它们来实现嵌套查询。想知道GraphQL如何执行请求,请进一步阅读这篇文章:“GraphQL Explained”



点击这里可以查看完整的例子并体验不同的查询效果



上图形象地说明了使用REST和GraphQL进行多种资源获取的方式的差异



小结
总的来说,REST和GraphQL都提供了很好的API调用方式。如果你对如何构建一个REST API足够熟悉,使用GraphQL来实现同样的API功能对你来说并不是一件难事。但GraphQL的一大优势是让你可以在不需要发起多次请求的情况下调用多个函数来获取资源数据。



相同点:REST的端点与GraphQL查询字段都在服务端调起函数执行。
相同点:REST和GraphQL都使用框架和类库来进行一些通用的网络协议处理。
差异点:一个REST请求对应一个路由处理器(Route Handler),而一个GraphQL的请求可以唤起多个解析器(Resolver)在一次响应中访问多种资源。
差异点:REST需要你自己构建整个请求的响应,而GraphQL的请求响应是由查询方指定结构、并由GraphQL进行构建组装的。



你可以把GraphQL理解为一个可以在一次请求中进行多个端点调用的系统,差不多算是REST的多路复用版。



综上所述
GraphQL里面还有很多东西由于篇幅限制这里并没有涉及,像对象识别、超媒体,以及缓存。这些话题以后有机会我们再来介绍。但我希望你通过本文对GraphQL有一个基本认识,知道它跟REST实际上是有很多概念上的相通。



我个人认为,GraphQL是有一些独特的优势的。特别是使用一系列小的解析器函数来构建一个完整的API这一点,实在是非常酷。这精简了不同场景下形态各异的API数量,并避免让API消费者取到对它来说并没有用的冗余数据。



但在另一方面,GraphQL还并不像REST那样有那么丰富的工具体系。比方说,你就不能像REST那样轻易地对HTTP结果进行缓存。不过目前GraphQL社区正在努力地丰富和完善这些工具和基础建设,就缓存这个例子,其实你也可以通过Apollo Client和Relay这样的工具去缓存GraphQL结果。



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



基于node的服务端开发中,GraphQL技术较为成熟常用。Golang作为高性能的现代语言在web服务器开发中也有很高的使用率,配合使用真香。
根据GraphQL 官网代码中找到graphql-go:一个 Go/Golang 的 GraphQL 实现。
这个库还封装 graphql-go-handler:通过HTTP请求处理GraphQL查询的中间件。
package main



import (
“encoding/json”
“fmt”
“log”



"github.com/graphql-go/graphql" )


func main() {
// Schema
fields := graphql.Fields{
“hello”: &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return “world”, nil
},
},
}
rootQuery := graphql.ObjectConfig{Name: “RootQuery”, Fields: fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf(“failed to create new schema, error: %v”, err)
}



// Query
query := `
{
hello
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON) // {“data”:{“hello”:”world”}} } 复制代码开始吧 go的版本建议1.12以后的,根据创建的项目都应该有一个go.mod进行依赖包的管理,说一说go mod 这里不解释了。 根据上面一段示例知道,在使用时需要有Schema、Query一起解析生成查询文档对象后,使用查询器对查询文档对象进行解析。 第一步 一般推荐SDL语法的.graphql文件,更强类型要求需要编写类似以下代码。 // schemaQuery 查询函数路由 var schemaQuery= graphql.NewObject(graphql.ObjectConfig{
Name: graphql.DirectiveLocationQuery,
Description: "查询函数",
Fields: graphql.Fields{
// 简单输出字符串
"hello": &graphql.Field{
Type: graphql.String, // 返回类型
Description: "输出 world", // 解释说明
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 根据查询处理函数方法进行返回对应类型的数据值
return "word", nil
},
},
// 参数直接输出
"echo": &graphql.Field{
Type: graphql.String, // 返回类型
Description: "参数直接输出", // 解释说明
Args: graphql.FieldConfigArgument{ // 参数接收
"toEcho": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String), // 接收参数类型,表示非空字符串
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 根据查询处理函数方法进行返回对应类型的数据值
return p.Args["toEcho"].(string), nil
},
},
}, }) 复制代码第二步 进行Schema文档组合 // Schema var Schema graphql.Schema Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: schemaQuery, // 查询函数Schema
Mutation: schemaMutation, // 如果有提交函数Schema }) 复制代码第三步 获取参数与Schema对应查询函数执行 // ExecuteQuery GraphQL查询器 func ExecuteQuery(params *graphql.Params) *graphql.Result {
params.Schema = schema
return graphql.Do(*params) } 复制代码第四步 在路由入口解析参数,并使用查询 // 请求入口 http.HandleFunc("/graphql", func(res http.ResponseWriter, req *http.Request) {
// JSON格式输出,状态200
res.Header().Add("Content-Type", "application/json; charset=utf-8")
res.WriteHeader(http.StatusOK)
// 解析请求参数,得到Query、Variables、OperationName三个参数
opts := ParseRequestOptions(req) // 需要自己写函数处理得到参数
// 进行graphql查询Query
result := ExecuteQuery(&graphql.Params{ // 使用查询
RequestString: opts.Query,
VariableValues: opts.Variables,
OperationName: opts.OperationName,
Context: req.Context(),
})
// 错误输出
if len(result.Errors) > 0 {
log.Printf("errors: %v", result.Errors)
}
// map转json序列化
buff, _ := json.Marshal(result)
_, _ = res.Write(buff) }) 复制代码大致通过上面四步完成,简单使用graphql进行接口操作。 查询选择字段 符合graphql的设计是根据对应查询字段出对应于字段的信息,不是查询全部字段才根据字段返回。 应该在获取查询时的字段后对应进行SQL字段查询。 获取提交查询的字段就比较麻烦,自己处理遍历SelectionSet得到。 根据大多数问题总结,下面提供两种方法函数解析获取。 // SelectionFieldNames 查询选择字段 func SelectionFieldNames(fieldASTs []*ast.Field) []string{
fieldNames := make([]string, 0)
for _, field := range fieldASTs {
selections := field.SelectionSet.Selections
for _, selection := range selections {
fieldNames = append(fieldNames, selection.(*ast.Field).Name.Value)
}
}
return fieldNames }


// selectedFieldsFromSelections 提交查询的字段列表
func selectedFieldsFromSelections(params graphql.ResolveParams, selections []ast.Selection) (selected map[string]interface{}, err error) {
selected = map[string]interface{}{}
for _, s := range selections {
switch s := s.(type) {
case *ast.Field:
if s.SelectionSet == nil {
selected[s.Name.Value] = true
} else {
selected[s.Name.Value], err = selectedFieldsFromSelections(params, s.SelectionSet.Selections)
if err != nil {
return
}
}
case *ast.FragmentSpread:
n := s.Name.Value
frag, ok := params.Info.Fragments[n]
if !ok {
err = fmt.Errorf(“getSelectedFields: no fragment found with name %v”, n)
return
}
selected[s.Name.Value], err = selectedFieldsFromSelections(params, frag.GetSelectionSet().Selections)
if err != nil {
return
}
default:
err = fmt.Errorf(“getSelectedFields: found unexpected selection type %v”, s)
return
}
}
return
}


Category golang