Facebook 在去年夏天公布了 GraphQL,就像往前端深潭砸下了一颗巨石,人们都被水声吸引到了湖边,观望是否会出现什么,有些人期待,有些人猜疑。过了半年多,社区已经慢慢的摸清这个石头的材质,本文希望在你入门 GraphQL 和 Relay 的过程中能帮你清除一些障碍。

GraphQL

GraphQL 是在 Facebook 内部应用多年的一套数据查询语言和 runtime。
初次入门者建议先把官网的资料都读一遍,难度不大(specification 和 API 可以后面再看)。

GraphQL 包括什么

  1. 类型系统 - GraphQL 是强类型语言,强类型虽然写时会稍微累点,但就不用写一堆类型检测的代码了;

  2. 验证 - GraphQL 提供机制对你的语法和请求做一定层度的校验;

  3. introspection - 一个让你能通过几行代码就能了解整个资源提供方的细节的 API。

GraphQL 优势

官网已经列举了,我用更简练的语言描述下。

GraphQL 与 REST

同类型协议目前最出名的是 REST,特点是资源可定位,使用 HTTP verbs。REST 具体应该怎么写有很多争议,但简单的例子是没有争议的:

GET /users/1

REST 优点是简单明了,缺点也是太简单明了,导致语法可扩充性不强。
我们来看看 GraphQL 官网是怎么和 REST 对比的:

语法灵活

GraphQL 只需要一次请求就能够获得你所有想要的资源。这里举一个和 REST 对比的例子 让大家有直观的认识。

现在,我想获取id为1的用户的名字,年龄和他所有朋友的名字

GraphQL 实现的方案:

{user(id: 1) {nameagefriends {name}}
}

REST 实现的方案:

GET /users/1 and GET /users/1/friends  

GET /users/1?include=friends.name

发现区别了吗?用 REST 要不就发多次请求,要不就得用一个不方便扩展的语法。

没有冗余

日后扩充资源也没有冗余,你只会获得你想要的资源。还是用上面的例子,如果 user 多了个属性 gender 会怎么样?
在 REST 的方案中,如果客户端不变,取到的结果是会多了 gender 属性,而在 GraphQL 方案中,客户端是不会获取到 gender 属性的。

强类型

有 introspection 机制,代码即文档,方便快捷,而不需要去找这个 API 的说明文档在哪里,看个例子:

自定义 schema

没必要像 REST 这样固定且通用的语法。

其他专有方案(Ad Hoc Endpoints)

和专有方案对比:

  1. 专有方案每个接口都自己定义获取数据,后端代码不能得到重用;

  2. 和 REST 对比的第二点一样;

  3. 每个接口的数据不能复用;

  4. 对比其他现有的专有方案,要么没有强类型,要么没有 GraphQL 这么昂贵,而且前面3点也还是没有解决。

与图数据库的关系

首先,介绍下什么是图数据库,可以参考neo4j的介绍,一图胜千言:


上边是关系数据库,下边是图数据库。

GraphQL 为什么有 Graph,是因为它的 query 是以图的形式来组织的:

user
┖-OWNS-> playlist┖-CONTAINS-> track┖-LIKED_BY-> users

GraphQL 并不要求后台一定要是图数据库,关系数据库也可以,它只是一套查询数据的语言而已。

DataLoader

Dataloader 是一个小工具,帮你把你的请求转成批量请求的形式,和 GraphQL 搭配的也挺好,看个例子:

query FetchPlaylist {playlist(id: "e66637db-13f9-4056-abef-f731f8b1a3c7") {idnametracks {idtitleviewerHasLiked}}
}

这个 query 是要获取某个用户的歌单。
注意一个细节,这个 query 想获取每个 track 的一些属性。我们定义一下 Track 这个类型:

import {GraphQLString,GraphQLBoolean,GraphQLObjectType
} from 'graphql';export default new GraphQLObjectType({name: 'Track',description: 'A Track',fields: () => ({id: {type: GraphQLString,resolve: it => it.uuid}title: { type: GraphQLString },viewerHasLiked: {type: GraphQLBoolean,resolve: (it, _, { rootValue: { ctx: { auth } } }) => ((auth.isAuthenticated) ? it.userHasLiked(auth.user) : null)}})
});

