前端跨平台数据模型优化实践

2021-03-19 12:40:12 浏览数 (1)

作者:zawaliang  腾讯TEG前端开发工程师

|导语  在重逻辑的投资并购业务前端开发中,随着业务平台的扩张,以及团队成员的增长,如何能更高效的提升研发效能及规范数据管理,以达到降本提效?

01

背景

伴随着业务发展,很多时候我们需要将前期Web的业务扩张到App、H5等平台上;常规做法,我们一般会基于平台去建不同的前端开发仓库,来实现业务目标;

在基于MVVM设计模式的前端架构中,随着业务复杂度增加、以及功能模块在不同平台间需要同步实现的要求,会出现维护成本越发增加的趋势;而这种维护成本随着团队成员的增加以及理解差异又会被再次放大;

上面这种情况正是我们投资并购系统研发业务在实际开发过程中遇到的场景。

02

业务痛点

投资并购业务有以下几个特点:

  • 专业性较强,有较高的业务理解成本;
  • 跨平台,业务功能需要在不同平台间同步;

专业性体现在投资并购业务非2C业务,具有很强的专业属性,没有相关从业经验比较难懂;如专业的行业术语、复杂的流程管控逻辑、重表单、多角色、重权限控制等;

跨平台体现在我们的业务需要跑在Web、App(Hybrid)、H5等平台上,而我们的不同平台由于历史原因存在一些基础框架不统一的情况(AngularJS、React并存),在各平台间保持基本的逻辑同步在现有架构上有着不少的开发维护成本;在堆人的开发策略上,交付效率并不一定能很好的提升。

03

前端架构优化

私有NPM仓库

如上图,基于这种MVVM的架构开发模式,我们发现上面说的业务成本其实很大部分是花在了Model层中;在我们的架构中,Model层是一种与框架无关的设计,只负责数据相关的处理;通过对核心Model的抽离维护,建立通用的数据模型层,以达到维护一份来降低成本的目标;

我们基于Verdaccio搭建了一套私有的NPM仓库,将通用数据模型封装成NPM包的形式来调用;

这个方案在一定程度上缓解了上面的问题,开发跟维护成本都有了一定提升;而且针对投资并购这种对业务安全要求较高的业务,私有仓库在一定程度上可以提升自有NPM包的代码安全管控;

但这个方案在实际跑下来后存在几个问题:

  • 高频变动的逻辑封装到NPM包,在开发调试、包版本管理及发布上都存在不少的成本;
  • 随着业务发展,私有NPM包数量也会膨胀,存在互引用,有种牵一发而动全身的感觉;

一句话总结就是成本转移,不是私有NPM仓库不好,是用的方式不太对;

跨平台收拢

回归需求本身,我们希望降本提效,不应该人为制造那么多的分裂;跨平台的核心问题就是视图层的差异,其他很多部分在底层上是可以复用的,架构的设计上应该更简单一点;

我们把原来分散的前端仓库整合成了一个仓库,将跨平台可复用的部分提取到了/shared目录,其他平台目录更多负责自身平台的渲染逻辑;私有NPM仓库继续保留,用于承载更多业务无关的一些私有包;最后通过优化打包构建流程,这样我们就可以有效降低跨平台间的开发维护成本;

Model的问题

模型的封装,我们一般会基于最小单元原则(在投资并购业务场景中,就是公司结构、项目结构、交易结构、角色结构、文件结构等很多数据实体),做到尽可能复用;

class UserModel {

    constructor(data) {

        this.name = data.name || ''

        this.age = parseInt(data.age, 10) || 0a

    }

}

class ListItemModel {

    constructor(data) {

        this.title = data.title || 'N/A'

        this.time = data.time || '--'

        this.users = (data.users || []).map(v => new UserModel(v))

    }

}

const response = Api.getData()

const list = response.map(v => new ListItemModel(v))

我们一般会写类似上面的代码,但这里的代码里有很多的数据类型兼容、赋值转换等操作,代码显得很冗余。

