一种关于低代码平台(LCDP)建设实践与设计思路

背景

负责菜鸟商业中心CRM系统开发已经有1年多时间,过程中发现有一个痛点:业务线特别多,每个业务线对同一个页面都有个性化布局和不同的字段需求,而我所在的团队就3个人,在资源有限的情况下如何支撑好呢?刚开始,我们是为各业务线单独定制页面和业务逻辑,1到2个业务线还能应付过来,目前已经发展有十几业务线,且每个业务线下还有子业务线,这种个性化的开发多了,工作量就大了,系统维护压力就巨大。所以就孕育而生了—— 销售魔方类低代码产品,与其说低代码产品,还不如说是一种解决千人千面的个性化业务搭建的前后端一体的解决方案。

本文就降本的情况下,我是如何低成本构建产品能力去支撑多条业务线、多租户,我先以小实践成果展示,再过度分享我后续升级的设计思路。

什么是LCDP

低代码开发平台(Low-Code Development Platform)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法(参考可视编程语言),使具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。低代码开发平台(LCDP)的正式名称直到2014年6月才正式确定,整个低代码开发领域却可以追溯到更早前第四代编程语言和快速应用开发工具。

魔方核心能力

产品能力

上图是魔方1.0 MVP版本基本运行原理,以及上线后降本增效的数据,业务开发从60人日缩短到20人日,年省成本180人日。

以上版本基本满足了80%以上的业务个性化需求自闭环开发。

还有一些小问题,基于这个版本,我们又不断的升级,提升产品体验、能力提升和业务覆盖。

后续我们可做到新页面上线,只需5分钟,新增字段无需模型变更和无需java代码发布,复杂页面前端也能做到0代码。

基于我们业务的诉求,所以销售魔方需具有以下几个核心能力:

  • 页面的千行千面(千人千面),包含同一个页面不同布局、不同字段、不同样式。
  • 数据模块的千行千面(千人千面),根据不同身份执行不同的业务技术逻辑和服务编排。
  • page一键创建,在没有新的业务组建和新的module情况无需开发接入,0代码上线,运营同学自行配置页面。
  • 前端组件复用,在没有新前端组件,前端无需参与开发,后端只需编写module对应的业务接口。
  • 实现module可复用,module数据渲染、数据写入,查询条件、浮层、半推页面、页面操作。
  • 新增字段扩展0代码,模型字段可以自定义,动态扩展,可定义来自本地数据库、远程HSF接口数据。
  • 环境可隔离,测试、预发、生产。
  • 平台和业务代码分离,业务上线只需关注业务逻辑本身的代码。
  • DO DTO可定义,动态映射。
  • 数据枚举动态定义,动态绑定。

魔方的设计

产品界面

先展现一个实例配置界面,有个体感



通过以上配置可以个性化配置页面输出的布局和字段,动态输出个性化页面

用户

运营:运营可以根据自己的业务身份,定义独有的page实例,自由选择页面的版块和需要展现和编辑的字段

产品:配置初始化页面,module、以及全量字段池

技术:定义元数据,module,编写module group逻辑执行单元代码

产品模块

核心逻辑

一般低代码平台,主要分为两部分,前端页面的渲染和后端服务接口绑定(服务编排等)。

大的逻辑差不多,因为我这个主要还是具有行业特色的类低代码产品,所以是紧扣行业特殊性构建。

前端渲染

因为我负责的是后端,所以前端我就不过多叙述,大概的逻辑如下两张图,大致意思是前端的页面渲染就如同做菜一样,用户根据自己的需求,可选择菜谱、食材和烹饪方式,不用关心烹饪过程,也不用自己亲自烹饪,都叫由厨师烹饪,厨师会根据你提供的菜谱和食材做好,最后将美食给你端上桌。

同理,前端渲染引擎会根据数据协议、组件库、渲染方式,动态渲染成页面,如果有业务数据将会动态绑定。

后端绑定

我们这有个特殊性,页面是通过后端给定schema,由前端根据这个schema进行页面渲染。后端通过识别出用户的身份,通过接口输出给前端千人千面的个性化schema,前端就通过schema配置动态去渲染。

这样就能实现我们说的同一个功能页面,不同业务身份展示不同的布局和字段。

同时,还会会有一个业务数据接口,用于绑定前端页面填充业务数据或提交表单数据。

每一个组件绑定业务数据接口后,就不是单纯的前端组件了,就具有行业业务属性的组件和页面,这样在这个领域是可以被业务系统直接复用,无需重新编写业务代码。

