Skip to content

专题: 低代码平台的架构和思考

::: 12.04 由于最近面试被深挖了,先占个坑 :::

这篇文章的目标是结合我的经验说清楚这个东西到底是什么,并同时辅以 demo 项目和可用的代码。

由于这个项目是在公司做的,这里只能凭借个人的经验和记忆重新开发,但我相信经过自己的经验和沉淀出来的架构,会比之前做的项目更优秀

项目链接:https://github.com/Kevin031/lowcode-engine

是什么

低代码平台是指管理员通过在线的拖拽编排,生成在生产环境可用的组件页面

它的组成必定包含:数据协议 + 源码组件 + 渲染引擎。

特性:

  • 输出 JSON,适配多端(PC、h5、app)

  • 可视化的拖拽编排

  • 标准化的 dsl,通过数据驱动,属性绑定,支持丰富的动态能力

  • 在页面下支持自定义组件的无限嵌套,通过细粒度的基础组件组合万物

  • 基于 typescript 提供完整的类型定义和文档

  • 通过云函数方案解决复杂数据转换问题

  • 通过 HOC 接入组件 dsl 转换、环境变量注入、监控和埋点体系

  • 微内核的架构,通过插件的方式渐进式支持工具栏、大纲树、组件管理和 json 编辑器

keywords:dsl、多端适配、细粒度的组件、微内核、云函数

优缺点

优势:

    1. 提效 + 自由度
    1. 可视化 + 快速搭建和上线
    1. 横向能力拓展

缺点:

    1. 上手成本高,需要学习新规范
    1. 前端框架的升级成本
    1. 不好维护:定位问题,性能优化
    1. 出码后的二次开发不可逆

深度思考

根据软件开发中没有银弹的理论,低代码的目的是为了提高90%的效率,需要付出的成本是多花费90%的前期研发时间与提升90%的代码复杂度,更可怕的是这种方式通常是更封闭的,二次开发难度远超传统的开发方式。

对于某些公司的业务来讲,有大量相似的页面,且需要追求极致的效率提升,一人天产出10几个页面,那么追求低代码是可以理解的。

而如果公司本身的业务量没有那么大,对稳定性和代码质量有要求,那么传统开发的方式会更适合。

受众

  • 开发者:

    • 组件开发:通过源码开发的方式进行基础组件库的搭建和完善

    • 业务开发:通过低代码的方式进行复杂的自定义组件的搭建,以及模板的搭建

  • 项目实施:通过开发者搭建的组件和模板去搭建项目页面

  • 运营者:对上线的页面进行维护,例如文案修改、上下架组件

对比相关开源项目协议

阿里低代码引擎

网易云音乐 tango

定位和发力方向

以上缺点决定了低代码的产品想要获得成功和规模化的使用,必须在垂直方向上发力,例如

  • 海报

  • H5 活动

  • 表单收集

它们的共同特点是轻交互轻逻辑

模块划分

  • 编辑器壳工程

  • 工具栏

    • 保存和草稿

    • 微内核的架构:实现插件化的清空、回退、复制

  • 物料区

    • 物料的分类:展示组件和容器组件(按基本类型)、源码组件和自定义组件(按架构)

    • 物料数据来源:基于 git 仓库、接口调用

  • 画布

    • 渲染器(npm 包)

    • 渲染器在 iframe 方式嵌入,自带沙箱机制

  • 表单生成器

    • 数据来源:物料信息
  • 通信代理

    • 接管各个 iframe 和壳工程的通信
  • 编译器

    • 产出 JSON 数据(重运行时)

    • 产出代码(React、Vue、SwiftUI,编译时,满足二开需求)

模块展开介绍

编辑器壳工程

技术选型:React + AntDesign 或 Vue + ViewUI

编辑器的 UI 界面主要包含了 4 大块:工具栏 + 物料区 + 画布 + 表单

通常来说它的重心在于表单,因此需要选择一个较为成熟的 UI 库来进行表单的渲染

编辑器之所以是壳工程,是因为以下的所有模块最终都会聚合在编辑上表现出来

聚合的方式比较灵活,包括接口数据、npm 包、iframe,具体方式如下

  • 接口数据:物料信息(组件列表+表单协议)

  • npm 包:通信代理、工具栏、插件、编译器(实际上可以通过项目内的 monorepo 子包导入开发)

  • iframe:渲染器

物料管理

物料管理包含了两大部分:组件源码和组件描述(协议),组件描述又包含了组件的基本信息(编码和默认属性)和配置表单信息

因此,物料的数据最终有 2 个去向

  1. 组件描述:通过后端接口的扫描和入库,以接口的形式提供给编辑器调用

  2. 组件源码:通过发布 npm 包的形式,分别提供给画布和运行态应用打包导入

工具栏

工具栏我希望采用微内核的架构方案,核心只提供基础功能:保存和草稿

