背景

​ 像平凡的大多数一样,大部分的时间还是做着 CRUD 的需求,使用 ECharts 搭建各种图表看板。直到产品给我们提出了一个这样的需求,前端通过不同的筛选方式选定一种规则,后端解析这些规则并通过大数据跑数筛选出符合规则的目标对象并且生成对象 id 给后续使用。

​ 一开始这个需求并不是定位于中台开发,产品只是作为一个常规需求提给我们。深入了解业务需求以后我们发现这个需求不仅在我们的项目中要使用,在另外一个项目中也要使用,并且不同的项目中表现的形式不一致,筛选的对象在最初的需求中只是针对到商户。稍微整理一下我们发现这个需求具有 跨项目 可配置 可扩展 这些特点。所以大胆的提出采用组件化开发,以中台的形式完成这个需求。

​ 目前这个业务中台已经累计接入 10+ 个项目,同时也帮助孵化了一个作为消费者的下游中台。接入方只需要添加自身的数据源,管理台进行相关配置,前端引入 SDK 输入参数便能生成圈选对象再自行消费。完全不用感知中间圈选的逻辑。大大减少了前端的开发时间。接下来将介绍作为一个中台应用,前端开发中踩过的坑。

架构介绍

从 0 到 1 搭建业务中台 - 知乎-LMLPHP

​ 作为一个中台应用必然要考虑嵌入在各个项目中,在通过 iframe 页面嵌入到项目中,以及通过打包成 npm 包由接入方各自引入两种方案中我们选择了后者。因为虽然 iframe 的方式方便发布升级,以及天然的作用域隔离,但是 iframe 本身接入方操作内部状态,消息同步,调试等成本过高。最后选择了 npm 包方案。这样接入方能够方便的通过组件对外暴露的属性调用组件的方法,并且也能直接修改组件的样式,和接入方本身的主题颜色保持一致。

组件参数设置

​ 整个中台通过一个 npm 包发布到内网,并且严格遵守 X.Y.Z 语义化。各个接入方在接入组件后通过 lock 文件锁定后,即使我们发布了升级也不会影响原有项目的稳定性。当涉及到 X 版本 breakchange 变更时,通过业务群同步接入方升级。因为在内网发布 npm 包后自动会同步到 cdn 中。未来我们希望通过接入 serverless。各个接入方的组件接入方式通过 script 脚本引入,由我们统一进行版本控制。这样接入方就不需要感知组件的升级状态。

​ 在组件变量的设置中,我们暴露了通用的设置方法

export namespace StrategyCenter {
    config(options: ConfigProps):void
    primaryColor(color: string):void
}

通过全局参数设置可以指定组件使用的 鉴权方式,环境变量,筛选模式,主题颜色等参数。部分接入方只使用单一筛选方式,因此只需要一次性设置参数即可。而有的接入方可能在一个项目中使用不同的筛选模式,针对这种情况我们允许在组件中设置变量,并且遵循

接口参数 > 组件参数 > 全局变量 的优先级。

不同筛选模式切换

在针对不同接入方筛选模式不同的需求时,一开始我们采用了 维度 A * 维度B = 筛选模式 这种笛卡尔乘积的配置模式,但是随着接入方越来越多,并且存在筛选模式相同但是背后对应的数据集不同的情况。最终我们改成了每一个接入方都使用一个唯一的 dataSet 作为标识符,标识符对应了接入方需要的数据源以及对应的筛选模式。开始由后端维护在代码中,后期接入了中台管理台以后可以直接由产品进行配置。减少了开发到最后和产品需求对不上的情况(都是产品自己配的)

接口交互

​ 完成了前期的基础配置后,再回过头来看看组件核心。中台的意义在于减少接入方的使用成本,封装复杂的具有领域特色的逻辑。因此把复杂的视图交互层都封装了组件中,由组件直接和后端的中台服务进行交互。中间复杂的接口签名接入方无需感知,全部由组件封装完毕。后端的中台服务只暴露出部分的查询服务供接入方的后端进行调用。

​ 在集团内部前端框架统一的情况下,组件采用统一的框架方便接入方使用。通过 webpack 加上 typescript 打包成 npm 包以后,组件本身会暴露出类型声明文件,在涉及到对外暴露的接口时我们也要求在接口类型声明上加上注释,以及枚举值声明。这样接入方在引入包时可以获得尽量友好的开发体验。

​ 考虑到部分老旧平台存在不同的技术栈以及可能存在接入方只是想使用查询部分参数,因此我们把和后端的交互统一封装了一个 service 层,类似微信的 jssdk。所有和后端的交互都通过 service 层进行。好处在于部分接口可以在不同的组件中重复使用,并且发生参数修改时也能在同一个地方进行修改。不同的技术栈也可以自行构建合适的筛选参数再通过调入 service 层也能完成圈选对象的生成。

内部模块