resolve 函数调用的是后端 API,注意这里的 it 就是 track 的对象。
我们获取 viewerHasLiked 这个属性需要调用 it.userHasLiked (auth.user)。那么,我的歌单里有 50 首歌的话,就要调用 50 次it.userHasLiked(auth.user),这样访问数据库的性能是无法接受的。合理的想法是变成批量的。那要怎么做呢?这就是 DataLoader 发挥作用的时候了:

import DataLoader from 'dataloader';
import BaseModel from './BaseModel';const likeLoader = new DataLoader((requests) => {// requests is now a an array of [track, user] pairs.// Batch-load the results for those requests, reorder them to match// the order of requests and return.
})export default class Track extends BaseModel {userHasLiked(user) {return likeLoader.load([this, user]);}
}

在一个 event loop 里每次调用 dataloader,dataloader 会记下你的请求参数,在下次 event loop 的时候把这么多次的请求参数变成一个数组提供你操作,你就可以拿这个数组对数据库执行批量的操作了。而且,它还对结果按你的请求参数进行了缓存,是居家必备的杀人利器。

安全性

或许有人有疑问,感觉 GraphQL 把我所拥有的资源全部都暴露了,别人不只一览全局,而且还能一次过全部拉下来,那还得了?
事实上,GraphQL 提供的资源不一定要和你数据库一样,因为它只是扮演中间层的角色,虽然也可能很像。所以,你要想好哪些资源可以被看。
至于获取,其实看到上面的例子里有这句 auth.isAuthenticated

可以看到你可以在里面插入权限限制的。至于获取资源太多拖垮服务器?

Jacob Gillespie 提到一些思路:

  1. 对语句做 AST 分析,太复杂的就拒绝了;

  2. 做超时限制,对容量也可以做限制;

  3. 客户端记得要做 cache(如 Relay)。

Relay

Relay 是连接 GraphQL 和 React 的一座桥梁。不过,除了让 React 认识 GraphQL 服务器之外,它还做了什么呢?

建议先把官网的资料都读一遍,Relay 相对来说比 GraphQL 复杂一些,而且文档并不详细(截至截稿时,Relay的版本是 v0.6.1),也缺失了关于 graphql-relay 库的详细介绍,扫一遍后,结合本文最后的学习资料的代码加深理解。

Relay 怎么用?

使用 Relay 是要侵入前后端的:

  • 在后端你得通过 graphql-relay-js 让 GraphQL schema 更适合 Relay;

  • 在前端再通过 react-relay 来配合 React。

Relay 包括什么?

Relay 把关于数据获取的事情都接管过来,比如说请求异常,loading,请求排队,cache,获取分页数据。我这里重点讲一下以下几个方面:

client-side cache

Relay 获取数据当然离不开 cache,可以看到 GraphQL 不再依赖 URL cache,而是按照 Graph 来 cache,最大的保证 cache 没有冗余,发最少的请求,我举一个例子:

比如下面这个请求:

query { stories { id, text } }

如果利用 URL 请求(比如说浏览器的 cache),那么这个请求下次确实命中 cache 了,那么假如我还有一个请求是:

query { story(id: "123") { id, text } }

看得出,下面这个请求获取的数据是上面请求的子集,这里有两个问题:

  • 如果第一第二两个请求获取的数据不一致怎么办?

  • 本来就是子集,为什么我还要发请求?

这两个想法催生出来了 GraphQL 的解决方案:按照 Graph 来 cache,也就是说子集不需要再发请求了,当然你也可以强制发请求来更新局部或者整个 cache。

具体做法是通过拍平数据结构(类似数据库的几个范式)来 cache 整个 Graph。

view 通过订阅他需要的每个 cache record 来更新,只要其中一个 record 更新了,也只有订阅了这个 record 的 view 才会得到更新。

最后,聊到修改,我们可以看到 mutation 有个反直觉的地方是请求的 query 里包括了需要获取的数据。为什么不直接返回你的修改影响的那些数据? 因为服务端实现这个太复杂了,有的时候一个简单的修改会影响到非常多的后台数据,而很多数据 view 是不需要知道它变化了。

