Netflix采用GraphQL的经验分享


NetflixOSS是Spring Cloud默认堆栈,他们的实践走在行业前列,在这篇博文中,他们解释了为什么GraphQL很棒,以及成长的痛苦在哪里。

在这篇文章中,我们将分享我们更新前端架构的过程以及将GraphQL引入Marketing Tech系统的经验。

我们的发布平台的广告管理应用在内部被称为Monet,用于增强广告制作并自动管理外部广告平台上的营销广告系列。Monet帮助推动增量转换,与我们的产品互动,总体而言,向全世界的用户展示我们的内容和Netflix品牌的丰富故事。为此,首先,它有助于扩展和自动化广告制作并管理数百万个广告素材排列。其次,我们利用各种信号和汇总数据,例如了解Netflix上的内容流行度,以实现高度相关的广告。
我们的总体目标是使我们在所有外部发布渠道上的广告能够与用户产生共鸣,并且我们不断尝试提高我们这样做的有效性。

问题
当我们开始时,Monet的React UI层访问了由Apache Tomcat服务器支持的传统REST API。随着时间的推移,随着应用程序的发展,我们的用例变得更加复杂。简单页面需要从各种来源中提取数据。为了更有效地将此数据加载到客户端应用程序,我们首先尝试对后端的数据进行非规范化。管理这种非规范化变得难以维护,因为并非所有页面都需要所有数据。我们很快遇到了网络带宽瓶颈。浏览器需要获取比以往更多的非规范化数据。

为了减少发送给客户端的字段数量,一种方法是为每个页面构建自定义端点; 这是一个相当明显的非首选项。我们选择GraphQL作为应用程序的中间层,而不是构建这些自定义端点。

我们还认为Falcor是一种可能的解决方案,因为它在许多核心用例中已经在Netflix上取得了很好的成果并且使用量很大,但强大的GraphQL生态系统和强大的第三方工具使得GraphQL成为我们用例的更好选择。此外,随着我​​们的数据结构越来越以图形为导向,它最终变得非常自然。添加GraphQL不仅解决了网络带宽瓶颈问题,而且还提供了许多其他优势,帮助我们更快地添加功能。

优点
我们已经在NodeJS上运行GraphQL大约6个月,并且它已经证明可以显着提高我们的开发速度和整体页面加载性能。以下是自我们开始使用以来对我们有益的一些好处。

1.重新分配负载和有效负载优化

通常,某些机器比其他机器更适合某些任务。当我们添加GraphQL中间层时,GraphQL服务器仍然需要调用与客户端直接调用的相同的服务和REST API。现在的区别在于大多数数据在同一数据中心内的服务器之间流动调用。这些服务器到服务器的调用具有非常低的延迟和高带宽,与来自浏览器的直接网络调用相比,前者性能是后者8倍。

因此,从GraphQL服务器到客户端浏览器的数据传输的最后一英里虽然仍然是一个性能慢点,现在采取GraphQL以后可以减少到一次网络调用。由于GraphQL允许客户端只选择它需要的数据,我们获得了明显更小的有效负载。页面加载变得更快,尤其是在数据受限的移动网络上,我们的应用程序使用的内存更少。这些更改确实以提高服务器利用率为代价来执行数据获取和聚合。

2.可重复的抽象
软件开发人员通常希望使用可重用的抽象而不是单一用途的方法。使用GraphQL,我们定义每一段数据,并定义它与我们系统中其他数据的关系。当消费者应用程序从多个源获取数据时,它不再需要担心与数据连接操作相关联的复杂业务逻辑。

我们只在GraphQL中定义一次实体:目录,广告素材和评论。我们现在可以根据这些定义构建多个页面的视图。相同的图模型可以为多个视图提供动力,而无需进行任何服务器端代码更改。

3.链式系统
许多人专注于单一服务中的类型系统,但很少跨服务。一旦我们在GraphQL服务器中定义了实体,我们就会使用auto codegen工具为客户端应用程序生成TypeScript类型。我们的React组件的道具接收类型以匹配组件正在进行的查询。

