低代码平台

753 阅读14分钟

定义

无代码开发平台(no-code development platform,简称NCDP

是可以让程序设计者及一般用户不用传统程序设计语言即可以开发应用程序的开发平台,使用的是图形用户界面(GUI)以及配置。

低代码开发平台(low-code development platform,简称LCDP

是一种方便产生应用程序的平台软件,软件会开发环境让用户以图形化接口以及配置编写程序,而不是用传统的程序设计作法。

目前现状

低代码这个概念真正火热起来,还是在于这两年 Outsystems 相继完成了数轮过亿元美金的融资,估值早早地站上了十亿美金级别,成为一方独角兽。由于国内这一领域缺少体量对等的厂商,所以大家自然也在期待哪家厂商能成长为中国的 Outsystems。与此同时,国内低代码赛道上选手也渐渐进入了大家的视野,例如钉钉宜搭、即刻应用、氚云、简道云等等。

案例分析

低代码-阿里(侧重架构思想)

demo:lowcode-engine.cn/demo/index.…

低代码体系的架构设计思考

自下而上分别是协议 - 引擎 - 生态 - 平台

  • 底层协议栈定义的是标准,标准的统一让上层产物的互通成为可能
  • 低代码引擎是对协议的实现,同时通过能力的输出,向上支撑生态开放体系,提供各种生态扩展能力
  • 引擎生态,是基于引擎核心能力上扩展出来的,比如物料、设置器、插件等,还有工具链支撑开发体系
  • 最后,各个平台基于引擎内核以及生态中的产品组合、衔接形成满足其需求的低代码平台

每一层都明确自身的定位,各司其职,协议不会去思考引擎如何实现,引擎也不会实现具体上层平台功能,上层平台的定制化均通过插件来实现,这些理念将会贯穿我们体系设计、实现的过程。

架构的分析

协议栈

  • 术语是我们沟通的基础,概念相通,我们才能高效沟通。我们根据物料的颗粒度,定义了基础组件、区块、低代码组件、模板等术语,另外还包括低代码生产过程中一些模块名称,比如编辑器、画布、事件绑定、数据绑定、渲染、出码、设置器之类的术语,
  • 结构, 包括页面描述的结构,如何定义页面组件树、数据源、生命周期、页面状态等等
  • 行为,不同的业务场景,我们对物料的配置、约束、扩展各不相同,所以我们在物料描述中有各种各样的钩子来支持自定制。
引擎内核

  • 入料模块就是将外部的物料,比如海量的 npm 组件,按照《物料描述协议》进行描述。 注意,这里仅是增加描述,而非重写一套,这样我们能最大程度复用ProCode体系已沉淀的组件。将描述后的数据通过引擎 API 注册后,在编辑器中使用。
  • x,本质上来讲,就是不断在生成符合《搭建协议》的页面描述,将编辑器中的所有物料,进行布局设置、组件 CRUD 操作、以及 JS/CSS编写/逻辑编排等,最终转换成页面描述,如下图
  • 渲染,顾名思义,就是将编排生成的页面描述结构渲染成视图的过程,视图是面向用户的,所以必须处理好内部数据流、生命周期、事件绑定、国际化等,如下图

  • 出码, 就是将页面描述结构解析和转换成应用代码的机制。

引擎生态的设计

物料、设置器和插件三个组成强大的生态;其中插件是扩展的入口,包括物料和设置器也是通过插件才能注册到引擎。

  • 蓝色部分是插件,这些都是能被看到的插件,因为调用的是面板 API,不仅如此,还有一些不能被看到,比如调用了快捷键 API,拖拽 API、事件 API 等。
  • 红色部分就是设置器了,可以定制我们如何给一个节点的属性赋值
  • 橙色部分就是物料了,其实物料本质上是一个模型,也是不可见的,不过这里通过物料面板调用了物料 API 来显性化展示了物料,再通过拖拽 API 和 节点 API 来拖拽并插入到画布中。

编排和渲染的双层架构设计

通过这个架构,我们实现了绝对纯净的编辑态渲染,即模拟器实现

为什么:编辑器中内嵌一个所见即所得的渲染模块,因为编辑器中各个模块,物料、设置器、插件都来自不同的团队,很容易产生 css 污染

怎么解决: 模拟器放入到一个新的 iframe 中运行,通过编辑器将相关资源注入到模拟器,建立数据通道,使用 facade 模式,即在编辑器和模拟器中各有一个 facade 对象来负责对外的方法暴露和调用,避免深度耦合。

低代码平台扩展能力

平台还有那些问题?
  • 搭建出来的页面描述保存到哪里去?
  • 研发流程如何定义?
  • 版本管理,多分支咋搞?
  • 页面区块 / 低代码组件 怎么搭建?怎么使用?
孵化器

引擎之上再加上一层,形成一个低代码平台的基座,或者叫孵化器

  • 解决产品能力的问题,实现了应用管理、研发流程、打包流程、发布流程 等一系列能力
  • 解决快速在找到符合需求的生态元素组合
包含哪些能力以及支持哪些定制化

  • 设计器:提供一个开箱即用的标准版页面设计器,开箱即用意味着整合了一批插件,插件都已经跟后端服务相绑定了;提供简单版、进阶版设计器定制方案。
  • 运行时:提供稳定的,功能丰富的运行时 SDK,包括页面描述的获取、路由、layout,甚至还有一套运行时中间件机制
  • 生态:提供「生态中心」,大量组件、插件、解决方案唾手可得;提供「一站式研发平台」,可开发、调试低代码领域的所有物料
  • 管理后台:提供功能完善、方便定制的管理后台模板应用,包括研发流程、应用依赖管理、打包配置、路由配置等
  • 后端服务:官方提供 140+ 网关接口,覆盖设计器、运行时、管理后台等全流程;允许上层平台注册服务到 UIPaaS,供其他平台使用。

低代码-美团乐高(侧重于功能模块分析)

基本视图布局

  • 组件树 组件树是页面的骨架(①所示区域)部分,由内置的各个组件组装而成。乐高为组件树提供了丰富的操作(②右键弹框)选项。除了添加、拷贝粘贴、预览、删除等功能,还可以通过拖动组件在组件树中的位置(③区域),即时的在预览区域展示出效果。
  • 预览页面 预览区块(④所示区域)占据了页面的右半边部分。在组件树中,每个组件都可以单独预览。组件的预览,显示的是这个组件及其子组件共同作用的效果。预览根组件,能看到完整的页面。也可以通过 “页面预览” 按钮进行完整页面的预览。
  • 右侧模块属性 每个组件,都有可配置的属性,打开⑤区域的面板可以对左侧选中的组件进行配置。如,配置按钮组件的颜色、大小等,都取决于组件开发者对该组件的预留项。
 /**
 * 该接口用来描述组件配置的相关属性,其子组件可以在编程/渲染接口中读取到父组件的配置信息
 *
 * type:数据类型,目前含盖的数据类型:
 *  text: 文本输入框类型
 *     textEx: {
 *       name: '测试属性1',
 *       type: 'text',
 *       def : '默认值',
 *       desc: '属性描述'
 *   }
 *  select: 下拉选择框类型
 *     selectEx: {
 *       name: '测试属性1', //最长不超9个字,否则内容尽量放到注释里
 *       type: 'select',
 *       options:{
 *          value1: '这是值1',
 *          value2: '这是值2',
 *       },
 *       def   : 'defValue',
 *       desc: '属性描述'
 *   }
 *  textarea: 多行输入框类型(配置同text)
 *  radio: 单选选择框类型(配置同select)
 *  checkbox: 复选类型(配置同select,最终值为 value1,value2 逗号分隔)
 *  button: 按钮
 *     textEx: {
 *       name: '测试属性1',
 *       type: 'text',
 *       def : '默认值',
 *       desc: '属性描述',
 *       color: '颜色',
 *       width: '宽度',
 *       height: '高度',
 *   }
 */
exports.model = {

}
  • 顶部页面操作 ⑥区域部分,包含对当前视图的操作。视图可以理解为一个独立的页面,包含了打开、发布、重命名等等功能。
  • 左侧导航 ⑦区域部分,包含了三个可选标签。

    • 第一个是下图所选的组装工厂。
  • 第二个是组件的开发工厂。
  • 第三个为整个系统的健康、QPS等等运营数据的实时监控。

同步数据

  • 乐高系统中提供了数据源的概念。用于模拟、校验模板页面中的数据。为了保障页面在乐高中的正常开发和预览,我们需要Mock一部分同步(可理解为后台写入ModelView的)数据。
  • 将JSON格式的数据写入数据源,即可实现Mock数据。此外,在后端调用乐高接口时,数据源还会校验传入数据的合法性。

组件

组件是组成任何一个视图的最基础元素,是整个平台的基石。组件之间的耦合度、通信、可扩展性、易用性是否足够强大,很大一部分程度上决定了整个系统的质量

  • 组件配置:每个独立组件提供了8个需要实现的接口:
 /**

 * 组件基本展示及功能规则的描述,目前对外暴露了8个接口(name, pyname, desc, leaf, uilib, model, script, render)

 */
'use strict';
 /**
 * 组件名称,用来标识该组件在系统中的引用名称。
 * 取值可以为汉字,大小写英文字母,数字和下划线的组合
 * [建议取名为英文,每个组件name唯一,主要供程序和RD使用]
 */
exports.name = 'Sample';
 /**
 * 组件别名,只能为汉字或者字母
 * [建议取名中文,每个组件pyname唯一,主要供PM等对hmtl及组件专有英文名称不太熟悉的人使用]
 */
exports.pyname = '中文名称';
 /**
 * 组件描述
 */
exports.desc = '';
 /**
 * 该组件可以添加的叶子节点
 * 1.如果可包含子节点,请在数组中添加组件id,如: exports.leaf = [12,23,34]
 * 2.如果不可包含任何子节点,请将leaf置为null,即: exports.leaf = null
 * 3.如果可包含任何子节点,请将leaf置为空数组,即: exports.leaf = []
 */
exports.leaf = [];
 /**
 * 当前组件需要适配的组件库
 */
exports.uilib = 'kui';
 /**
 * 该接口用来描述组件配置的相关属性,其子组件可以在编程/渲染接口中读取到父组件的配置信息
 *
 * type:数据类型,目前含盖的数据类型:
 *  text: 文本输入框类型
 *     textEx: {
 *       name: '测试属性1',
 *       type: 'text',
 *       def : '默认值',
 *       desc: '属性描述'
 *   }
 *  select: 下拉选择框类型
 *     selectEx: {
 *       name: '测试属性1', //最长不超9个字,否则内容尽量放到注释里
 *       type: 'select',
 *       options:{
 *          value1: '这是值1',
 *          value2: '这是值2',
 *       },
 *       def   : 'defValue',
 *       desc: '属性描述'
 *   }
 *  textarea: 多行输入框类型(配置同text)
 *  radio: 单选选择框类型(配置同select)
 *  checkbox: 复选类型(配置同select,最终值为 value1,value2 逗号分隔)
 */
exports.model = {

}
 /**
 * 组件脚本。会插入到页面html中执行,组件内部逻辑或与外部交互可放到该函数中执行
 * @param mvId 组件用到的mvId,组件唯一标识
 * @param evtMgr 页面全局事件中心,可以通过 bind(evt, handler) unbind(evt, handler) 和 trigger(evt, data, context)三个方法控制事件流的绑定和触发
 * @param modelData 组件属性数据,默认传参encode字符串,首先需要decodeURI,然后换成json对象
 */
exports.script = function (mvid, evtMgr, modelData) {
  modelData = JSON.parse(decodeURI(modelData));
}
 /**
 * 该组件在用户界面的展示
 * @param {Object} node node中包括{_children:[], _parent:'', _innerHtml:'', _modelData:{}, _mid:'', _mvid:'', _mname:''},还有其他字段暂不建议使用
 */
exports.render = function (node) {
  var _modelData = node._modelData;
  var _children = node._children;
  var content = '';
  _children.map(function (child) {
    content += child._innerHtml;
  })
  return ``;
}
  • 功能逻辑梳理: 乐高的组件作为一个界面系统的元素部分,在设计之初需要考虑到以下的五个方面。通过这几部分,逐渐的形成了一套比较完善的模块化方案

  1. 组件的通信

业务组件之间的数据传递,是一个比较常见的的场景。

我们给每个组件实现的编程(Script)接口中传递了三个参数mvid、evtMgr、modalData。其中,evtMgr就是乐高系统中的事件中心。通过绑定或者触发相应的事件,在实现模块间通信的同时,较好的解耦了模块

 // 事件的触发
  evtMgr.trigger('tata', {a: 1});
  // 事件的绑定
  evtMgr.bind('tata', (params) => {
    // TODO Here.
    console.log(params)   // {a: 1}
  })
  1. 数据给组件的交付

部分业务组件会有自己的数据。数据从来源划分,可以分为系统数据、配置数据。系统数据又可以被划分为同步数据和异步数据:

  • 配置数据来自使用乐高配置的人员,在开发模块的预留接口中配置信息。
  • 系统数据中的异步数据可以通过AJAX的方式从后端拉取。
  • 同步的数据,装配时可以配置在数据源中,方便预览效果。使用时可以直接在模块的Render接口中调用。
  exports.render = function (node) {
    var _modelData = node._modelData;
    var _children = node._children;
    var content = '';
    _children.map(function (child) {
      content += child._innerHtml;
    });
    return `${data_from_datasource}`;   // 数据源数据字段读取
  }
  1. 组件的版本控制

为了在修改、发布组件时,不对线上页面造成影响,也为了满足多人协同开发的需求,我们实现了简单的版本控制功能。

每个组件预留了开发者版本。开发完毕后,需要单独的发布该组件。在视图中进行组装时,可以选择所需要的版本,如果新版本有问题,能够及时做到单独模块的线上回滚。

  1. 组件的渲染

乐高中比较核心的功能。是实现了一个页面的解析引擎。输入为在工厂中形成的页面描述的数据结构,逐步添加外部资源(数据源、界面资源库、模块)进行组合,进而生成最终的HTML或者模板。

重点回顾

从架构上考虑

  • 分成架构:协议 - 引擎 - 生态 - 平台

  • 协议栈

    • 术语:定义了基础组件、区块、低代码组件、模板等术语,概念拉通;
    • 结构:如何定义页面组件树、数据源、生命周期、页面状态
    • 行为:推动钩子、生命周期、数据流等
  • 引擎

    • 物料:海量api
    • 编排:节点转换为页面描述
    • 渲染:页面描述转为视图
    • 出码:视图转为代码
  • 引擎生态的设计
    • 插件:各种面板 API组件
  • 设置器:节点的属性赋值
  • 物料:vue、antd等API
  • 编排和渲染的双层架构设计:iframe潜入
  • 低代码平台扩展能力

    • 低代码平台的基座,孵化器
  • 提供版本管理、管理后台、后端服务等;

从功能模块分析

  • 基本视图布局

    • 组件树 :
    • 预览页面
    • 右侧模块属性
  • 同步数据:Mock数据

  • 组件

    • 组件的通信
  • evtMgr乐高系统中的事件中心
  • 数据给组件的交付:通过AJAX的方式从后端拉取,配置数据Render接口中调用
  • 组件的版本控制
  • 组件的渲染:实现解析引擎,包含数据校验、递归解析、模版组装等;

下期期待

目前缺少对插件的接入、代码的渲染等底层实现,下次深入分析,敬请期待

更多参考

github.com/taowen/awes…