JavaScript的弱类型是一个老生常谈的问题,设计之初造就了其灵活性与易用性,但不可避免的会出现很多常见的隐私类型转换等带来的问题。TypeScript虽然可以解决很多静态类型检查的问题,但很多时候我们是在跟API接口打交道。前端业务的稳定性一部分决定于API接口的数据输出格式的严谨性,一部分决定于前端工程师对这些数据的兼容处理。能否对数据有一种高效的动态类型转换的方案,来提升整个团队的开发效率及系统稳定性?

通过对业务中遇到的问题梳理,对Model的处理有几个较明显的问题:

  • 缺乏数据类型定义,来减少隐式数据类型转换带来的风险;
  • 缺乏数据结构描述,来高效定义数据之间的关系;
  • 缺乏数据类型转换,来达到运行时数据类型转换,减少冗余代码;
  • 缺少异常收集机制,来快速定位问题;

Model的优化

为了规避API输出时可能存在的类型、默认值等的不规范问题,以及JS隐私数据类型转换等问题,我们需要对预期的值类型有一个清晰的定义。类型可以是基础数据类型以及任意自定义类型。

class DealModel extends Model {

    constructor (data) {

      super(data)

      this.schema({

        name: String,

        count: Number,

        expire_date: Date,

        flows: [String]

      })

      this.parse(data)

    }

}

通过对象、数组与值的自定义关系表达式实现对数据结构的关系描述,来简化整个模型的关系定义。

class ProjectModel extends Model {

    constructor(data) {

        super(data)

        this.schema({

            // 定义值表达式定义数据类型

            name: String,

            // 通过数组表达式定义列表关系

            deals: [DealModel],

            // 通过对象表达式定义父子关系

            props: {

                edit: Boolean,

                deleted: Boolean

            }

        });

        this.parse(data)

    }

}

const response = Api.getData() // response like { name: '拼多多', deals: [{deal_type: 'xxx', name: 'xxx'}], props: { edit: false, deleted: true } }

const model = new ProjectModel(response)

最后通过对自定义关系表达式及数据类型的处理,来达到运行时数据类型解析的目的。

Model是我们为了解决这些问题引入的基类,先不用关心其实现,这只是一个简单的例子。

这里只是为了让大家看到我们通过简单的JavaScript语法将key-value的结构进行了描述,让模型获得了自动解析的能力;我们通过给属性指定数据类型,把类型转换的工作进行了收拢,我们不需要再写那么多冗余码了。

数据异常上报&快照

上面提到,前端项目的稳定性很多时候决定于后端API输出类型的稳定性。在不断迭代的过程中,可能会出现API的改动受到影响而没有知会前端的情况,造成前端报错用户侧使用异常。虽然通过JS异常上报可以发现问题,但这个定位过程还是不够直接。

为了规避这类问题,提升定位效率,通过统一的数据结构描述,我们把数据类型、属性、模型进行了关联,在数据解析的过程中我们可以做到从源数据到模型再到属性节点的异常收集,可以很明确的把问题直接暴露出来,结合异常上报、告警,我们可以在这类型问题出现时及早进行定位修复。

投资并购类业务对数据的准确性要求是非常高的,当用户看到某个数据并产生疑问时,我们希望做到有据可依,快速定位到这个值的整个处理过程并找到问题根源。

我们在整个建模过程中,通过收集节点解析前后的源数据与结果数据,以及节点在页面生命周期所发生的变化,建立解析过程的时间管道,将一次数据解析过程中所发生的所有关键数据进行了快照。通过对这些快照进行上报,我们可以回溯任意一个过程中发生的数据变更操作。

04

总结

复杂业务的前端开发架构中,建立一套高效的数据模型开发体系能有效提升团队的开发效率,降低维护成本。

近期热文

【Node开发】分布式调用限频限流的开发设计

解决单点故障 - 有状态服务的高可用

如何输出有价值的商业解决方案?

让我知道你在看

0 人点赞