Airbnb:大规模代码迁移至Apollo+GraphQL的实践

3,138 阅读9分钟

Brie Bunge在2019年的GraphQL Summit上分享了Airbnb是如何实现大规模代码迁移至Apollo+GraphQL。虽然GraphQL这个技术已经诞生很久,同时在接口灵活性、性能等方面都得到验证,但是如何在大规模代码级别上进行迁移,始终是个非常困难的事情。

Aribnb采用了一种渐进式并且不会回退的方式进行大规模的GraphQL迁移,这次分享从三个方面介绍了Airbnb进行GraphQL迁移的实践总结。

1 迁移状态

从去年开始,Airbnb开始进行GraphQL的迁移,并且越来越多的团队开始采用。今年一个技术和产品的混合小组进行移动web应用的开发,采用了PWA的技术方案,这个技术方案给用户带来了巨大的体验提升,我们也通过这个项目推动了GraphQL的落地。

可以看到从今年开始,来自GraphQL的流量正在稳步的提升。移动Web应用是主要的流量来源,当然我们也看到了来自桌面PC的流量,iOS和Android的工程师也在不断迁移系统至GraphQL。

5.8%的线上流量使用GraphQL技术,到年底这个数字会到10%左右。首先被迁移的功能是搜索、详情页和下单页,后续完整的业务流程包括落地页、支付页、收藏页、旅游规划页、消息页面都会采用GraphQL。

2 迁移策略

我们原有的技术栈主要是由React+Redux组成,服务端API采用Rest API,未来我们期望采用React、Apollo和GraphQL技术栈。

在迁移策略上,我们有几个选项:

  • 重写所有代码,这个听起来总是很诱人,但是往往需要时间,要契合一次功能重新设计等待
  • 暂停并且重构,这个往往可能在较小的团队或者模块中可以实现,但往往也会带来非常多的问题
  • 增量式采用,我们发现这可能是在大型项目中唯一可行的方式,让我们来讲讲是如何实现的

前提条件

我们将整个迁移分成5个步骤,每个步骤都能产出一个功能完整、无须回滚、可以发布的版本。在我们开始之前有一些前提条件:

  1. 在后端设立好GraphQL基础设施,可以通过以下链接了解更多的细节

  1. 在前端,我们需要使用TypeScript,因为我们可以从schema定义直接生成TS的类型文件,这个会成为前后端唯一的数据标准,TypeScript是一个必选项可以保证端到端的类型正确性。开发会更加迅速,同时研发也会更加有信心。

下图左边是一个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进行替代。这样会带来几个好处:

  1. 更加一致的心智模型
  2. 减少模板式代码
  3. 更好的缓存能力

通过这五个步骤,我们逐步的迁移完成了GraphQL,并且每一步都保证能够正常发布上线,无需回滚。

3 下一步计划

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作为统一的前后端通信接入层,并且在这层统一做数据的整合能力。同时我们也能更好的利用缓存能力。

我们利用批量请求和缓存能力,这样可以减少相同数据的多次请求。

写在最后

最后分享一下一些收获:

  1. 采用Apollo和TypeScript,可以给迁移工作带来大量的信心
  2. 采用增量式的架构演进,对于一个大型现存项目,通过增量式的迁移策略,逐步推出可运行、可发布的系统,这应该是最好的方式
  3. Apollo+GraphQL的组合可以作为非常好的前端数据基础能力

『奶爸码农』从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结。