BFF 层在千寻前端团队落地已经三年的时间,BFF(backend for frontend)已经不是一个新鲜概念了,今天想和大家分享下我们团队 BFF 建设历程中的思考与实践经验。
BFF 层建设背景
在微服务化的大背景下,后端会拆分出很多按领域划分的微服务,而前端一个页面需要的数据往往是跨多个领域,并且不同的客户端 Web/H5/APP 需要的数据也存在差异。此时,我们需要引入 BFF 层来统一接入客户端的请求、对后端的服务进行聚合。BFF 层作为 API 网关的一种,除了做聚合,还可以承担鉴权、限流等功能。
对于这一层谁来建设?我们与后端达成一致约定:后端只提供原子接口,前端对后端提供的原子接口进行聚合以适配不同客户端的查询请求。
这样的职责划分个人觉得主要有以下几点收益:
- 减少前后端的沟通成本,因为没人能比前端更懂页面需要什么数据;
- 前端人员可以加深对业务的理解,提升全局视野;
- 后端可以更加关注于底层核心能力建设。
BFF 层演进历程
第一代 BFF
2019 年中台前端落地了第一代 BFF 层服务,主要是基于 Apollo GraphQL 和 KOA。GraphQL 作为一种基于图模型的 API 查询语言,确实比较适合用来实现 BFF 层。使用 GraphQL 我们只需要将后端的原子接口的入参和返回结构“翻译”成 GraphQL 的 schema 定义,然后通过 resolver 定义取数据的逻辑,便完成了一个 GraphQL query 的定义。一个原子接口可以作为 GraphQL 图模型中的一个顶点,不同的原子接口也可以通过 resolver 串联,如此便建立起不同顶点(原子接口)间的关系。GraphQL 会自动帮我们处理好来自客户端的嵌套查询、接口的聚合/裁减,相对于常规的 rest 接口定义,可以有效减少我们的开发量。当然 GraphQL 还有很多其他优点,我们就不在此展开。
第一代 BFF 层落地的同时,我们很快就发现几个问题:
- 第一代作为落地的试验田,在技术选型上偏轻量化,后期的扩展性较差
- 都啥年代了不上 TypeScript 怎么能行?缺少 TypeScript 的加持,代码的可靠性较差
- GraphQL 原生的 schema 定义需要前端学习 GraphQL 的语法,有一定学习成本
基于以上原因,下半年我们便开始对 BFF 层进行重构。
第二代 BFF
第二代中间层相对第一代,主要在技术选型和写法上进行升级:
- 采用了 NestJS + Apollo GraphQL 的技术选型
- 全面支持 TypeScript
- 采用了 code first 的模型定义方式,即通过 class 定义 GraphQL 模型,这种方式更加贴合前端编码习惯
- 我们在上条基础上开发了 cli 命令,支持通过后端的接口文档自动生成 GraphQL 模型定义代码,进一步提升开发效率
随着时间的推移和业务的发展,接入的后端服务也越来越多,接入的客户端也从前台的商城业务到后台内部系统和APP端,由于各端在用户鉴权上的差异,BFF 服务也从一个演变成三个。
第二代 BFF 层和第一代一样,都是单体应用,单体应用存在如下缺陷:
- 单体随着业务发展容易形成巨石应用,业务逻辑相互耦合,时间久了难以维护。
- 由于我们同时存在后台业务和前台业务的 BFF 服务,不同 BFF 服务如果调用了同样的后端服务,模型定义上存在重复劳动。
- 如果团队比较大的话,不同团队开发同一个 BFF 服务,代码、发布冲突概率增大,是敏捷迭代的绊脚石。
- 单体服务挂了,整个系统便无法使用。
你懂的,没有银弹,只有不断演进的架构,于是第三代 BFF 它来了...
第三代
第三代中间层我们使用了 Apollo Federation 的技术架构,对 BFF 单体服务进行了拆分。在 Apollo Federation 的技术架构里,单体的 GraphQL 服务被拆分成网关加子服务的形式。每个子服务可以作为业务大图中的一个子图,网关负责将所有子图合并成一张完整的图。APP 端与 PC 端的请求差异主要是 APP 端没有 cookie ,用户 token 等鉴权信息是在 header 上,我们通过在网关层适配 APP 端和 Web 端的差异,子服务里面不需要关心端的差异。
升级后架构如下:
总结
在最初的 BFF 层技术选型中我们选择了 GraphQL ,在需要大量聚合的业务场景下, GraphQL 确实减少了我们的开发量,并且优化了我们的请求。比如:一个查询同时串联了 a->b->c 三个接口模型,在某些场景下你只需要 a 和 b 的数据,GraphQL 很智能的就不会发起 c 的请求。
第一代 BFF 到第二代 BFF 主要是在技术栈上的升级、优化了写法以及提供了工具支撑。第三代 BFF 采用了 Apollo Federation 的技术架构,对单体 GraphQL 服务进行拆分。通过拆分单体服务减少了不同端 BFF 层的重复劳动、提高了扩展性、可维护性,在多团队合作开发 BFF 层的时候可以做到关注点分离。但是拆分后也必然会引入服务治理成本,这也是所有微服务要考虑的事情。所以我们还开发了中间层的治理平台,实现了子服务的自动注册、依赖拓扑图等功能,本篇我们不对此展开说明。