背景概述
在日常开发中,大部分工作的内容往往都是重复做一件相同的事。例如开发一个后台管理系统,系统内有多个模块,但基本上每个模块对应的页面的内容结构都是相同的,包含搜索框、表格、表单、弹窗等。开发者在不同的页面,利用现成的三方组件库,不断实现搜索框、表格、表单、弹窗等。
上面描述的只是大部分的情况,实际上还是有一些逻辑复杂、定制程度高的功能。与重复工作相比,定制工作占开发者的工作时间低很多。既然如此,能不能减少重复性工作内容,提高效率,进而把精力集中实现复杂的功能,优化性能等更有意义的工作内容呢?或许我们可以把一些重复的工作抽象出来,做成一个框架,提前实现固定逻辑,再通过外部简单的配置,通过框架直接生成页面。
低代码方案
早在几年前,就已经有很多解决方案出现,令人熟悉的低代码方案就是其中之一,低代码通过提前实现各种不同的物料,物料对应不同的组件,组件有基本的实现,也允许外部传参配置。开发者便可以通过一份 schema 配置,然后处理配置生成页面,省去了很多代码编写的工作。
基于以上思路,再搭建一个可视化配置平台,平台在创建页面前提供一个画布,允许用户直接拖拉组件,对页面进行布局,再补充各个组件的属性信息,然后自动生成一份 schema 配置,通过 schema 生成页面。这种平台也称低代码平台,不仅是代码开发者,对于不懂代码语法的使用者,也能通过低代码平台构建一个系统。
低代码构建页面流程如下,对于开发者来说,图中绿色区域并不是必要的,绿色区域的作用是可视化操作、预览页面效果、改变 schema 的创建方式。
建站框架设计
参考低代码的流程,设计思路可以分为三部分:“物料 ——> schema ——> 页面”,先从物料入手分析。一个页面通常会有固定不变的部分和可变的部分,如果每次构建一个页面时,都需要以组件为颗粒度,去拼成一个页面的话,就会重复固定部分的工作。因此,放大一下构建维度,从页面的角度去构建,页面模板沉淀一些固定逻辑,通过 schema 配置可变部分,可以减少重复工作。
以后台管理系统的页面为例,其结构通常都包含 Header 和 Content,而 Content 包含左侧二级菜单以及页面内容面板,当然二级菜单可以根据实际情况取舍,由此建立一个基础的 schema 配置。
const config = {
model: 'dashboard', // 模板类型
name: 'manage system'
menu: [{
key: 'menu1',
name: 'Menu1',
siderConfig: {
menu: [{
key: 's-menu1',
name: 's-Menu1'
moduleType: 'schema', // panel 的类型
schemaConfig: {...}, // panel 对应类型内容的配置
}, ... // s-menu2、s-menu3 同样结构]
}
}, ...// menu2、menu3 同样结构]
}
接下来再分析一下 panel,常见的内容结构由搜索部分和表格部分组成。
根据前端开发的习惯,一般会从组件的维度,可以以搜索框和表格入手分别在 schemaConfig 中配置:
schemaConfig: {
searchConfig: {
searchList: [{
key: 'item1',
label: 'item1',
type: 'input'
}, ... // item2 同样结构],
btn: 'search'
},
tableConfig: {
column: [{
key: 'item1',
name: 'item1',
width: '200',
// 省略其他配置...
},
... // item2 同样结构,
{
key: 'action',
name: 'action',
btn: ['查看','修改','删除']
}]
}
},
这样确实可以正常渲染,如果后面需要添加组件,就继续添加相关的 config。不过,我们可以发现,字段 item1、item2、...... 、itemN,贯穿了多个组件的逻辑,所有组件的逻辑都是围绕这字段 item1、item2、...... 、itemN 展开的,那么其实可以把这一部分提取出来单独配置,至于 [xxx]Config 只配置组件内独有的逻辑。因此配置就变为:
schemaConfig: {
schema: {
item1: {
label: 'item1',
searchOption: {
type: 'input'
},
tableOption: {
width: 200
}
},
// item2、... 、itemN 同理
},
searchConfig: {
btns: []
},
tableConfig: {
btns: [{
label: '查看'
}, {
label: '修改'
},{
label: '删除'
}]
}
}
这样配置的话,以后维护的时候能够清晰查看属性字段与组件的映射关系,如果想要把某个字段删除,只需要删除对应的 item,就可以把该字段与所有相关的组件逻辑都删除了。至此,一个常见的后台管理系统页面的基础部分已经沉淀出来了。
拓展性添加
在实际开发中,业务并不是一成不变的,随着系统版本的不断迭代,多多少少会有额外的逻辑,所以需要提供一些手段给开发者。首先要说的是 menu 的拓展,如果一个新系统对 menu 的内容,有 50% 要复用,另外 50% 要自定义,该怎么做?
对于该问题,使用面向对象编程的思想去解决。上面也提到,要把基础的部分沉淀出来,除了把页面结构沉淀,也可以把一些基础业务沉淀出来,此时,沉淀出来的 schema 可以看作一个表示基类的 schema。后续如果有新系统,有部分功能需要复用,有部分需要自定义,就可以以继承的方式,产生新的表示子类的 schema,用来生成页面。
上图表示子类的 schema 把基类 schema 继承后的结果,绿色部分表示基类独有的,红色部分表示子类独有的,蓝色部分表示子类把基类覆盖的结果。此时,不仅仅是 menu 可以灵活拓展,关于配置内的所有功能其实都能够拓展的,例如组件的拓展,由于在页面开发的时候,会暴露一些页面的钩子和事件的钩子,只要再 schema 中添加对应的配置即可完成。
schemaConfig: {
schema: {
item1: {
... ,
searchOption: {...},
tableOption: {...},
// 组件拓展
compOption: {
comp1: {}
}
},
// item2、... 、itemN 同理
},
searchConfig: {...},
tableConfig: {...},
// 组件拓展
compConfig: {
comp1: {}
}
}
如果开发者想摆脱系统提供的 panel 结构,也可以自定义页面,只要把页面和路由指定好,就能够实现了。
const config = {
model: 'dashboard', // 模板类型
name: 'manage system'
menu: [{
key: 'menu1',
name: 'Menu1',
moduleType: 'custom', // panel 的类型为用户自定义
customConfig: {
path: '/custom', // 页面路由路径,此处为 '/custom'
},
}]
}
服务端能力
后端的工作与前端有相似之处,都是存在着重复性工作,为了提高效率,需要把固定不变部分与可变部分分离,沉淀出固定部分,拓展可变部分。紧接上文,以上文的模板页为例,在后端项目中的业务逻辑主要为:
- 接口参数校验
- 数据的增删改查
- 数据的进一步处理
- 调用其他服务
其中 "数据的增删改查" 是固定必有的逻辑,一般都是以 “router ——> contorller ——> service” 这种结构去处理。所以只要在 schema 中提供接口路径、参数信息、操作行为,就能够实现基本功能。
接口路径与操作行为可以通过 RESTful 规范去声明,这样在 schema 中只需要提供一个 api 参数就能够表示了;而参数信息可以复用 schema 中的参数配置信息,在原有参数配置基础上添加与 api 有关的配置。
schemaConfig: {
api: '/api/model/scene',
schema: {
item1: {
... ,
searchOption: {...},
tableOption: {...},
compOption: {...},
// api 选项
apiOption: {...}
},
// item2、... 、itemN 同理
},
searchConfig: {...},
tableConfig: {...},
compConfig: {...},
// api 配置
apiConfig: {...}
}
通常这种业务场景的接口参数以 json 的形式表示,所以接口参数校验上,以 json-schema 规范执行,对接口 json 数据中的每一个字段配置 rule,配置完 rule 后的 schema 格式如下:
schemaConfig: {
api: '/api/model/scene',
schema: {
type: 'object',
properties: {
item1: {
type: 'string'
[xxx]Option: {...},
...
},
// item2、... 、itemN 同理
},
required: ['item1', ...],
...
},
[xxx]config: {...},
...
}
代码中除了基本逻辑,还会暴露出一些后端的钩子,使开发者可以进一步处理数据,或者调用第三方服务等等,从而满足定制化的需求。
业务模型沉淀
通过 “schema + 模板” 实现了对代码的复用,提升了重复工作的完成效率。业务系统中,大部分的代码又是跟业务相关的,那么,能不能够对与一些重复相似的业务能力进行提取,进而复用呢?
一个系统通常会由多个板块组成,每个板块会对应处理一种子业务,不同板块之间的可能没有关联,也可能会有一定的关联性。例如:一个电商管理系统由商品管理、订单管理、用户管理组成;另一个电商系统除了由商品管理、订单管理、用户管理组成外,还加了一个消息管理。不难发现,对于同一性质的系统,里的板块组成类似,板块内的业务处理逻辑也相似,那么就可以从 “板块” 的颗粒度入手,沉淀业务复用能力。
一种 “业务板块” 的内容包含了前台页面、后台 api 接口、以及数据库表,这种文中提到的 schema 构建方式刚好能匹配这种能力模式,对于一种 “业务板块”,可以用一种 “schema” 来表达。通过把多种板块拼接,表示把多种子业务组合,形成一种 “业务模型”,这种 “业务模型” 服务于一类业务相近的系统。这样,就能够实现对重复业务的沉淀。
业务拓展
沉淀了重复的业务,沉淀了固定不变的部分后,同样需要思考拓展性问题,对于一些定制化的内容,是无法避免的。从业务角度来看,实现思路与代码方案类似,也是使用面向对象的思想,先封装好一个 “基类业务模型”,根据定制需求实现不同的 “子类业务模型” ,再对 “子类业务模型” 实例化,创建出一个 “实例业务系统”。
至此,一个具有代码复用能力,业务复用能力,而且具备一定拓展性的建站框架就完成了。在框架中,只需根据规则完成 schema 配置,便可以完成系统的创建,同时可以定制化特有功能,满足各种额外场景。