【BFF 连载 1/9】如何在蚂蚁数金中玩转 BFF 低码实践

第 64 届早早聊大会将于 2023 年 5 月 7 日(本周日)举办 - 团队管理|选用育留 人事双修,5 位讲师下午直播,关键词:小团队/管人管钱/管优先级/团队建设/女性领导力。选用育留,人事双修,迈向管理的必修课,上车戳:www.zaozao.run/conf/c64

正文如下

本文是 2021 年 8 月 14 日,第三十届 - 前端早早聊【前端玩转 BFF】专场,来自蚂蚁集团 —— 对二的分享。感谢 AI 的发展,借助 GPT 的能力,最近我们终于可以非常高效地将各位讲师的精彩分享文本化后,分享给大家。(完整版含演示请看录播视频和 PPT):www.zaozao.run/video/c30

自我介绍

大家好,我是对二,是来自蚂蚁集团数字金融线保险体验技术部的前端同学。

目录

想让大家收获什么

通过这次分享,想和大家聊聊在互联网金融场景下的 BFF 开发中我们所遇到的痛点。然后我们是如何去怎么定义这些问题,然后一路摸索慢慢解决的。同时也会分享一下其中的技术实现。最后希望能给大家带来一些启发,在自己的业务中如何去设计低码的方案。

BFF 的本质到底是什么?

既然是 BFF 的开发场景,那么我们肯定需要先确定 BFF 到底是什么?它在整个系统架构中承担是怎么一个角色?之前的系统架构都是简单的单体服务架构,它的核心就是 All service in one,所有的服务都在一个系统之中。然后对接客户端,提供服务。它的服务之间是相互依赖,相互耦合的,随着业务的发展,服务间会变的难以拆分,难以扩展,从而应用系统变得越来越复杂,变得难以维护。

还有一个问题在于单体服务架构的应用提供的接口到底是通用接口的还是前端页面定制化接口?通用接口的话,一个是会导致前端请求过多,另一个在于前端需要根据页面处理业务逻辑,会使得页面的通用性不方便维护。页面定制的话又会造成单体应用的服务端开发成本过高,大量接口冗余。

所以随着互联网业务的发展,互联网的应用架构变向着分布式服务架构发展。服务端下沉,只关心自己领域模型的逻辑并提供原子化的服务能力。客户端则向上,只接受页面维度的接口,沉淀通用化的组件。然后在中间加一层 BFF,即 Backends For Frontends,服务于前端的后端。通俗而言,就是面向前端视角设计的服务层。将众多微服务进行业务逻辑的聚合,提供适配页面的接口服务。成为前端后端的桥梁,大大的提高了前后端的灵活性。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

BFF 具备的功能

确定了 BFF 的本质,也基本对 BFF 需要具备的功能有了大致的框框。简单概括下:

  • 后端提供一个登陆接口,BFF 不需要做什么额外处理,那么就直接透传就可以了。也就是 BFF 只是将前端的参数转发给后端。
  • 数据聚合,例如个人信息页,需要展示用户的姓名和用户所拥有的红包,分别由用户信息服务系统和用户营销系统提供,那么 BFF 则需要调用两个后端接口,然后一起返回给前端。
  • 需要根据不用的用户状态展示不同的信息,则会有服务逻辑依赖的处理。
  • 服务端渲染 SSR,BFF 直接输出 HTML。

概括而言,就会发现 BFF 其实就是把单服务架构下那些分工界限不明确的脏活累活给做了。也会导致一些开发的问题。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

BFF 的开发现状

依托 Node.js 的发展和天然的 I/O 密集型能力,现阶段大量的 BFF 系统都是由 Node.js 构建的。同时也都是由前端开发同学承担开发。这也就会造成 BFF 研发中的三个困境:

  1. 人力吃紧,大家应该都有体会前端开发是真的难招,然后业务发展往往前端的需求占大头,与此同时还要开发 BFF,也就造成了前端人力的短缺。
  2. 低技术成长,BFF 的开发基本都是业务逻辑的处理。底层的运维,稳定性,性能保障不是框架做了就是专门 devops 干了。业务的前端开发比较难通过 BFF 的业务开发获得成长。
  3. 研发成本的提高,多了一层BFF,就多了一层的调试。

