干货 | 携程基于 GraphQL 的前端 BFF 服务开发实践

作者简介工业聚,携程高级前端开发专家,react-lite, react-imvc, farrow 等开源项目作者。兰迪咚,携程高级前端开发专家,对开发框架及前端性能优化有浓厚兴趣。一、前言过去两三年,携程度假前端团队一直在实践基于 GraphQL/Node.js 的 BFF (Backend for Frontend) 方案,在度假BU多端产品线中广泛落地。最终该方案...
摘要由CSDN通过智能技术生成

作者简介

 

工业聚,携程高级前端开发专家,react-lite, react-imvc, farrow 等开源项目作者。

兰迪咚,携程高级前端开发专家,对开发框架及前端性能优化有浓厚兴趣。

一、前言

过去两三年,携程度假前端团队一直在实践基于 GraphQL/Node.js 的 BFF (Backend for Frontend) 方案,在度假BU多端产品线中广泛落地。最终该方案不仅有效支撑前端团队面向多端开发 BFF 服务的需要,而且逐步承担更多功能,特别在性能优化等方面带来显著优势。

我们观察到有些前端团队曾尝试过基于 GraphQL 开发 BFF 服务,最终宣告失败,退回到传统 RESTful BFF 模式,会认为是 GraphQL 技术自身的问题。

这种情况通常是由于 GraphQL 的落地适配难度导致的,GraphQL 的复杂度容易引起误用。因此,我们期望通过本文分享我们所理解的最佳实践,以及一些常见的反模式,希望能够给大家带来一些启发。

二、GraphQL 技术栈

以下是我们 GraphQL-BFF 项目中所采用的核心技术栈:

  • • graphql

    •   基于 JavaScript 的 GraphQL 实现

  • • koa v2

    •   Node.js Web Framework 框架

  • • apollo-server-koa

    •   适配 koa v2 的 Apollo Server

  • • data-loader

    •   优化 GraphQL Resolver 内发出的请求

  • • graphql-scalars

    •   提供业务中常用的 GraphQL Scalar 类型

  • • faker

    •   提供基于类型的 Mock 数据

    •   结合 GraphQL Schema 可自动生成 Mock 数据

  • • @graphql-codegen/typescript

    •   基于 GraphQL Schema 生成 TypeScript 文件

  • • graphql-depth-limit

    •   限制 GraphQL Query 的查询深度

  • • jest

    •   单元测试框架

其他非核心或者公司特有的基础模块不再赘述。

三、GraphQL 最佳实践

携程度假 GraphQL 的主要应用场景是 IO 密集的 BFF 服务,开发面向多端所用的 BFF 服务。

所有面向外部用户的 GraphQL 服务,我们会限制只能调用其他后端 API,以避免出现密集计算或者架构复杂的情况。只有面向内部用户的服务,才允许 GraphQL 服务直接访问数据库或者缓存。

对 RESTful API 服务来说,每次接口调用的开销基本上是稳定的。而 GraphQL 服务提供了强大的查询能力,每次查询的开销,取决于 GraphQL Query 语句查询的复杂度。

因此,在 GraphQL 服务中,如果包含很多 CPU 密集的任务,其服务能力很容易受到 GraphQL Query 可变的查询复杂度的影响,而变得难以预测。

将 GraphQL 服务约束在 IO 密集的场景中,既可以发挥出 Node.js 本身的 IO 友好的优势,又能显著提高 GraphQL 服务的稳定性。

3.1 面向数据网络(Data Graph),而非面向数据接口

我们注意到有相当多 GraphQL 服务,其实是披着 GraphQL 的皮,实质还是 RESTful API 服务。并未发挥出 GraphQL 的优势,但却承担着 GraphQL 的成本。

fd0f37bf41b18ec4cf089f0f4526ef67.png

如上所示,原本 RESTful API 的接口,只是挂载到 GraphQL 的 Query 或 Mutation 的根节点下,未作其它改动。

这种实践模式,只能有限发挥 GraphQL 合并请求、裁剪数据集的作用。它仍然是面向数据接口,而非面向数据网络的。

60c34c1a69e139e9238013f57806453d.png

如此无限堆砌数据接口,最终仍然是一个发散的模型,每增加一个数据消费场景需求,就追加一个接口字段。并且,当某些接口字段的参数,依赖其它接口的返回值,常常得重新发起一次 GraphQL 请求。

而面向数据网络,呈现的是收敛的模型。

763e62dc3a5f8e8fe7d265c0859c2bf2.png

如上所示,我们将用户收藏的产品列表,放到了 User 的 favorites 字段中;将关联的推荐产品列表,放到了 Product 的 recommends 字段中;构成一种层级关联,而非并列在 Query 根节点下作为独立接口字段。

相比一维的接口列表,我们构建了高维度的数据关联网络。子字段总是可以访问到它所在得上下文里的数据,因此很多参数是可以省略的。我们在一次 GraphQL 查询中,通过这些关联字段,获取到所需的数据,而不必再次发起请求。

当逐渐打通多个数据节点之间的关联关系,GraphQL 服务所能提供的查询能力可以不断增加,最后会收敛在一个完备状态。所有可能的查询路径都已被支持,新的数据消费场景,也无须开发新的接口字段,可以通过数据关联网络查询出来。

3.2 用 union 类型做错误处理

在 GraphQL 里做错误处理,有相当多的陷阱。

第一个陷阱是,通过 throw error 将错误抛到最顶层。

假设我们实现了以下 GraphQL 接口:

31d79c06211fc20c6424e6a29a7ea339.png

当查询 addTodo 节点时,其 resolver 函数抛出的错误,将会出现在顶层的 errors 数组里,而 data.addTodo 则为 null

37ebf38db4ec11844277cfc5e50a0615.png

不仅仅在 Query/Mutation 节点下

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值