​ 组件的核心在于其中的筛选模块,这一块就是整个策略中台视图交互逻辑层,并且具有很强的领域特色。圈选对象的生成是通过用户在不同的筛选模块中进行交互,筛选出符合要求的具体业务对象。筛选的模块涉及到时间、区域、指标、维度等等不同模块。并且还要求进行状态的回显、数据绑定等。此处参考了 antd 的 form 表单设计思想,把每一个模块抽象为一个筛选基类,对外暴露了统一的方法

interface BaseFilter {
  setForm(params: FormParams)
  getForm(): FormParams
  onChange<T>(filed: string, filerValue: T)
}

外部的容器初始化或者进行表单回显时依次调用各个模块的 setForm 方法,传入外层的表单参数,筛选模块内部自行响应表单参数并且完成内部状态的设置;用户在进行界面交互时,又会触发筛选模块的 onChange 事件把状态同步到外部容器,此时其它对此模块由依赖的组件响应 onChange 事件完成更新。最后外部容器调用 getFiledsValue 方法时依次收集各个模块的 getForm 方法得到表单值并且完成表单校验。

​ 这里虽然我们把筛选模块定义为了一个基类,但是代码中并不是使用 extends 的方式进行继承。只是通过暴露出了对应的方法让外部组件进行调用。面向接口编程只是一种思想,并不是强制的按照某种设计模式进行代码的编写。

​ 除了筛选组件以外,还有目标设置以及效果复盘组件,这是基于大数据部门的特点提供了完整的数据分析组件。完成业务上的闭环,在打包的时候进行分组件进行打包,接入方按需引用。未来的方向也是在维护已有筛选模块数据源的同时,继续拓展出更多的效果复盘类组件。继续挖掘大数据背后的价值。

后端和权限

​ 目前我们的涉及到的项目几乎以及后端接口都在 http://a.com 域名下。通过 SSO 中心设置的 cookie 后端能直接读取到用户的权限的信息。因此组件本身没有进行权限相关的控制,未来可能涉及到不同域名下的项目,因此可以通过在参数中接入方设置鉴权方式以及传入 token 的形式让后端进行权限校验

中台管理台

​ 中台管理台包含了 标签配置、组件文档、组件预览、组件配置表等四个模块。中台本身和 npm 包在一个仓库,发布时 npm 包和中台分别进行打包发布。

​ 组件的文档完善与否直接决定了组件的接入成本,前期通过 typescript 打包已经有了接口类型声明文件,文档中介绍了完整的组件接入流程以及对应的产品、前端、测试、后端等同学的联系方式。结尾还制作了 QA 列表列举了组件使用中常见的问题。文档约细致就能减少作为开发者被钉钉中断的痛苦

​ 组件预览中参考 elementUI 的文档,列举了组件的使用方式以及当前的效果,开发者可以直接在测试环境验证各个模块的使用方式

​ 标签配置中列举了筛选模块中的数据源,在没有管理台之前这些配置全部由产品提出 PRD,后端写死在代码中。增加新的数据集耗费时间成本,并且常常出现指标的口径错误。增加配置台以后相关的标签全部进行了建模处理,产品负责新增标签相关的属性。前端直接通过配置调取相关的资源。目前这种模式也被推广到了其它项目中,产品配置标签属性,前端已经完成了相关组件的渲染逻辑。极大的减少了开发新页面的成本。

​ 配置表中列举了不同数据集 dataSet 对应的筛选模块配置。前端通过读取模块配置渲染筛选组件,减少了由前端同学维护筛选模块可能带来的配置错误等问题。

总结

​ 许多同学可能觉得平时工作的需求无非就是改改页面没有太大的业务价值,我认为一个好的工程师善于把重复枯燥的逻辑抽象出来进而提升整个需求的开发效率。去抽象需求的一个重要前提就是深刻的了解业务,找到业务的痛点,并且找到不同业务中的共同点。在这个策略中台的开发中,我们就是找准了业务的共同点——利用平台大数据的能力精准的圈选出目标对象。这个是业务的核心诉求,接着再通过区分不同业务的差异性完成不同的筛选模块,这个过程中,面对也就不再是写页面等初级开发,而是更有挑战性的思考如何在保持统一性、可维护性的同时又能应对不同的业务需求,这也是对个人架构能力的提升。

​ 另一方面,前端社区一直推崇微前端方案。但是目前看到的微前端方案还是基于整个路由级的改动,并且要求接入方对自己的代码有一定的改动。我认为,在公司内部框架统一的情况下,完全可以采用这种 npm 包形式的“微前端“方案,比如策略中台中的效果复盘组件。组件中包含了完整和后端交互的逻辑,接入方不用感知,只需要输入参数便可完成页面的渲染。保证了数据、样式的准确性又能极大的减少前端重复开发页面的时间成本。以上只是个人关于 “微前端” 方案的一点思考,希望能和同学们在评论中探讨。

09-03 05:47