其余功能例如回退和恢复、变量管理、函数管理、接口管理,则是通过插件的形式接入进去

介绍一下这几块重要插件:

变量管理:可以给页面注册变量,通过模板字符串注入组件属性

函数管理:可以封装一些 JS 逻辑,通过模板字符串注入到组件事件 api

接口管理:可以封装一些请求接口的调用信息,通过各种钩子挂载到页面上,同时支持数据和变量产生的关联和绑定

画布和渲染器

由于低代码的数据最终需要在运行态中渲染出和设计态等同的效果,因此我是通过渲染器模块实现了代码的复用

画布和渲染器的关系可以这样理解:

画布 -> 入口页面 -> 渲染器(npm 包提供) -> 最终效果

运行态应用 -> 指定页面 -> 渲染器(npm 包提供) -> 最终效果

表单生成器

组件的物料信息中描述了表单信息,因此可以借助物料的表单信息去遍历出真实的表单 DOM 节点。

通信代理

信息需要在物料、画布、表单之间流转,因此我封装了一个通信代理来完成所有的信息传递,这也方便了捕获和排查问题。

编译器

默认情况下最终输出的是 JSON 数据,描述了组件树的信息。我们可以对 JSON 数据进行一定的加工,例如:标记哪些组件是需要出现在首屏的。

JSON 可以通过解析生成 AST,最终转换成代码。

物料协议

对于标准化的产品流程,物料协议是必备的,它约定了平台标准的输入输出

物料名词

  • 基础组件(Basic Component):前端领域通用的基础组件,直接由 UI 组件库提供

  • 图表组件(Chart Component):前端领域通用的基础组件,直接由图表 UI 组件库提供

  • 业务组件(Business Component):又称为源码组件,由第三方业务线为满足业务需求自行接入的组件

  • 自定义组件(Mix Component):通过编辑器将基础组件和业务组件一起搭建的混合组件,区别于源码组件,它是 JSON schema 的形式保存在线上,同时可以进行线上的二次编辑和热更

  • 容器组件(Container Component):拥有特定插槽和布局能力的组件,例如弹性盒子、弹窗容器、列表容器

  • 页面(Page):由组件组合而成,由页面容器包裹,描述页面级的状态和公共函数

  • 模板(Tempalte):垂直业务领域的已有页面,可以直接用于初始化新页面

搭建系统名词

  • 编辑器(Page Editor):壳工程,聚合了众多模块

    • 物料面板(Component List):提供了组件库的基本信息,通过拖拽挂载到页面上

    • 画布面板(Renderer):提供了可视化的页面

    • 页面渲染器(Page Engine)

    • 属性配置器(Configurator)

    • 大纲树(Outline Tree)

  • 组件库(Component Package)

    • 组件物料(Component Schema)

    • 组件源码(Source Code)

  • 全局变量:页面和自定义组件上定义的变量字典,可以通过变量绑定关联到组件上,驱动更新

  • 环境变量:系统注入的变量,包含部署容器后的二次配置、系统信息、布局信息

  • 变量绑定:通过定义的 dsl 将全局变量或者一些别的环境变量关联到组件属性上

  • 逻辑编排:通过预设的动作(点击动作、接口请求),去操作变量或执行其它的反馈动作、如弹窗等等。

  • 生命周期:一个组件的创建、挂载、更新和销毁等关键阶段的统称

协议结构

组件描述

  • {code} 组件编码,通常和基础组件一一对应,例如 Text、Image、Button

  • {version} 组件版本号,关联组件库的版本

  • {source} 组件来源,指某个 npm 包、或者自定义组件的数据包 id

  • {configuration} 组件配置信息(用于生成属性配置表单)

  • {defaultProps} 组件默认属性

  • {mapping} 组件属性映射关系,用于在低代码自定义组件的场景下将自身的属性映射到子组件上,有一套完整的 dsl 规范

  • {structure} 低代码结构,用于自定义组件,结构和页面协议相似(这一项通常不会保存到页面上,实际上是接口请求的时候又后端根据引用 id 拼凑出来

页面协议

  • {version} 页面版本号

  • {title} 页面名称

  • {pageConfig} 页面相关的配置信息

  • {variableConfig} 变量管理

  • {apiMap} 接口管理,具名化的接口请求,可提供给行为进行调用

  • {actions} 页面行为,用于在对应的生命周期钩子挂载行为,例如初始化的时候触发接口请求

  • {components} 组件映射表,{code}-{component-schema}的组合,如果

插件协议

  • {name} 插件名称

  • {mountArea} 挂载的区域,目前支持 topBar 和 sideFar 和 footerBar

缺失环节

  • 源码导入:目前出码是不可逆的,因此缺失了这一重要环节

  • 多人协同:多人同时开发同一个页面出现的冲突情况会相互覆盖(目前想到的方案是后端解决)