那么作为前端开发同学对于 BFF 研发模式的期望,就是希望既能有 BFF 的好处,又可以不感知 BFF 的存在。例如,我需要数据时,只需要声明我需要的是什么。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

前期的解决思路

我们的前期的解决思路就比较简单,在我们的业务开发中,部分 BFF 接口就只是转发下参数,我们不希望为了一个转发的接口就去开发一个接口然后发布 BFF,还需要补充单测等等。投入产出不成正比。于是我们就开发了一个通用的转发接口 commonInvoke,把需要转发的后端接口名称和参数都作为参数传入,然后 commonInvoke 根据入参来调用不同的后端接口。这样就不需要额外写接口了。那么需要聚合数据时,就传个后端接口的数组,通过通用的处理聚合逻辑。但是在互联网金融的业务场景下,往往需要身份证脱敏,敏感字段裁剪,这又该怎么办?有些有逻辑依赖的接口又该怎么办呢?

社区方案

前面说到,前端想要什么就拿什么,那么第一反应就是 GraphQL 声明式的开发方式。一个通用接口,传入所需要的内容,接口就可以返回对应的数据。不需要每个页面都去开发一个接口。一个 GraphQL 的接口就能够走天下了。我截取了 GraphQL 官网上的调用示例,就很灵活快捷。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

GraphQL 优势

这里列了一下 GraphQL 官网给出的特点,它的优势相当的多。今天后面的分享,也有关于 GraphQL 在生产的应用,相当的期待。

对于我们研发场景,最具有吸引力的,一个请求可以整合多个资源,还能够可以准确的获取数据,同时也能够使用现有的数据和代码,很符合我们的需求,那么只需要按照 GraphQL 的规范对 BFF 进行重构,后续页面级的接口就不再需要每个页面去开发接口了,直接要啥就获取啥。

于是我们对 GraphQL 进行了深入的调研。发现还是有相当多的问题。

GraphQL 为何没有火起来?

关注过 GraphQL 的同学应该有看到过在知乎上关于 GraphQL 最热闹的话题就是“GraphQL 为何没有火起来?”,GraphQL 在2015年就推出了,而现如今对大多数的 BFF 系统还都是基于 RESTFUL 的规范,真正能够将 GraphQL 玩转,并应用在生产中的还是少数。

我们在深入调研和与我们的研发场景结合时发现 GraphQL 描绘的终态太理想化了。如果能够按照它的规范确实能够大幅度减少接口的开发成本,但是基于这套规范的系统重构需要相当大的成本。

我这里举例了下主要的三个方面:

  1. 前后端的协作问题,前端开发起来简单。那么 GraphQL 的 Schema 的实现是由前端 BFF 做,还是后端同学对原子化接口进行改造?后端同学是否愿意配合。
  2. 即使大家全力配合了,在业务快速发展的情况下,该如何去设计合理的 Schema?跨业务的领域模型该如何优雅的聚合?如果缺乏了前瞻性的设计,那么带来的频繁模型改造该如何解决?
  3. 巨大的改造成本,将原本的 RESTFUL 规范接口改造为 GraphQL 的规范接口在一个复杂应用中,其改造的工作量也会不可小视。

我们的最初目的是减轻开发工作量,但是使用 GraphQL 的话,带来的额外成本一点都不小。基本是违背了我们的初衷。ROI 的产出比就非常的不合适了。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

需求定义

那么回归最初的需求,我们到底希望是怎么样的解决方案?我们定义了几个最重要的需求点:

  • 一个是前端开发要足够简单,简单逻辑的接口完全可以要什么声明什么。
  • 能够包含基本完整的功能,包括字段截取,脱敏等操作,可以在前端声明,服务端执行。
  • 一定要轻量,不需要做大的改造,最好引一个插件就行了。
  • 是可支持扩展的,因为不同的业务会有不同的二次加工和模型需求,
  • 前端可以在自己的工作范围内解决,不需要后端的配合和增加额外的联调成本。

Cod 基本原则

我们先看左边的一张图,在我们的业务场景下,BFF 中 80% 的接口都是简单的接口,我们对于简单接口的定义是:一个或多个接口组合,并有一些简单的数据处理逻辑。还有 20% 是复杂的接口,就是有需要定制化的 UI 逻辑和复杂的数据处理逻辑。

