本文重点介绍了京东金融APP在BFF层实践过程中遇到的问题,并引出可视化服务编排在金融APP中的落地实践,其中重点介绍了可视化服务编排系统的核心功能及实现。
可视化服务编排系统已经稳定支持了金融APP从去年618到现在的所有发版迭代,对人效提升帮助明显,希望能够对大家在BFF的实践有参考意义。读者通过这篇文章可以了解到基于传统编码方式来实现业务需求时遇到的问题和挑战,以及通过“可视化服务编排”如何规避及解决之前遇到的问题。
01 前言
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
随着SOA架构的提出到微服务架构的落地实践,原本在同一个系统内运行的业务被拆分到了不同的系统或服务中。这样做,在增加业务架构灵活性的同时,也给端上的调用带来了更多的复杂性,如:原本一次请求即可处理完成的业务,现在可能需要多次请求才能完成。为了降低端上逻辑的复杂性并提高前后端交互效率,BFF层应运而生。
BFF作为前后端的代理层,为端上的应用提供了一个业务接口聚合层,它屏蔽了复杂的服务调用关系,让端上应用可以聚焦在所需要的数据上,而不用关心提供数据的具体服务。但BFF实践的过程中,也遇到了很多问题和挑战,如BFF层的需求往往比较简单,但通过硬编码的方式实现,流程非常繁琐且效率低,那如何提高BFF层需求的交付效率,便是目前需要重点关注解决的问题。
02 BFF实践中的问题
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕
BFF(Backend For Frontend), 即服务于前端的后端,可看做是一个后端服务的代理层,它主要做接口聚合和响应数据裁剪。这里需要指出的是:BFF 只是架构分层中引入的一个概念,而非一种技术。
BFF层的核心职责是为前端(包括原生、小程序、H5等)适配不同的业务场景,降低客户端与业务端的耦合,前期通过硬编码的方式来实现BFF层的需求,是最简单最直接的方式。但随着BFF层承接业务需求的增多,通过编码的方式也逐渐暴露出一些问题,如编码效率低、编码细节难以规范、调试测试效率低和服务治理能力弱等。
2.1 编码效率低
在接到一个新的业务需求,除了前期的需求沟通外,开始编码前,通常还需要做一些准备工作,对于Java应用来说,流程大概是这样的:
整个流程中,有太多需要人工处理及等待的步骤,这将会大大降低整个研发流程的效率,虽然现在有一些CI/CD的工具可以减少部分等待时间,但整体的编码体验及效率上的问题还是得不到根本的解决。
2.2 编码细节难以规范
由BFF层的特点决定,其承接的需求大多是对业务接口进行整合输出,包括对多个接口的调用、对返回数据进行裁剪、排序、格式化等操作。单看接口的调用方式就有多种实现,如并行调用、串行调用等,为了降低服务的响应时间和提高系统性能及吞吐量,多个无依赖关系的接口我们通常会采用并行调用的方式实现。并行调用,我们可以通过线程池实现,也可以通过事件回调的方式实现,通过事件回调相对于线程池的实现会有更高的性能和稳定性,但实现起来也会更复杂,研发同学通常都会选择更简单线程池实现。
2.3 调试测试效率低
虽然市面上目前有很多单元测试的工具和框架,但使用起来都少不了配置和编码环节。只要有功能添加或修改,我们就要编写对应的单元测试代码,另外单元测试代码运行大多需要启动整个应用,而应用的启动通常都是分钟级的,这就导致我们研发效率进一步降低。
此外,开发环境中,我们依赖的业务接口通常是部署测试环境的,但测试环境经常会有部署、重启的操作,甚至有些接口都没有测试环境,这就对我们研发调试带来了更多不便。
2.4 服务治理能力弱
代码本质上是非结构化的文本数据,我们很难基于代码进行统计,尤其是大促备战前,我们需要知道某个业务方的接口都被哪些服务引用或被哪些页面调用了,此时接口和服务间的依赖关系就显得尤为重要,但基于编码的方式我们是很难做到精准统计,虽然有一些调用链追踪工具可以提供帮助,但还是不够直接,还是需要人肉的去做进一步的识别。
03 可视化服务编排
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
可视化服务编排的提出,就是为了解决上面提到的问题。可视化服务编排的初衷是希望尽可能地抛弃代码,通过线上可视化拖拽的方式完成功能的开发、调试、测试和上线,我们不写代码或写少量代码就能完成业务需求的交付,没有代码就消除了前面提到的大多问题,这样极大提高研发的交付效率及编码幸福感。整个服务的编排效果如下图所示:
我们可以通过线上拖拽的方式完成接口调用关系的编排,如接口的串行、并行和排他调用等,通过简单的脚本完成不同业务需求的定制,如对接口返回数据的裁剪、排序、格式化等操作。编排后可通过在线测试的功能,直接对编排的服务进行测试,实时秒级验证功能的正确性,可以最大程度降低编码及编译打包的等待时间,提高业务整体的交付效率。
3.1 核心功能
可视化服务编排系统的核心功能都是对BFF日常需求及研发流程的抽象,从接口的调用方式、出入参的处理、接口异常情况的处理、服务的调试测试、服务的上线流程等几个维度完成系统整体功能的设计。
- 接口调用
接口间调用关系可以抽象为:串行调用、并行调用、排他调用。当依赖的接口间没有依赖关系时,我们可以通过并行的方式,对所有所有同时发起请求,这样可以减少服务的响应时间,从而提高系统整体的吞吐量。
当服务依赖的接口有依赖关系时,如接口A的入参需要通过调用接口B来获取,那接口A和接口B之间就必须通过串行的方式调用,即需要先调用接口B,拿到接口B的响应结果才能才调用接口A。
排他调用就好比代码中的if...else,非A即B,这种场景主要用于根据条件判断调用接口A还是调用接口B,或是执行其他的业务逻辑。
根据不同的业务需求和场景,串行、并行和排他的调用方式可能在一个服务中同时存在,所以功能的实现中必须支持不同调用方式的组合及嵌套。
- 参数处理
接口的入参主要有静态和动态两种形式,针对静态的入参,只需要在界面上提供输入框配置即可。针对动态的参数,值可能来自于其他接口的返回结果,也可能来自动态生成的,如随机数、UUID等,所以编排系统提供了通过表达式或脚本的方式来取值或生成值,以适配灵活的业务场景。
- 异常处理
接口的异常通常由两个维度进行判定,一是接口是否调用成功,如果接口抛出异常或超时都可以认为是接口调用失败,另一种情况是接口返回数据是否符全预期,如果接口调用成功,但返回的数据不是预期的,如关键字段没有返回或返回的数据格式不正确,同样需要将接口调用判定为失败。
接口调用失败的情况下,不同场景下的处理策略可能也会不一样,因为有的接口并不是业务强依赖的,即便此接口出现问题也不会影响整个服务的响应。但有些接口则是服务强依赖的,如果请求失败则要求返回兜底数据或直接返回错误。
所以对接口的异常判定和异常的处理方式设计了针对性的功能,即“ 异常断言”、“异常处理策略”和“异常处理器”。
异常断言需要用户填写表达式,用于判断接口返回结果是否符合预期,当异常断言返回True时,则认为接口的调用是失败的。
异常处理策略则分为“忽略”和“中断”,针对弱依赖接口可以使用“忽略”处理策略,此时如果接口调用被判定为失败,则会执行对应的“异常处理器”,用于根据实际业务需求返回对应的兜底数据。针对强依赖的接口可以使用“中断”处理策略,直接返回错误。
- 调试测试
将需求从之前硬编码改为线上可视化编排的方式来实现,原本的编码习惯及调试测试的相关功能就需要在线上得到体现,为了方便服务的调试及测试,编排系统添加了调试控制台和接口数据Mock的功能。
调试控制台可以在线实时查看服务执行输出的日志,方便研发同学对服务调试过程中的问题进行排查和定位。
为方便研发同学根据用例自测服务,编排系统添加了接口响应Mock的功能,可根据入参进行匹配并返回特定的数据,这样研发同学就可以不依赖业务方的接口返回,自己通过数据Mock的方式完成服务的自测,从而提高研发效率。接口Mock的功能如下图所示:
- 服务部署
基于传统编码的方式,当需求开发完成后,除去一些审批和验证流程,上线过程可以概括为以下3个核心步骤:代码提交->编译打包->上线部署,其中最关键的步骤为“上线部署”环节,我们需要重启应用或容器,应用重启需要的时间大多是分钟级的,通常完成一台机器的部署需要3-5分钟,且随着机器的增多,整个上线过程所需的时间也会增多。而通过编排实现的服务,整个上线过程都不需要重启应用,其核心部署工作就是刷新内存数据,只需要线上选择要部署的机器,即可在秒级内完成服务的部署。
3.2 功能实现
可视化服务编排系统的核心功能有两个,一个是前端编排画布,一个是后端服务执行引擎。编排画布用于实现可视化操作部分,其核心功能是定义可操作的功能并根据用户的意图生成后端可解析执行的DSL。
关于DSL的选择,方向主要有两个,一种是根据功能需求,定义一套全新的描述规范,另一种是基于已有的标准进行扩展。通过对前后端实现的复杂度及时间成本的考虑,最终决定基于BPMN规范进行裁剪和扩展,以实现编排整体的DSL规范定义。
BPMN(Business Process Modeling Notation),即业务流程建模符号,是一种流程建模的通用、标准语言,通常用来绘制业务流程图,如OA审批流等。服务编排本质上也是流程编排,在BFF场景下,并不需要BPMN定义的所有功能,所以我们只需要对标准的BPMN进行裁剪即可。
- 编排画布
建模语言确定后,我们需要做的是确定如何实现编排画布。通过调研开源BPMN建模工具,从易用性、开放性、活跃度等几个方面考虑,最后决定基于bpmn.js二次开发来完成前端整个编排画布的实现。bpmn.js是一个基于BPMN2.0渲染引擎和建模工具,基于Web,使用JavaScript编写。
因为bpmn.js原生画板及属性面板是基于标准的BPMN的规范来实现的,在BFF场景下,其中很多配置和属性是冗余的,为了优化用户的操作体验及降低实现复杂度,我们对bpmn.js中的属性面板基于VUE进行了重构,裁剪了BFF中不太关注的属性,添加了BFF中特有的配置项,整个编排画布效果如下:
- 执行引擎
执行引擎是编排系统最核心的功能,其负责执行编排出来的服务。在执行引擎的研发过程中,调研了市面上已有的工具,但不管是从性能、灵活性还是可维护性上,都达不到我们的要求,另外基于已有的引擎做二次开发,时间成本及后续的维护成本也很高,所以最后决定通过自研的方式完成执行引擎的实现。
IO选择
因为BFF的核心功能是接口调用及对接口返回的数据进行处理,所以网络IO这块采用的是全链路异步IO,基于事件回调的方式实现,这样做可以带来两个直接好处:
1. 高性能:IO异步化后,使用极少的线程即可完成大量并发请求的处理,可明显减少高并发场景下CPU上下文切换带来的额外性能损耗;
2. 高稳定性:异步IO消除了因上游接口响应延迟导致自身线程池打满的情况,对服务稳定性有更好的保障;
预处理
相较于传统执行引擎解释执行的方式,自研引擎在初始化的过程中会将整个流程提前预处理为一个个“执行单元”,即“单元化”,不同执行单元进行嵌套组合完成整个流程的执行,这样做的好处是,系统可以在执行前完成整个流程的预编译,将一些没必要在运行期的判断、检查逻辑提前到预处理环节,从而减少运行期的逻辑,提高引擎的整体执行性能。执行引擎核心功能参见下图:
04 运行成效
4.1 交付效率
BFF的业务需求由原本硬编码的方式改为线上可视化编排后,原来需要线下处理的流程全部转为线上操作,规避掉了大多编译构建及测试的等待时间,对人效提升明显,有些需求从原来的小时级提升为分钟级。
4.2 服务治理能力
通过编排实现业务需求后,可以由系统统一管理服务和接口,这样接口和服务的元数据天然就是结构化的,接口和服务的引用依赖关系可以做到一目了然。由系统管理接口和服务后,可以添加更多维度的标签,如接口归属的业务线、服务归属的页面等,可为日常的管理提供更多维度的统计数据。
4.3 问题排查效率
基于服务编排实现的需求,天然具有流程图属性,如下图所示,和编码的方式相比,我们对服务的功能逻辑、接口前后依赖关系、调用关系都能一目了然,对我们日常的问题排查提供了有力帮助。
05 总结
本文重点介绍了京东金融APP在BFF层实践过程中遇到的问题,并引出可视化服务编排在金融APP中的落地实践,其中重点介绍了可视化服务编排系统的核心功能及实现。可视化服务编排系统已经稳定支持了金融APP从去年618到现在的所有发版迭代,对人效提升帮助明显,希望能够对大家在BFF的实践有参考意义。