所以,Relay 团队最后选择的方案是,让客户端告诉服务器端你认为哪些数据你想重新获取。具体到实现,Relay 采用的方案是获取 cache 和 fat query 有交集的部分,这样既更新了 cache,而且不在 cache 里的也不会获取。

Relay 的声明式数据获取

React 是按 Component 组织 view 的,最好的方式也是把 view 需要的数据写在 view。如果用常规的做法,view 负责自己的 Data-fetch,那么,由于 React 是一层一层的往里深入 Component 的,那么也就意味着每一层 Component 都自己发请求去了,是不可能做到用一个网络请求来获取所有数据的。

所以,Relay 通过抽象出一个 container 的概念,让每个模块提前声明自己需要的数据,Relay 会先遍历所有 container,组成 query tree,这样就达到了只使用一个网络请求的目的。

另外,通过声明式数据获取还可以更好的对组件约束,只能获取它声明的数据,并且 Relay 也可以做些验证。

graphql-relay-js

在看一些 React 和 Relay 协作的例子时,经常发现这个库的存在,这个库到底是干什么的?

通过查看源码后发现,里面其实是各种 helper 方法,负责生成一些 GraphQL 的类,为什么需要这样做?其实,这是因为 Relay 提供的一些功能(比如 ID handling,分页)需要 GraphQL 服务器提供特定的代码结构。如果你要开发一个 GraphQL 的前端,就算它基于其他框架,基于其他语言,实现一个像 graphql-relay-js 所实现的 Relay-compliant 的 server 是很有帮助的,比如graphql-go/relay。

babel-relay-plugin

Relay 的 container 依赖的数据资源是通过声明的,但客户端是不知道后端的数据结构的。为了让客户端了解整个后台结构,就要引入这个 bable 插件,这个插件通过读取服务端的 schema,就可以让客户端正确理解它所需要的资源在服务端是长什么样的。

optimistic UI update

我们看下例子:

<Relay.RootContainerComponent={ProfilePicture}route={profileRoute}renderLoading={function() {return <div>Loading...</div>;}}renderFailure={function(error, retry) {return (<div><p>{error.message}</p><p><button onClick={retry}>Retry?</button></p></div>);}}
/>

可以看到在 Relay 里可以很简单的处理请求整个请求过程中的 UI 变化。

总结

相信阅读本文的读者都是对这两者有一定兴趣的人,但在我上手之后,我的心情是复杂的。GraphQL 和 Relay 带来了一些优势,最重要的是可以一次性获取资源,看上去是未来之路,但这优势其实用些不优雅的方法来解决也没什么问题,但为了这些优势需要编写大量与业务逻辑无关的代码,让我真心忧虑它的路能走多远,相信看过一个官方的 TODOList的例子 的入门者很容易就能感觉到。REST 如此简单,普及开来尚且用了几年,复杂好多倍的 GraphQL 的未来还任重而道远。

学习资料

  • GraphQL 和 Relay学习资源汇总:这里列举了比较全的相关学习资源,5颗星。

  • 搭建你的第一个 GraphQL 服务器:这篇文章从0开始帮你搭建一个 GraphQL,比较浅,3颗星。

  • relay-starter-kit:这个例子简单的描述了 Relay 和 GraphQL 的关系,但没有 mutation,3颗星。

  • From rest to GraphQL:提到了rootValue,dataloader,讲了比较真实的例子,5颗星。

  • Relay 官方例子 TODOlist:比较完整的增删改查的官方例子,5颗星。

  • Unofficial Relay FAQ:这篇 FAQ 是 Facebook 员工写的,里面提到 Relay 是要取代 Flux,而且 routing 还在积极修改中。

相关的库

  • server:比如 express-graphql。

  • ORM:比如 graffiti。

  • facebook/dataloader。

  • adrenaline:React bindings for Redux with Relay。

  • react-router-relay:结合 react-router,介绍。

  • graphql-relay-js

  • babel-relay-plugin