由于这些类型和查询也是针对服务器模式进行验证的,因此服务器的任何重大更改都将被使用该数据的客户端捕获。将多个服务与GraphQL链接在一起并将这些检查挂钩到构建过程中,可以在部署错误代码之前捕获更多问题。理想情况下,我们希望从数据库层一直到客户端浏览器具有类型安全性。

4.DI / DX - 简化开发
创建客户端应用程序时常见的问题是UI / UX,但开发人员界面和开发人员体验对于构建可维护应用程序同样重要。在GraphQL之前,编写一个新的React容器组件需要维护复杂的逻辑,以便为我们需要的数据发出网络请求。开发人员需要考虑一个数据如何与另一个数据相关,如何缓存数据,是否并行或按顺序进行调用以及Redux中存储数据的位置。使用GraphQL查询包装器,每个React组件只需要描述它所需的数据,并且包装器会处理所有这些问题。样板代码更少,数据和UI之间的关注点更清晰。这种声明性数据获取模型使React组件更容易理解,

5.其他福利
还有一些其他较小的好处,我们也注意到了。首先,如果GraphQL查询的任何解析程序失败,则成功的解析程序仍会将数据返回到客户端,以尽可能多地呈现页面。其次,后端数据模型大大简化,因为我们不太关心客户端的建模,并且在大多数情况下可以简单地为原始实体提供CRUD接口。最后,测试我们的组件也变得更容易,因为GraphQL查询可以自动转换为我们测试的存根,我们可以独立于React组件测试解析器。

成长的烦恼
我们向GraphQL的迁移是一种简单的体验。我们为构建网络请求和转换数据而构建的大多数基础架构都可以轻松地从我们的React应用程序转移到我们的NodeJS服务器,而无需更改任何代码。我们甚至最终删除了比我们添加的更多代码。但是,与任何向新技术的迁移一样,我们需要克服一些障碍。

1.自私的解析器
由于GraphQL中的解析器旨在作为与其他解析器无关的独立单元运行,因此我们发现它们对相同或类似的数据进行了许多重复的网络请求。我们通过将数据提供程序包装在一个简单的缓存层中来解决这种重复问题,该缓存层将网络响应存储在内存中,直到所有解析器完成。缓存层还允许我们将对单个服务的多个请求聚合成一次性所有数据的批量请求。解析器现在可以请求他们需要的任何数据,而无需担心如何优化获取它的过程。

2.我们编织的网络多么糟糕
抽象是提高开发人员效率的好方法......直到出现问题。毫无疑问,我们的代码中存在错误,我们不希望使用中间层来混淆根本原因。GraphQL会自动编排对其他服务的网络调用,从而隐藏用户的复杂性。服务器日志提供了一种调试方法,但它们仍然是通过浏览器的网络选项卡从自然调试方法中删除的一步。为了使调试更容易,我们将日志直接添加到GraphQL响应有效负载,该负载公开了服务器正在进行的所有网络请求。启用调试标志后,您将在客户端浏览器中获得与浏览器直接进行网络调用时相同的数据。

3.打破类型
传递对象是OOP的全部内容,但不幸的是,GraphQL打破了这种范式。当我们获取部分对象时,就不能使用那些返回完整对象的方法和组件。当然,您可以手动转换对象并希望获得最佳效果,但是您会失去类型系统的许多好处。幸运的是,TypeScript使用duck typing,因此调整这些方法返回只需要的对象属性。定义这些更精确的类型需要更多的工作,但总体上提供更大的类型安全性。

接下来是什么
我们仍然处于探索GraphQL的早期阶段,但到目前为止它是一次积极的体验,我们很高兴接受它。这项努力的关键目标之一是帮助我们在系统日益复杂的同时提高开发速度。我们希望投资graph 数据模型,以便随着时间的推移,随着更多边缘和节点的增加,我们的团队将更加高效,而不是陷入复杂的数据结构。即使在过去的几个月里,我们发现现有的graph模型已经变得足够强大,我们不需要任何graph 更改就可以构建一些功能。它确实使我们更有成效。