这里有一个难点,每次新增业务线(租户)后就有新增字段需求,而且字段的差异还挺多,约占80%,在不修改模型的前提下,如何做到?

带着这个问题可以先思考下。

一般解决方案有以下几种:

1、通过设计纵表,以扩展字段。缺点就是查询复杂度高。

2、元数据驱动。缺点就是成本高,架构更加复杂

3、通过定义一个特殊feature字段,存储类型为json,约定好协议,扩展字段按照不同行业存储在json内。优点就是轻量。

根据我们自身的定位和资源情况,最终选择的是第3种方案。

这里有个担心就是json数据方便搜索吗?性能如何?

mysql5.7上,在相同数据量的情况下,虚拟索引和普通索引查询效率基本一致 在大数据量情况下不推荐用聚合函数计算json数据,但是如果json key建立了虚拟列,可以对该虚拟列进行聚合操作,效率跟普通列一样。

模型设计

整个模型可以看出,从左往右:

DO的定义、DTO的定义、module定义(等同VO定义)、原始页面定义(originalPage)、实例化页面配置(instancePage)

这个过程基本描述了一个页面是如何定义出来的,以及动态模型的扩展(模型驱动),从模型到页面组件的布局定义。

行业个性化配置则通过对OriginalPage实例化成InstancePage而得。这样就能动态生成千行千面。

OriginalPage到InstancePage这里借鉴了java的思想,类似Class和实例对象。

渲染页面逻辑

页面在渲染的时候,只需两个接口:

1、实例页面数据结构 ;2、业务数据。

前端根据两个接口分成两步渲染:

1、初始化页面布局;2、填充业务数据。

这个借鉴了Spring容器启动时的设计思路,类似实例化、初始化,属性数据填充。

template定义

即是对页面类型的定义

  • 列表页面
  • 详情页面
  • 半开页面
  • 表单提交页面

page定义

定义一张前端页面,分成两个阶段:origin;instance。

origin是定义的原始页面,可以理解成java 的Class类,可以构建多个实例页面。

instance page是最终渲染的运行态。

页面结构:一个page 由N个module组成、一个module由N个field组成。如下图

一个实例page由以下维度定义

1、用户传入参数

page_code

custom_dimension

2、系统获取参数

biz_code

sub_biz_code

enviroment

module定义

页面的组成单元,一个页面由多个module组成。代表页面显示区域单元,有多个前端组件组成,是页面容器的布局单元。

module_code 全局唯一,实例化后的module可被复用、重写、实现多态。

modules 数据结构为一个 B+tree,只有叶子节点是有具体的实体数据。

叶子节点是真正包含具体的字段和属性配置

module_type 定义

对module类型的定义

1、主列表查询模块 MAIN_LIST_MODULE

2、导出模块 EXPORT_MODULE

3、弹出页面模块 FLOAT_PAGE_MODULE

4、搜索条件区域 SEARCH_ARE_MODULE

5、子列表查询模块 SUB_LIST_MODULE

6、编辑表单模块 EDIT_MODULE

7、信息平铺呈现 DISPLAY_FLAT_MODULE

McubeContextAware

module容器代码定义

@Component
public class McubeContextAware implements ApplicationContextAware {private static volatile ApplicationContext alc;@Resourceprivate ModuleBeanFactory moduleBeanFactory;@Resourceprivate ModuleGroupBeanFactory moduleGroupBeanFactory;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {alc = applicationContext;}@PostConstructpublic void init(){setModuleBeanMap();setModuleGroupBeanMap();}private void setModuleBeanMap() {Map<String, McubeModuleExecutor> beanMap = alc.getBeansOfType(McubeModuleExecutor.class);if (beanMap != null) {beanMap.values().stream().forEach(m -> {McubeModule module = AnnotationUtils.findAnnotation(m.getClass(), McubeModule.class);if (module != null) {String code = module.code();String name = module.name();if (code != null) {moduleBeanFactory.getMcubeBeanMap().put(code, m);}}});}}private void setModuleGroupBeanMap() {Map<String, McubeModuleExecutor> beanMap = alc.getBeansOfType(McubeModuleExecutor.class);if (beanMap != null) {beanMap.values().stream().forEach(m -> {McubeModuleGroup module = AnnotationUtils.findAnnotation(m.getClass(), McubeModuleGroup.class);if (module != null) {String code = module.code();String name = module.name();moduleGroupBeanFactory.getMcubeBeanMap().put(code,m);}});}}
}