GraphQL and Relay 浅析相关推荐

  1. GraphQL 初探—面向未来 API 及其生态圈

    什么是 GraphQL ?第一次看到这个名词未免让人联想到数据库查询语言 SQL .但本质上,这是两个完全不同的东西, GraphQL 在官方文档里的定义如下: GraphQL is a query ...

  2. 为什么说 GraphQL 可以取代 REST API?

    几年前,我在 DocuSign 带领了一个开发团队,任务是重写一个有数千万个用户在使用的 Web 应用程序.当时还没有可以支持前端的 API,因为从一开始,Web 应用程序就是一个.NET 大单体.西 ...

  3. 为什么说GraphQL可以取代REST API?

    几年前,我在DocuSign带领了一个开发团队,任务是重写一个有数千万个用户在使用的Web应用程序.当时还没有可以支持前端的API,因为从一开始,Web应用程序就是一个.NET大单体.西雅图的API团 ...

  4. 新书 5 折腰斩!畅销技术类图书推荐

    题图 | Sale vector created by vectorjuice - www.freepik.com 以下新书按照 24 小时畅销度排名.附京东5 折购买链接,数量有限! 1. 活文档: ...

  5. 在疯狂的前端世界,为什么选择学习React

    题图 | https://github.com/react-icons/react-icons React Native 和 Prettier的作者之一.前端大牛.Twitter大V @Vjeux(C ...

  6. 2017 年该学习的编程语言、框架和工具

    转:http://top.jobbole.com/35926/?utm_source=blog.jobbole.com&utm_medium=sidebar-top-news 软件开发行业继续 ...

  7. 2020年最新前端学习路线

    这段日子在 B 站上收到小伙伴最多的要求就是出一个前端学习路线,我能够充分的感受到大家抓耳挠腮加挠墙的迷茫~所以在这里给大家总结了一套前端学习路线.先从初级前端工程师所需的技能开始,然后一路升级到高级 ...

  8. 2019 年 React 学习路线图

    作者 | javinpaul 译者 | 无明 之前我们已经介绍了 2019 年 Vue 学习路线图,而 React 作为当前应用最广泛的前端框架,在 Facebook 的支持下,近年来实现了飞越式的发 ...

  9. React.js 2016 最佳实践 徬梓阅读 1584收藏 71

    为什么80%的码农都做不了架构师?>>>    译者按:近几个月React相关话题依旧火热,相信越来越多的开发者在尝试这样一项技术,我们团队也在PC和移动端不断总结经验.2016来了 ...

最新文章

  1. 树莓派上利用 Tensorflow 实现小车的自动驾驶
  2. 【知乎直播】千奇百怪的CNN网络架构等你来
  3. MySQL: Starting MySQL….. ERROR! The server quit without updating PID file解决办法
  4. easyUI 展开DataGrid里面的行显示详细信息
  5. BugkuCTF-PWN题pwn3-read_note超详细讲解
  6. 使用python在ArcGIS中合并多个图层中的数据
  7. java中add和addall区别,java中list的add与addall方法区别
  8. JavaScript对象的理解
  9. 同步手机文件到家庭服务器,同步文件到服务器
  10. python搭建图书管理系统
  11. (vue)监听浏览器刷新
  12. 服务器加什么网站才打开快,如何优化让网站打开速度更快些呢?
  13. (附源码)springboot物联网智能管理平台 毕业设计 2111120
  14. Android全局捕获异常信息,并上传到服务器。
  15. CBS paper防伪安全纸
  16. simplex法(单纯形) 并在python实现简单的应用
  17. Flutter 打开外部第三方应用
  18. Programming tools
  19. WS2812+ESP32 diy拾音灯+各种灯效 通过Blinker控制
  20. 我跟Android TV有场约战~战书

热门文章

  1. 【C++基础学习】C++中的引用
  2. VMware Workstation 6.0全貌概览
  3. Upgrade after a crash is not supported. The redo log was created with Maria的解决办法
  4. vscode安装python插件成功 调试时通知一直在载入_vscode写python时的代码错误提醒和自动格式化...
  5. linux开启docker mysql_Linux服务器利用Docker快速搭建MySQL数据库
  6. Pygame Rect区域位置(图解)
  7. pb 修改数据窗口种指定字段位置_如何在PB数据窗口中修改数据---设置数据窗口的更新属性...
  8. 硬件重要还是软件重要?一个人能同时学会吗?
  9. kettle全量抽数据_漫谈数据平台架构的演化和应用
  10. C语言题解:用二分法思想求解10个元素中某个元素的下标(包含函数方法)