有了 GraphQL 的调研经验,我们确定了一个原则,就是“有所不为”,因为鱼和熊掌不可兼得。既然我们期望要轻量,那么一定是无法适配解决所有问题的。

所以我们决定用最轻量的服务编排的方式来解决 80%的简单场景问题,还有 20% 的复杂场景仍使用原来的开发方式。我们也对这种编排方式取了一个名字,C,O,D,cod,就是上方这个标题,code 去除一个e,让编码少一点的语义。

借鉴思路-CodQL

我先大致介绍一下 Cod 的运行机制,我们借鉴了 GraphQL 的设计思路及规范,但是将其极致的简化,仅汲取它最精华,最适合我们 BFF 业务场景的那一部分。

我们从上往下看,先是 Controller 层,除了对外暴露 RPC,HTTP 等接口外,还暴露一个 CodQL 的通用查询接口,这个接口的入参即是我们借鉴 GraphQL 定义的 CodQL 的规范入参。前端通过 CodQL 来描述 BFF 层执行的代码逻辑,具体的形式,后面会向大家介绍。

然后下面是 Service 层,除了业务定义的 Service 之外,我们提供一个 model-bus 的插件,model-bus 用于解析 CodQL 的入参,然后根据解析的结果来执行其表达的逻辑。也就是下面的模型调度,包括调度的模型,模型的逻辑运算和相互之间的依赖运算。model-bus 就像它的名字一样,装模型的大巴。

而模型则包括内置的模型,包括 Proxy,就是调用后端接口的通用模型,获取 OSS 文件的模型,还有 Redis 读取数据的模型等等。也就是一个后端接口的调用就是一个模型。然后是自定义模型,例如一个通用逻辑调用了多个后端服务,并进行了逻辑串联,然后将他们定义为一个 Service,并装饰为一个自定义模型。然后这些模型就可以根据前端传入的 CodQL 来进行逻辑执行了。最下层的依赖便是 DB,Redis 和后端的微服务等等。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

model-bus 模型定义

model-bus 的模型定义,我们也借鉴并汲取 GraphQL 的教训,做到简单及够用。内置模型前面已经介绍过了,扩展后足够通用的模型,都能够抽象为一个内置模型,例如调的接口的是否是强弱依赖,如果需要也可以定义 Controller 模型,将现有 Controller 接口和其他逻辑模型进行编排、串联。

model-bus 的模型中心,在 model-bus 的模式中,我们关注的是数据模型,前端通过 CodQL,也就是类 GraphQL 的语句来查询需要的数据模型。每个模型对应 BFF 的一个 Service 方法,模型本身的数据字段是丰富的,在客户端我们通过 CodQL 来选择和转换需要的数据字段,这样就能够做到数据适量和功能丰富之间的平衡。同时作为一个数据模型,应该尽量通用,而不是尽量接近页面模型。例如,模型中字段的 format 操作应该在客户端,而不是在 model-bus 的模型上的。

model-bus 的单层模型,也就是我们在够用和简单之间做的平衡。model-bus 的话只支持一层模型的模糊查询。GraphQL 中每个字段都可以指向一个 resolve,可以无限嵌套。因为我们发现,BFF 的应用场景中,服务端给的模型是可以直接用的,并且已经非常接近终端了,模型之间基本不需要逻辑依赖。即便模型之间有依赖关系,也只是业务逻辑上的数据组装的逻辑,完全可以在客户端用查询语句来描述其依赖关系,并且需要什么字段直接取就可以了,并不需要关心他们是如何关联的。

在单层模型下,实现就会变得更简单,接入也就更容易,并不需要为了嵌套而去实现复杂的业务逻辑。同时也能够避免深层嵌套导致的性能问题。当然如果真的有很复杂模型间多层嵌套的业务逻辑,则还是使用传统的模式进行开发。

model-bus 模型定义

我们看一下 model-bus 的使用方式,model-bus 是基于 Egg.js 的,只需要在 plugin 中开启 model-bus 的插件,然后注册 CodQL 的通用接口,例如 POST 接口 common/query,也可以配置些需要处理的前置方法和入参的处理。然后进行模型注册,也就是个枚举,没有其他特别的作用,只是为了方便管理。最后就在需要定义模型 service 方法上添加上装饰器就可以了。例如这个 User,后续只要在前端声明我要这个 User 模型,就可以执行返回了。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

