Brie Bunge在2019年的GraphQL Summit上分享了Airbnb是如何实现大规模代码迁移至Apollo+GraphQL。虽然GraphQL这个技术已经诞生好久,同时在接口灵活性、性能等方面都获得验证,可是如何在大规模代码级别上进行迁移,始终是个很是困难的事情。前端
Aribnb采用了一种渐进式而且不会回退的方式进行大规模的GraphQL迁移,此次分享从三个方面介绍了Airbnb进行GraphQL迁移的实践总结。web
从去年开始,Airbnb开始进行GraphQL的迁移,而且愈来愈多的团队开始采用。今年一个技术和产品的混合小组进行移动web应用的开发,采用了PWA的技术方案,这个技术方案给用户带来了巨大的体验提高,咱们也经过这个项目推进了GraphQL的落地。npm
能够看到从今年开始,来自GraphQL的流量正在稳步的提高。移动Web应用是主要的流量来源,固然咱们也看到了来自桌面PC的流量,iOS和Android的工程师也在不断迁移系统至GraphQL。后端
5.8%的线上流量使用GraphQL技术,到年末这个数字会到10%左右。首先被迁移的功能是搜索、详情页和下单页,后续完整的业务流程包括落地页、支付页、收藏页、旅游规划页、消息页面都会采用GraphQL。缓存
咱们原有的技术栈主要是由React+Redux组成,服务端API采用Rest API,将来咱们指望采用React、Apollo和GraphQL技术栈。数据结构
在迁移策略上,咱们有几个选项:架构
前提条件函数
咱们将整个迁移分红5个步骤,每一个步骤都能产出一个功能完整、无须回滚、能够发布的版本。在咱们开始以前有一些前提条件:工具
下图左边是一个GraphQL查询语句,右边是使用Apollo工具自动生成的TypeScript类型文件,这样咱们能够保证类型正确,而且有Null错误检查。TypeScript已是Airbnb的前端官方开发语言,已经有超过3百万行代码已经迁移至TypeScript,若是你对如何迁移至TypeScript感兴趣,你能够看看上半年的JSConf的分享。在咱们迁移至GraphQL+Apollo以前,咱们经过内部工具将大量JS代码转换成TypeScript。组件化
第一步:REST到GraphQL
第一步是将REST接口迁移至GraphQL,从而验证端到端的整合,验证TypeScript类型文件的生成。在这个阶段,咱们不会改变任何React组件和API返回数据结构。
让咱们以预约房间接口为例,咱们使用Apollo client来发起GraphQL请求,首先咱们要保证GraphQL请求的返回数据格式和原有的REST接口保持一致。若是手动实现GraphQL查询语句,这个每每很是繁琐而且容易出错。
咱们利用了GraphQL的Playground,里面提供了字段自动补全的能力,同时又能实时验证结果。
咱们也使用GraphQL中的字段别名的能力,这个对咱们很是有用,由于原有的REST接口采用蛇形命名,而GraphQL中采用了驼峰命名。
咱们经过Schema自动生成了数千行的TypeScript类型声明代码,这个简直太棒了!这个在之前会很是繁琐,须要根据服务端的请求结果手动编写,并且接口发生变化也难以同步。
其次,咱们还须要关注转换代码(adapt),在咱们的后端的request和response数据结构有少许的变化,因此咱们建立了adapter工具方法,能够把一个对象转换成另外一个对象。经过adapter的转换,咱们保证了GraphQL的request和response同原有的REST接口彻底保持一致。
每一步迁移咱们都保证能够有上线的版本,因此这一步完成后咱们实现了GraphQL的上线。
第二步:TypeScript类型
这一步的目标是开始使用自动生成的TypeScript类型定义文件,这样能够有效的提高咱们的迁移信心,若是出现类型问题会在编译阶段第一时间获得错误提高,但咱们并不会影响运行时的行为。
使用TS Type文件,一个问题就是Null的检查,如上图部分所示,每每须要一层层对对象里面的内容进行判空处理,咱们经过采用一个IDX的npm库来解决这个问题。固然optional channing已是JavaScript TC39的提案,而且已经在TypeScript 3.7版本中获得支持。
第三步:使用Apollo Hooks
在Airbnb咱们很是喜欢使用React Hooks,减小了许多模板式代码,同时很好的结合TypeScript共同使用。Apollo Hooks提供了一系列函数,能够用于替代原有的HOC的实现方式。
当前阶段咱们使用Apollo Client,同时咱们仍是使用Redux Actions去触发事件,而且组件也使用Redux Store。
咱们对系统进行了重构,采用Apollo HOC或者Hooks,而且更新了组件数据采用Apollo cache数据源替换掉Redux Store。咱们依旧保留了Redux Store,由于有部分数据还未迁移,但它的做用已经基本宣了结结。
第四步:自下而上的组件化查询
在第一步迁移至GraphQL的时候,咱们采用保留现有REST的request和response的方式,可是这样会有很是多的冗余字段。所以这个阶段,咱们将会对查询语句的字段进行裁剪。
咱们从组件的叶子节点开始,寻找当前节点所须要的字段,而且定义相应的查询条件。同时咱们也会自动生成对应的TypeScript类型文件,而且经过这个文件来帮助检查是否有字段的缺失。
而后咱们会将子节点的查询条件上升整合至父节点,直至App节点,这个过程能够一直重复循环直至整个树都转换完成。这时候App节点就有一份完整的整合好的查询条件,因为每一个节点都只获取它所须要的数据,咱们几乎能够认为App节点的查询条件也是近似于最简洁的数据格式。
第五步:状态管理
当咱们进行GraphQL迁移的时候,咱们总会关心当前Redux Store如何处理。咱们发现Redux Store的能力,一般均可以被React本地state或者context,以及Apollo数据API进行替代。这样会带来几个好处:
经过这五个步骤,咱们逐步的迁移完成了GraphQL,而且每一步都保证可以正常发布上线,无需回滚。
Service Worker接口预加载
目标是触发GraphQL查询越早越好,这样可让用户体验更好。咱们采用Service Worker的实现方式来作到这点。
这张图对比了采用服务端渲染(SSR)和Service Worker的两种方式的体验状况。在图的上半部分是采用SSR,能够看到因为须要等待服务端渲染,因此存在大量的白屏时间,而后直接加载整个内容。而采用Service Worker,用户能够一开始就看到应用的骨架布局,并且因为大量静态资源已是本地缓存,所以页面渲染也很是迅速。
这是一个采用SSR方式页面的调试结果,首先咱们获取服务端请求,这个请求同时也包含了GraphQL的查询,而后解析HTML而且下载JS,而后页面逐步渲染出来,当React完成了页面渲染,用户能够进行页面操做。
当采用Service Worker方式,因为大量资源都是从本地缓存读取,因此第一时间就会进行HTML解析和页面渲染,而后会开始GraphQL查询,数据返回,而后页面能够交互。
能够看到在页面开始加载到GraphQL查询发起之间存在一个很大的间隙,所以这是一个很是大的优化空间。
咱们能够在路由上注册GraphQL请求,而不是在页面级别。这样GraphQL请求能够在页面最开始的时候就发起,这样它把GraphQL请求和React页面渲染解耦。在这个场景下,咱们将节省400ms,提高了23%性能,这个优化在低配设备上尤为明显,咱们模拟了6倍慢速的CPU设备,咱们获得50%的性能提高。
统一的Schema
另外一个改进是采用统一的Schema,咱们首先来看看Aribnb的SOA服务,SOA服务主要分为三层:数据层,聚合数据层,展现服务层。
咱们使用GraphQL做为统一的先后端通讯接入层,而且在这层统一作数据的整合能力。同时咱们也能更好的利用缓存能力。
咱们利用批量请求和缓存能力,这样能够减小相同数据的屡次请求。
最后分享一下一些收获:
『奶爸码农』从事互联网研发工做10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,按期分享关于大前端技术、投资理财、我的成长的思考与总结。