内容来源:2018 年 06 月 09 日,有数派联合创始人周文宇在“杭州第一届 GraphQLParty—GraphQL与领域驱动带来的协同价值”进行《Stratup使用GraphQL的姿式》演讲分享。IT 大咖说做为独家视频合做方,经主办方和讲者审阅受权发布。前端
阅读字数:6109 | 16分钟阅读web
本次演讲主要介绍如何使用GraphQL,分别从先后端两个角度分析GraphQL的优劣势,对比Restful又可以给先后端协同开发带来哪些好处。数据库
之因此要使用GraphQL主要出于几方面的考虑。首先咱们的业务复杂度高,应用自己的业务场景极其复杂,涉及到纺织行业大大小小几十个业务场景和十几个不一样工种功能之间的联动与交互。后端
其次是因为Tech Team有一半同事Remote在全国各地,所以对协同效率与开发工具链的要求很高,而GraphQL刚好能解决沟通成本的问题。第三是由于终端过于繁杂,既有b2b的平台也有SaaS产品和硬件产品,各个终端的链接很是多,若是用传统的Restful API开发会用到很是多的胶水代码。数组
这里大概介绍一下传统接口在咱们这个领域中应用所会遇到的一些问题。缓存
沟通成本高。先后端须要反复沟通接口结构和数据类型,可是对于B端这种相对复杂的业务场景,数据和业务场景都是多变的。同时前端对数据接口结构的掌控有限,先后端Pipeline也不一致,传统BBF对前端不够友好,更多的还得依赖后端。安全
开发效率低。文档维护成本高,一致性和同步性问题很难解决。服务器
一些技术债。好比对多终端和多场景支持不友好,缺少标准化的约束,先后端都须要重复工做。微信
以上是从后端的角度来对这些问题进行的分析,接下来由咱们的前端方面的负责人介绍下咱们在前端方向上的一些实践。数据结构
刚才周老师已经从后端的角度分析了这些痛点,我这里就从前端的层面来从新谈论下这些问题。
第一个要提的是反复沟通接口的问题,对于后端来讲他们更关注的是数据的表结构,而前端所须要的仅仅是界面的展现数据。有时这些数据并不可以从某个表中直接获取到,可能要跨不少的表,这就须要前端和后端之间进行相互的磨合沟通才能得到最终的结果。
其次是前端对接口的结构掌控有限,当前端的请求发送出去后,接口所返回的数据形式有可能并不符合预期,好比本该返回的数组变成了对象。
再来看下开发效率低的问题。前端开发中UnderFetching和OverFetching一直没法避免。
UnderFetching指的从接口中取到的数据远低于须要展示的数据,由此引起了N+1问题,即须要根据已取得的接口中的ID或者详细信息再去请求对应的接口,这就致使了前端的请求从异步变成了同步。
本来页面的渲染时间是取决于最慢的接口响应时间,而同步模式下则是全部接口串行返回的时间。OverFetching则正好相反,接口的返回数据远高于须要展示的数据,对前端和服务器端的资源形成了双重消耗。
面临传统接口所带来的这些痛点,通常团队都会选择NodeJs做为中间层的解决方案。那么Node和GraphQL相比有那些优缺点呢?下面来一块儿看下。
Node.js在必定程度上减轻了underFetching和overFetching问题,尤为是解决了N+1的问题,后端能够经过Node.Js中间层来进行数据资源的整合,可是此时仍旧只能返回一个固定的数据结构。
Node.js缺陷也很明显,好比对多端须要作额外的适配,难以适应前端的快速迭代,须要花费大量时间维护,由此还会引起接口文档的断层。
GraphQL对于Node碰到的这些问题基本上都可以很好的解决。GraphQL中能够由前端来定义Query,页面和数据能完美匹配。同时一旦Schema肯定,先后端就能够快速并行开发。前端对字段及返回类型也可以了如指掌,GUI清晰的展示了字段的类型结构。
给高速运行的汽车更换轮子是一件至关危险的事情,而咱们早期所面临的状况和这差很少。当时咱们的产品已经推出有一段时间了,数据库很是庞大,功能也很繁杂,并且数据彻底是基于Restful。此时须要将Restful彻底替换成GraphQL,这无疑对先后端来讲都是一个很是大的挑战。
在跟换轮子以前,我先比较了一下前端数据缓存的框架,目前主流的有Relay和Apolloo。Relay由Facebook官方推出,支持的框架有React和React Native,Apolloo则支持绝大多数主流框架。
Router方面Relay官方支持React Route,新版本中还支持一个新的路由Found。Apollo因为自己的运行方式和生命周期已经彻底和路由割离开,因此可以支持任何Route。
Relay的数据缓存由官方提供的RelayStore完成,Apollo则是基于Redux。基于以上几点考虑,我最终选择了Apollo。
以前咱们在使用Redux加Redux-saga的时候,倒没有遇到什么特别大的问题,主要仍是特别麻烦。须要手动管理和存储返回数据,还要为每一个资源创建一套Action,Reducer,Redux-saga,同时要针对每一个请求进行异常及数据处理,针对页面须要的数据触发屡次串行请求。
在使用GraphQL和Apollo以后,前端方面只须要全局定义一个URL,接下来就是定义每一个Query须要取得的数据,根据页面定制接口数据。同时还能够作全局的异常处理,接口请求的合并。
接下来将讲一下我是如何安装轮子的,会涉及到一些面向场景的解耦操做,还有具体的代码演示。
对于咱们应用的订单页面数据,在Restuful场景下首先会根据订单ID请求订单信息,接着依据从订单信息中获得的产品ID获取产品详细数据,以后还须要根据建立人ID获取客户详细数据,最后将这些数据结合起来才能渲染页面。
上图展现的是经过GraphQL来作订单页面时后端定义的一些类。最下方的员工类是一个基类,它包含了id、性别、部分、姓名这些通用的字段。
Employee对象被嵌套在Order类和Peuduct类中,在这两个类中可以很轻易的经过creator字段获取到Employee的数据信息。
而Order类和Peuduct类之间是相互引用的关系,经过items字段分别定义对应的对象数据。
这张图分别展现了前端Fragment和Query。前端Fragment是基于对象的,左边的第一段代码就是一个基于Employee对象的employee Fragment,咱们能够从中获取到的id、gende、name等数据。在其余的Fragment中可以自由的引用employee对象。
Query其实就是GraphQL对传统前端Fragment的定义,它可使用GraphQL官方提供的方法将关联的数据字段绑定给某个component。
在结合GraphQL和Apollo的状况下咱们的鉴权方案主要依赖于AppAsyncStorage和web localStorage这两个数据可持久化的方案。
在登陆过程当中产生的token缓存到App AsyncStorage或web localStorage中,注册GraphQL Server的时候经过outLink来setContext,setContext方法主要用来重写header,在header中添加资源字段。这个资源字段通常是和后端商议后决定,不过Apollo官方的推荐经过传入token来实现整个鉴权方案。
前面提到过咱们的终端设备中还包含树莓派,这是一个工业控制设备,通常被放置在用户的厂房中,用来打印记录库房数据。
咱们经过阿里云物联网套件来实现服务器端和树莓派之间的通讯,设备能够发布和订阅一些数据到MQTT中 ,每隔一段时间就会有心跳包从设备上传到MQTT,以此来更新页面数据。以后MQTT会将心跳包传递到消息队列中,而后再经过API到应用层来同步消息。
最后app端或web端经过暴露给他们的GraphQLAPI来读取到设备的信息,好比设备编号、固件版本、公网内网IP等。
当用户发现设备版本和服务器版本存在差别,执行须要更新的时候,本质上是发送动态操做到GraphQL API层,而后由GraphQL API层发送消息到设备 ,以后消息经过SDK API到达MQTT,接着发布一系列的请求到设备。最后设备重启完成以后会再从新发布心跳包到MQTT来更新一系列的操做。
在使用新轮子的过程当中碰到的第一个问题就是在学习成本和团队适应方面。一是文档不够不够健全,相关的学习资料偏少,可能产品已经推出了2年时间,可是文档倒是刚推出时写的。
除开文档问题以外,本质上还有一个思路的改变。原先使用Redux发送请求时,虽然和后端沟通麻烦了一点,可是毕竟已经和熟悉了。如今转换到GraphQL后,请求发起机制、数据刷新、文件上传等等都彻底不一样,至关于要从头开始学习新的东西。
在使用Apollo的过程当中咱们也遇到了一些坑。好比屡次请求触发致使返回结果为underfined,之因此会这样是因为第一个接口请求发送出去后,还在loading阶段时,同一个接口又发送了第二次,致使返回数据发生冲突变成undefined。
还有资源对象和id重复致使资源数据被覆盖的问题,这是由Apollo的数据存储的特性所形成的,Apollo的每一个资源对象的类型和id是定义数据字段惟一的标识。
之前设计的Component有容器组件和展现组件这两个概念,容器组件和Redux store之间存在交互,用来更新数据,展现组件则是单纯的展现数据。如今使用GraphQL以后,咱们发现了一个更优的解决方案。
由于每一个对象的资源字段固定,彻底可让每一个Component和GraphQL 的Query片断一一对应。这意味着Component再也不是为了请求而定义的,而是根据对象类型来定义
咱们在使用GraphQL 的过程当中曾出现过一次很是大的性能问题。当时为了更高效的开发,咱们将每一个资源对象所有使用Fragment来写,且每一个对象都取到了全部页面中所有的数据。
此时一个要动态计算的字段被放在了一个基类中,在多个Fragment中循环调用,甚至嵌套调用。这时候后端就可能接收到一个须要计算n方次动态计算的结果的请求,服务器的负载压力可想而知。
以上这种状况对于前端来讲,操做的只是某个实体的一个或几个资源字段。可是对于后端来讲,每一个实体背后可能对应着不一样的数据库甚至不一样类型的存储集群,同时后端集群间的海量数据自由join获得的结果。前端泛滥数据会致使后端负载急剧上升。
以上大概就是前端方面要讲的所有内容,接下请继续看下关于后端方面的问题。
使用GraphQL的过程当中遇到的第一个比较严重的问题就是接口设计思路转变困难,以前在写RestfulAPI的时候想的更多的是面向资源,而GraphQL的设计思路则是面向场景,这彻底颠覆了后端设计接口的哲学。
其次是开发feature时要更多考虑性能的问题,避免被查宕机。另外GraphQL相比Restful在鉴权模型设计上要投入更多的精力。
说完了这些弊端,再来看下有那些有利的地方。一是接口开发工做量大幅减小,重复性工做得以减小,避免了出错。其次是错误返回和文档等标准化工做能够借助工具自己完成。最后是对API多版本管理更加友好。
从后端来看早期的性能问题都是由于急于快速上线引发的,主要有如下几个因素。
第一点就是GraphQL的N+1场景,即前端在查询数据的时候可能首先要查到IDS数组,而后再map IDS数组从新对后端发起请求,最后后端经过多条SQL取到的多是列表数据。
第二点是在前期开发的时候没有作请求层级限制,致使前端查询多层嵌套,服务器没法承受压力。
第三点是没有作分页的数目限制,通常来讲前端能够经过传递特定的参数给接口来设置每页的接收数量,可是有些前端人员可能会为了快速上线新功能将每页的数量直接设置为9999,这样服务器就又会被搞宕机。
为避免发生一些安全问题,须要在认证、受权和请求频率限制几个方面多加注意。认证上每次都要在Header上加上token,认证机制应改成JWT,咱们这边因为涉及到的终端较多,因此还会按平台检验合法性。
受权上能够针对操做也能够针对字段进行具体的受权。请求频率限制上既要防止恶意刷请求也要实现基于IP和UserID的限制。
这里的标题是安装工人的心得,其实指的就是我我的在使用GraphQL过程的一些感悟和总结。
先来看看GraphQL还有那些弊端。
第一,虽而后端已经作了一些优化,可是仍是没有彻底实现前端的按需查询,当数据量达到必定级别的时候,数据库查询可能会成为性能瓶颈。
第二,生态群并不完善,文档和实例缺少,可能在google查询到的资料没法确切的解决问题。
第三,评价一个技术的实用性,不光要看技术力还要看它可否快速落地。一个现有的项目要从Restful切换到GraphQL,其实重构的压力很大,且重构和业务并行推动困难。
第四,因为目前国内使用GraphQL的团队不是不少,因此很难招聘到有经验的工程师,须要从零开始积累。
谈完了弊端再来讲下我的的感悟,总结起来就是三个确实。确实提高了先后端的协做开发效率;确实下降了在先后端协做过程当中的错误率;确实提升了接口复用度和开发效率。
我的认为用开发工具自己提升不管是前端、后端仍是DevOps的工做效率是将来的发展趋势,我也相信GraphQL将来会支持更多的框架,解决更多的问题,并在性能和安全上有进一步提高。
有数派是专业的纺织SaaS产品,具有工业(硬件)控制、样品管理、仓库存储、销售管理、设计辅助等功能,应用场景覆盖了行业几十个场景,近十个终端。
咱们在技术栈的选择上,web用的是React,App用的React Native,后端主要的API源是用Ruby写的,还有部分Python用来作数据分析,目前全部的API都被迁移到了GraphQL上。硬件部分用到了C++,服务器的容器管理用的是Docker,DB则是PostgreSQL。
咱们目前在终端上有树莓派、tv、pad、web、app、微信端、ipad。
以上为所有分享内容,谢谢你们!
编者:IT大咖说,转载请标明版权和出处