执行单元(moduleGroup executor)

为了保证页面的数据填充效率,所以并不是一个module绑定一个服务接口,

而是一个执行单元对应一个或多个module,负责多个module的数据渲染和数据写入,moduleGroup executor是一个页面计算单元。

通过moduleCode动态路由对应的module group,执行相应的计算单元。

每个module 执行单至少都包含读、新增、编辑和删除接口。

页面上的每一个module就自动绑定了后端的业务接口,实现了前后端一体化搭建。

/*** Created by hzliuxuan on 2022/5/27.* @author hzliuxuan* 模块接口*/
public interface McubeModuleExecutor<T,V> {/*** 填充数据,页面渲染,一般是read接口* @param value* @return*/T populate(V value);/*** 编辑模块* @param value* @return*/void edit(V value);/*** 写接口* @param value* @return*/void add(V value);/*** 删除接口* @param value* @return*/void delete(V value);
}

McubeModuleGroup

module执行组注解定义

@Inherited
@Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface McubeModuleGroup {/*** moduleGroup code (必填,唯一标识)*/@NotNullString code();/*** 对应module code值*/@NotNullString[] moduleCodes();/*** moduleGroup name*/String name();@NotNullModuleGroupType type();
}
```

field定义

一个module对应多个field。

如果要支持动态扩展,module需要对应一个实体模型。

module只代表一个VO层的部分显示片段,要想达到字段可以动态扩展需要定义一层实体模型的映射关系,这样才能找到统一的feature对象去解析,完成DO、DTO、VO的相互自动转换。 当module需要动态扩展的时候,从实体模型中去选择已经定义好的field。

因为我们的VO也是动态生成的,这样就不需要因为新增一个字段而进行模型变更或者代码发布。即实现0代码上线。

field数据结构定义

page 数据结构

public class McubePageBeanDTO {

/*** 页面编码*/
@CrmOperateLogBizCode
private String pageCode;
/*** 业务线*/
private String bizCode;/*** 配置类型*/
private TemplateTypeEnum templateType;/*** 配置模块*/
private List<McubeModuleBeanDTO> originalModules;
/*** 配置字段*/
private Map<String, List<McubeField>> originalFields;/*** 实例的模块*/
private List<McubeModuleBeanDTO> instanceModules;private List<String> instanceModulesList;
/*** 实例的字段*/
private Map<String, List<McubeField>> instanceFields;private String subBizCode;
/*** 元页面version*/
private Byte originVersion;
/*** 实例version*/
private Byte instanceVersion;
/*** module version*/
private Byte moduleVersion;/*** 属性集合*/
private List<Property> properties;///**
// * 显示的模块
// */
//private List<String> instanceModulesList;private Boolean isCache;@Data
public static class Property {/*** property*/private Boolean checkable;private Boolean isEdit;private Boolean selectable;private Boolean isLeaf;private Boolean isAdd;private Boolean isDelete;private String showType;private Integer level;private String extendedField;}

page渲染运行时序图


运行时类设计图

每一个模块背后都会绑定一个 moduleGroup executor ,业务开发只需通过对这个executor实现,即可快速完成开发上线,整个过程无需前端参与。简单的字段添加也无需发布上线,我们会通过动态扩展映射背后的DO扩展。

总结

好了,整个魔方的产品设计到这里基本描述完。

整个产品我理解更多的是贴近业务而产生的一种前后端一体化,低成本快速构建方案。如果要做大,做全,可以参考salesforce元数据驱动模型。

模型关系图:

可参考:https://www.infoq.cn/article/rwstpgujoxxuw9tlm88t

salesforce官方架构文档:https://www.developerforce.com/media/ForcedotcomBookLibrary/Force.com_Multitenancy_WP_101508.pdf

salesforce 已经超脱了模型驱动,下探到元数据模型驱动架构 Metadata-driven Architectures。

优点很明显,就是真的强大,业务都不需要建任何表,想怎么扩展模型就怎么扩展,此架构一般适用于与SAAS产品。

缺点也很明显,完全失去业务语义,开发成本和维护成本高,需要一套强大的sql管理和解析、实时的ETL数据架构、检索能力,后期成本也非常大。

故不适用我这个业务团队采纳的方案,但这套设计方案也给我打开了些思路。

魔方这套方案搭建工时约为50人日,两个后端加一个前端,解决了十几个业务线多租户的个性化接入,一定程度实现了模型驱动,千人千面的能力,我认为是典型小投入大产出吧,希望对正遇到同样问题的同学有一定的帮助和启发,限于个人能力,最终要搞大的需要有专业的团队支撑,也欢迎指正和探讨。

作者 | 刘玄(玄哥)

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis 数据类型 list 以及使用场景

数据存储需求&#xff1a;存储多个数据&#xff0c;并对数据进入存储空间的顺序进行区分 需要的存储结构&#xff1a;一个存储空间保存多个数据&#xff0c;且通过数据可以体现进入顺序 list类型&#xff1a;保存多个数据&#xff0c;底层使用双向链表存储结构实现 list 类型数…

TairSearch:加速多列索引查询

互联网及传统行业应用服务的关键数据一般存储在MySQL这类的关系型数据库中。如需缓解数据库访问压力&#xff0c;可引入Redis等缓存系统承担热数据的查询&#xff0c;以此提升查询效能。然而业务场景如果是在数据库上做随意多列组合索引查询或者like模糊匹配查询&#xff0c;使…

如何在 Anolis 8上部署 Nydus 镜像加速方案?

在上一篇文章中详细介绍Anolis OS 是首个原生支持镜像加速 Linux 内核&#xff0c;Nydus 镜像加速服务重新优化了现有的 OCIv1 容器镜像格式&#xff0c;重新定义镜像的文件系统&#xff0c;数据与元数据分离&#xff0c;实现按需加载&#xff0c;本文作为使用 Nydus 的教程将详…

机器学习访存密集计算编译优化框架AStitch,大幅提升任务执行效率

近日&#xff0c;关于机器学习访存密集计算编译优化框架的论文《AStitch: Enabling A New Multi-Dimensional Optimization Space for Memory-Intensive ML Training and Inference on Modern SIMT Architectures》被系统领域顶会ASPLOS 2022接收。 AStitch通过编译优化的手段来…

微前端架构的几种技术选型

背景 随着SPA大规模的应用&#xff0c;紧接着就带来一个新问题&#xff1a;一个规模化应用需要拆分。 一方面功能快速增加导致打包时间成比例上升&#xff0c;而紧急发布时要求是越短越好&#xff0c;这是矛盾的。另一方面当一个代码库集成了所有功能时&#xff0c;日常协作绝…

真正的 HTAP 对用户和开发者意味着什么?

数据库的全称是 DBMS&#xff08;Database Management System&#xff09;&#xff0c;早期是不区分 OLTP 与 OLAP 的&#xff0c;E.F.Codd 在 1970 年就提出了关系模型&#xff0c;Jim Gray 在 1976 年提出了事务模型。随着数据库的应用场景越来越丰富&#xff0c;单一数据库的…

const常见用法

const用法主要是防止定义的对象再次被修改,定义对象变量时要初始化变量 下面我就介绍一下几种常见的用法 1.用于定义常量变量,这样这个变量在后面就不可以再被修改 const int Val 10; //Val 20; //错误,不可被修改 2. 保护传参时参数不被修改,如果使用引用传递参数或按地址传…

微服务治理热门技术揭秘:无损上线

为什么有了无损下线&#xff0c;还需要无损上线&#xff1f;无损上线可以解决哪些问题&#xff1f; 本篇文章将一一回答这些问题。 无损上线功能不得不说是一个客户打磨出来的功能我们将从一次发布问题的排查与解决的过程说起。 背景 阿里云内部某应用中心服务在发布过程中出…

深度强化学习技术概述

深度强化学习介绍 强化学习主要用来学习一种最大化智能体与环境交互获得的长期奖惩值的策略&#xff0c;其常用来处理状态空间和动作空间小的任务&#xff0c;在如今大数据和深度学习快速发展的时代下&#xff0c;针对传统强化学习无法解决高维数据输入的问题&#xff0c;2013…

大屏小程序探索实践 | Cube 技术解读

所谓大屏小程序&#xff0c;是以 Cube 小程序技术栈 为载体&#xff0c;运行在智能电视或智能机顶盒等设备上的一种小程序形态。这些设备的主要特点是&#xff1a; 以 Android 系统为主&#xff0c;系统版本普遍较低&#xff0c;有些设备依然停留在 Android 4.2&#xff0c;An…

阿里云解决方案架构师张平:云原生数字化安全生产的体系建设

关于今天的分享主题——“安全生产”&#xff0c;内容主要分为三大部分&#xff1a; 第一部分是安全生产的背景&#xff0c;以及我们对于安全生产这个领域的理解&#xff1b;第二部分主要介绍阿里巴巴集团的安全生产工作到底是怎么开展的&#xff0c;借此给各位有作为参考和借…

从斜边之长为L的一切直角三角形中,求有最大周长的直角三角形.(多元函数的极值及其求法)

三条直线围成的直角三角形三个顶点A(16,0),B(0,8),C(0,0),设点(x,y)到AB,BC,AC的距离分别是d1,d2,d3,有: |AB|*d1|BC|*d2|AC|*d32S(ABC) 而(|AB|*d1|BC|*d2AC*d3)^24S^(ABC)/(|AB|^2|BC|^2|AC|^2)128/5 等号成立当且仅当|AB|/d1|BC|/d2|AC|/d3 就是40/|x2y-16|8/|x|16/|y| …

全链路灰度新功能:MSE上线配置标签推送

为什么需要配置标签推送 从全链路灰度谈起 在微服务场景中&#xff0c;应用的灰度发布迎来了新的挑战。不同于单体架构中将应用整体打包即可发布测试版本&#xff0c;微服务应用往往由多个服务组合而成。这些服务通常由不同的团队负责&#xff0c;独立进行开发。一个新功能通…

动态尺寸模型优化实践之 Shape Constraint IR Part I

在本系列分享中我们将介绍BladeDISC在动态shape语义下做性能优化的一些实践和思考。本次分享的是我们最近开展的有关shape constraint IR的工作&#xff0c;鉴于篇幅较长&#xff0c;为了提升阅读体验&#xff0c;我们将分享拆分为两个部分&#xff1a; Part I 中我们将介绍问…

云原生事件驱动引擎(RocketMQ-EventBridge)应用场景与技术解析

在刚刚过去的 RocketMQ Summit 2022 全球开发者峰会上&#xff0c;我们对外正式开源了我们的新产品 RocketMQ-Eventbridge 事件驱动引擎。 RocketMQ 给人最大的印象一直是一个消息引擎。那什么是事件驱动引擎&#xff1f;为什么我们这次要推出事件驱动引擎这个产品&#xff1f…

动态尺寸模型优化实践之 Shape Constraint IR Part II

在本系列分享中我们将介绍BladeDISC在动态shape语义下做性能优化的一些实践和思考。本次分享的是我们最近开展的有关shape constraint IR的工作&#xff0c;鉴于篇幅较长&#xff0c;为了提升阅读体验&#xff0c;我们将分享拆分为两个部分&#xff1a; Part I 中我们将介绍问…

PolarDB 助力易仓打造跨境行业生态链协同的产业链 SaaS

2022年7月&#xff0c;易仓ECCANG WMS东南亚版正式上线&#xff01;专为东南亚海外仓业务打造&#xff0c;帮助东南亚海外仓企业排忧解难&#xff0c;实现订单、仓库、人员、财务高效管理。易仓科技是头部的跨境行业SaaS服务商&#xff0c;其生态涵盖了300工厂、100000卖家、17…

iLogtail 社区版使用入门 - 采集 MySQL Binlog

iLogtail是阿里云日志服务&#xff08;SLS&#xff09;团队自研的可观测数据采集Agent&#xff0c;拥有的轻量级、高性能、自动化配置等诸多生产级别特性&#xff0c;可以署于物理机、虚拟机、Kubernetes等多种环境中来采集遥测数据。iLogtail在阿里云上服务了数万家客户主机和…

融合数据库生态:利用 EventBridge 构建 CDC 应用

引言 CDC&#xff08;Change Data Capture&#xff09;指的是监听上游数据变更&#xff0c;并将变更信息同步到下游业务以供进一步处理的一种应用场景。近年来事件驱动架构&#xff08;EDA&#xff09;热度逐步上升&#xff0c;日渐成为项目架构设计者的第一选择。EDA 天然契合…

Pandas+ SLS SQL:融合灵活性和高性能的数据透视

Pandas是什么 Pandas是一个十分强大的python数据分析工具&#xff0c;也是各种数据建模的标准工具。Pandas擅长处理数字型数据和时间序列数据。Pandas的第一大优势在于&#xff0c;封装了一些复杂的代码实现过程&#xff0c;只需要调用接口就行了&#xff0c;避免了编写大量的…