网站开发课题研究背景,企业口碑推广,室内设计培训学费多少,谷歌seo服务公司本文为 2018 年 6 月 9 日#xff0c;宋小菜与 Coding 共同举办的第一届 GraphQLParty #xff0c;下午第五场国内某大型电商前端开发专家邓若奇的演讲稿#xff0c;现场反响效果极好#xff0c;对于想要尝试 GraphQL 和在公司初步实践的团队有很大的借鉴意义。 大家好宋小菜与 Coding 共同举办的第一届 GraphQLParty 下午第五场国内某大型电商前端开发专家邓若奇的演讲稿现场反响效果极好对于想要尝试 GraphQL 和在公司初步实践的团队有很大的借鉴意义。 大家好我是阿里的邓若奇。我和 Scott 是好友非常幸运今天站在这里和大家面对面的交流。我是一名前端听说今天来的前端特别多非常高兴压力也很大。 我今天分享的主题是基于 SPA 架构的 GraphQL 工程实践。主要从一名前端的视角来看 GraphQL 在整个 web 链路中包括前端和后端协同效率的问题。 先介绍一下我自己我叫邓若奇做前端之前做过几年的后端开发现在在阿里 CBU 体验技术部主要 B2B 前端工程体系基础建设同时是集团 nodejs 中间件客户端维护者之一及 devops 接口人。 今天分享大概分为五个部分 1、谈谈我对 GraphQL 哲学的一些理解。 2、基于前后端分离一些架构设计与技术选型。 3、详细介绍基于 GraphQL 构建 BFF 这一层我的一些分层设计和思考。 4、介绍一下前后端协作一些效率方面的问题。 5、讲一下引入 GraphQL 之后需要解决的问题。 前面其实很多讲师已经讲到了 GraphQL 一些东西GraphQL 其实就是通过一套 schema 做领域模型的定义官方称之为 SDL同时引入一套类型系统。通过这套类型系统来对模型进行约束就像 PPT 展示的这三个类型一样。 在实际使用过程中客户端通过把想要获取的字段通过 schema 文本发送给服务端服务端经过处理之后以 json 格式返回。这是最常见的一种使用方式。 通过以上的描述大概知道 GraphQL 有以下几个特点
第一它提供一套统一的模型定义。第二相比 REST 提供了灵活的按需查询的能力。第三点其实也是容易被大家忽略的一点就是它通过这套类型系统提供了模型和模型之间关系的描述。这就引入了一个不争的事实 application data graph虽然服务器是以 json 数据返回的但我们的应用数据是一个图或者说是一个网。这也是 GraphQL 成为描述应用数据的一个极佳选择。我认为也是它之所以叫 GraphQL 而不是叫 TreeQL 的原因。 架构设计与技术选型前后端分离说起前后端分离是一个老生常谈的问题自从我开始做前端一直到现在我认为前后端分离大致分为四个阶段 第一阶段前端异步去请求数据接口然后刷新局部的 UI第二阶段前端接管 view 层这个时候基于 spa 框架开始涌现并且一直流行到今天第三和第四阶段随着 nodejs 技术的兴起我们开始思考与后端的协同效率问题通过引入 BFF 这一层实现可以让前端进行快速的业务迭代同时后端下沉为服务或者微服务能够变得更加稳定和高效。 这个架构图相信很多人已经看到过就不多说了。
这是技术选型显然它不是唯一的因为前面很多讲师有他们自己的选型。前端选择 react 和 relayrelay 其实是一种基于 react 和 GraphQL 的一种数据整合方案前面有讲师有提到relay的一些痛点其实在我看来并不是完全的痛点relay 最大痛点是文档太少了笑。BFF 这一层引入 eggjseggjs 是阿里开源的一个面向企业级开发的一个外部框架可以理解成它就是一个 koa 或者是一个 express亦或者是一个 mvc 的框架就行了。 如何设计BFF。 先来看一下基于传统的 mvc 模式的 web service 怎样受理一个 rest 请求的首先请求进入到 middleware我们叫中间件或者是拦截器在中间件处理一些通用的逻辑比如说用户登陆判断和 api 鉴权然后请求到 routerrouter 通过 url 把请求分发到不同的 controller在 controller 这一层调用 model 进行业务处理然后 model 再调用 service 层进行取数数据返回了之后在 controller 层完成数据拼装并且返回。 在引入 GraphQL 之后发生了一些变化。首先 router 不需要了因为 GraphQL 并不是基于 endpoint 的controller 这一层也不需要了因为 GraphQL 天生的 resolver 会帮我们搞定数据拼装的功能另外还引入两个模块第一个是 connector第二个是 schema loader之所以有 connector这个模块主要是因为基于两点考虑第一点是出于性能考虑针对 GraphQL 的一些特点需要进行特殊的缓存设计另外在做前后端协作的时候需要有一些约定或者规范比如说分页跟客户端进行约定分页逻辑的时候需要有这么一些规范需要在 connector 层实现。
另外schema loader 就非常好理解在应用启动的时候需要加载 schema所以我们需要有一个模块来加载它。 下面将我在构建 BFF 层当中体会比较深的点跟大家交流一下。第一如何构建 schema 这其实是一个开发模式的问题我在刚开始写 GraphQL 代码的时候代码是这样的其实这段代码也是在 graphql-js 的官方 repo 上抄来的但是我的代码跟它是一样的但是现在看来我不会这样写。 因为它存在两个问题首先schema 是一个与语言无关的只是模型的一个描述其次做开发的时候本着设计先行的原则先确定模型是长什么样的然后才开始动手写代码。 所以比较赞同SDL First Philosophy。这个不是我提的这个是老外提的。 首先先确定模型的描述以及它们之间的关系等确定模型之间的关系之后我们再来书写 resolver 具体应该如何处理。 最后在应用加载的时候 schema loader 把两者绑定。这些都不是问题。 第二鉴权与授权。 鉴权称之为 authentication授权称之为 authorization说实话我一开始总是搞错并且这两个概念其实在中国人看来其实可以互换或者说是相同的意思 我认为鉴权主要是针对一些通用的逻辑比如刚才说的用户登陆的一些逻辑是一些比较粗粒度的。 这是刚才的那张图把用户登陆判断和 API 鉴权可以放在 middleware 里面实现。 授权是什么呢授权其实是具有一些定制的逻辑它的粒度可能比较细针对 GraphQL 来说可能是细化到某一个字段。 来看一个例子这个 query 要查询小明的工资小明的工资只能小明自己看如果服务端给它放出来就出现水平权限的问题。 所以我们在 resolver 里面需要加入授权逻辑来保证一定是小明自己才能看这里抽象了一个 User.isSelf 接口这里的设计我们把授权的逻辑把它封装在 model让它在不同的 resolver 里面都可以复用。User.isSelf 不光在 salary可能在 login password 这种地方都可以用到。 第三缓存设计 这是数据库里面两条用户记录用户 1 和用户 2 他们互为 friend。 然后程序里面有这么两段代码第一段代码先去查询用户 1查询回来之后再查询它的 friend也就是用户 2第二段代码正好反过来。那么 它的请求时序图是这样的可以看到一共发了四个请求并且我们最终查到的数据只有两条可以看到这是非常浪费的所以考虑优化一下 先引入缓存在引入缓存之后第二轮的查询在第一轮缓存结果中找到非常幸运的是第二轮不需要发新的请求了。 再进一步深挖一下如果第一轮请求能够合并成一个请求这样就太棒了。 所以总结一下我们为了达成这个目的可能需要缓存这是毫无疑问的。然后我们需要一个请求队列请求队列什么意思我们在同一个队列当中node 的 event loop 是分为一个周期一个周期的同一个周期里把所有请求全部放在一个队列里在下一个周期合并成一个请求发出。最后需要批量处理的能力。一个请求带着批量的 key 过来的时候应该怎么表现 索性的是 Facebook 提供这样的解决方案data loader data loader 接收这个就是面对批量 Key 请求过来的时候应该如何处理并且每个 data loader 的实例下面都有一个 cache。 所以刚才的需求引入 data loader 之后实现起来就变成这样。 这段代码最终的效果把三个请求合并成一个请求在我们的后端我们其实是执行一条指令把它搞定的。 但是在实际使用的时候感觉结合关系型数据库使用起来还是有一点复杂。 首先一张关系型数据库表大家会设置一些 key除了有 primary key 之外还会设置 unique key我们经常会根据 pk 进行查询或者根据 uk 进行查询像这段代码里面会根据 id 去查询用户也可能会根据 mobile 去查询用户我们的代码根据 data loader 的哲学不得不初始化两个实例。 这种方式带来的最大问题因为是不同的 data loader 实例缓存是不同的缓存即使记录是完全一样的但是你没法利用到导致缓存的利用率并不高。 为了解决这个问题我书写 rdb-dataloader 这个模块 这个模块其实它的作用很简单就是我无论查询PK还是查询 UK在同一个实例里面搞定让它复用缓存看红框中的代码先通过“name”来查询一条记录然后把这条记录通过它的ID来进行第二次查询第二次查询显然是不会发出去它会用缓存。 所以要实现这样的功能其实这里面有一个问题你缓存记录的时候是缓存每条记录的全部字段让这些字段覆盖你的 UK 和 PK你可能会担心数据量的问题但是它其实真的不是问题数据量控制应该由你的分页逻辑来关心的。 这里抛出一个思考 data loader 这种形式是请求级别的缓存当一个请求进来的时候初始化一个 data loader 的实例当请求结束之后它就销毁这个和我们平时用的基于 redis 中心化缓存有什么不同可不可以切成 redis 的方式留给大家思考。 前后端如何协作 作名一个前端其实在用了 GraphQL 之后必须要思考对于浏览器性能怎么样这是进一步挖掘 relay 的原因下面给大家简单分享一下 relay 的部分特性。 这是一个最普通的 react component一个最普通的诉求就是组件需要异步取数然后把数据进行渲染所以在 componentDidMount 里面去把异步取数逻辑加上。 所以现实中随着组件数越来越深页面加载时间也就越来越长因为子组件必须要等到父组件加载完它的数据之后才开始渲染这个问题我想优化一下应该怎么优化 最简单的方式把所有组件所需要的数据全部放在首个请求里面这个项目交付了之后很不错后面产品经理找我要搞一个需求 结果我搞了一个 bug因为我已经搞不清楚这个 query 里的哪些字段是对应哪些组件的。 我们看一下 relay 的方式relay 这里有一个 create Fragment Container这个方法通过这个方法传入一个react组件然后传入 GraphQL schema 来返回一个 relay container其实这是一个高阶组件通过这种方式我们实现了依赖注入也没有打破数据封装性。 这个 fragment 在首个最初始的 query 里面把它内联进去这样就知道这个玩意是哪个组件发出来的。 relay 有一个非常 smart非常智能的特点应用的数据是一个图状的 是怎样去存取应用的一个数据的这是一个伪代码但是表示 relay 底层的一种协作方式从上面的例子可以看到存储三个对象第一个对象是博客有内容也有作者但是这个作者是一个 user 类型博客不会直接存储 user 的全部数据而是通过引用的方式引用到第二个对象同理在给博客里面下面添加评论评论的作者和它属于哪个博客同样是用引用的方式这个有什么好处 当我比如说作者改头像比如说 github 你们经常改头像吗反正我是不经常但是我发现只要改了头像任何地方都会修改提的 issue代码的提交者全部都会改掉。但是我可以确定 github 不是用的 relay笑只是说表达这个意思只要你改了这个对象在界面任何引用它的地方全部都更新这就是视图一致性。 返回来看一下在 cache 里面 123 是什么东西是缓存 key它的缓存 key 是一个全局唯一的因为把所有的实体全部都塞到缓存里面去了我不能用数据库的 ID否则用 ID 为 1 的 user 和 ID 为 1 的博客它们之间怎么搞 所以需要实现 relay 的规范Global Identifier我们要保证每个对象它的 ID 是唯一的。 这是一种简单的方式可以定义任何一种算法或者方式来确定你的 global ID。这里我只是把简单的类型加上它数据库的 ID 进行 base64 了 所以在 relay store 里面我看到都是这些东西。 刚才说了是 global ID 是需要后端来进行配合我们需要在后端定义两个方法fromGlobalId 就是在 relay 发请求的时候会把 ID 带过来然后我在后端我只识别数据库 ID所以要把它解包把它解成数据库的 ID在吐出去的时候需要给每个数据库 ID 给装包有一个 toGlobalId这两个方法如果使用刚才我的方法graphql-relay 这个包已经提供。 当客户端把文本发送到服务端服务端经过处理的时候我们往往发现这种文本是非常大的特别是对于网络环境不好的一些无线终端它的体验是非常差的。 并且它们是一种静态的文本能不能把它优化一下比如说传一个 ID 过去给每一个 query 赋予一个 ID服务端根据 ID 反查成为 query然后再把它处理出来。 所幸 relay 也提供这样的方式。它给出的答案是在构建时期做构建 relay 脚本的时候本质上它是一个模块会给这个模块做一个 hash标识当前的 schema 给它加一个指纹 这个 hash 在后端可以去 load 到内存里在前端也可以把它通过打包打进去这个时候前后端就对应起来了。 所以在真实的场景中是通过 hash而不是通过 schema text。 需要解决的问题 前面的讲师都已经有提到过 DOS Attack。 说白了就是这种嵌套攻击请注意这并不是死循环这只是一个攻击者故意通过你的 query 无限写的非常复杂的嵌套让你的服务器消耗殆尽。 最简单的你设置 query 文本大小来做预防肯定不行而设置白名单那么我们使用 GraphQL 的意义又在何处所以我们还是对 query 的深度进行控制 这里给出一个例子后面大家其实可以看到。 rate limiting 限流 因为 GraphQL 它不是基于 rest 的所以肯定不能对于 /graphql 这个路由你去限制它每分钟只能调用多少次你真正要限制是读写和操作。 这是一个例子这个例子表示每分钟最多只能添加 20 个评论通过 directive来做 但是限流实现成本比较大如果你是专门实现限流这个功能它需要依赖第三方的一些服务比如说你在计算限流次数的时候还有计算时间窗口的时候因为你的应用是一个集群你不可能做在应用里面。所以我在代码里面还没有实现。
更多专业前端知识请上
【猿2048】www.mk2048.com