CodQL

介绍完 BFF 端的执行插件,向大家介绍一下前端的编排语法 CodQL,一开始我们是借鉴 GraphQL 设计的编排语言,大家可以看一下,就是一个字符串的入参。先是 Data 对象名,后面的语句运行完便赋值给 Data,然后返回给前端。后面 Proxy 就是模型,然后传入参数,在后面便是过滤器,filter() 和 get(),根据前面 API 接口返回值进行二次加工。当然过滤器是支持使用的同学自定义拓展的,还可以进行金额的标准化、脱敏等等操作。

但是后续在开发同学的使用当中,普遍反馈这种模版语言的语法有点上手成本而且也不知道编写是否正确,要执行了才知道。所以我们又做了些改进,前端同学最熟悉的还是 JS,所以我们也将编排进行了函数式的封装。变成了可链式调用的形式,同时也可以加上代码和类型的提示。右边则是服务编排语言所对应的 AST 结构树定义。大家可以看下,就不细说了。

给大家展示一个实际业务运用中的伪代码。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

可视化

同时我们也发现可视化是有价值的,能够让我们直接看到逻辑过程,也能够验证编排代码是否有问题。所以我们也做了编排可视化的支持。可以自动的把 CodQL 的语法转换为 UML 的流程图。例如上面那个例子,生成的图就是这样的。在做一些需求的系统分析时,便也可以通过 CodQL 来描述逻辑然后生成图片示意在文档中。

稳定性建设 - 单测

当然了,为了保障业务的可维护性和稳定性,单测是必不可少的。所以我们也做了单测的支持,在客户端声明所调用接口的 Mock 数据,然后执行,断言返回值是否符合预期。

稳定性建设 - 监控

除了单测,要在生产中运用,能够承受大促的考验,就必须要考虑,怎么分接口做限流,怎么做监控和报警。

Egg.js 是基于 Koa 的洋葱模型,右边这种图就示意了一下我们做工作。所有除了编排的语法之外,在调用通用接口中,我们还会传入其实际要表达 Controller 名称。然后在洋葱的最外层进行接口的校准。让它在后续的处理中都变成一个独立的 Controller 进行限流和监控。同时我们也定义了标准的 Cod 运行时日志格式。通过日志的过滤对所有的 Cod 编排接口进行统一监控。

服务降级

在业务迭代的过程中,通常会出现本来是个简单的页面,通过服务编排就能够简单解决,但是慢慢的将变成了复杂的逻辑,服务编排已经无法 cover 了,所以我们也提供了反向编译,将编排还原至 JsCode,进行后续的需求开发。

渐进式的编排研发模式

最后向大家总结分享下,我们一路走来的研发历程。所有的功能都是从遇到的问题出发,从最开始想不要每个转发接口都要开一个接口发一次应用,我们做了通用的透传接口。后来不满足于转发了,还想支持些其他逻辑,就有了轻量化的服务编排。到后续的为了支撑项目的维护和运行的安全,完善了单测和日志的功能。然后就是开发同学提出的周边需求,像可视化调试和反向编译的能力。

希望大家能有所收获,在遇到问题的时候可以思考如何去调研方案并解决它,一个小问题一个小问题的解决,然后慢慢的变成一套好用的完整的方案。

选用育留,人事双修,5 月 7 日(本周日)管理专场上车戳:www.zaozao.run/conf/c64

推荐一本书


编程如鱼得水,管理举步维艰?第 64 届早早聊大会将于 2023 年 5 月 7 日(本周日)举办 - 团队管理专场,5 位讲师下午直播,关键词:小团队/管人管钱/管优先级/团队建设/女性领导力。选用育留,人事双修,业务赋能,迈向管理的必修课:www.zaozao.run/conf/c64

  • 举办时间:2023 年 5 月 7 日 13:00 ~ 18:00
  • 截至时间:2023 年 5 月 7 日 19:00
  • 举办方式:微信群 PPT 推送 + 线上视频实时直播 + 会后资料推送
  • 报名方式:www.zaozao.run/conf/c64
  • 大会主办方:前端早早聊

猜你喜欢

转载自juejin.im/post/7229253983965298749