当前位置: 首页 > 科技观察

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

时间:2023-03-13 17:36:20 科技观察

作者|很多,每个业务线对同一个页面都有个性化的布局和不同的字段需求,而我的团队只有3个人,资源有限怎么支持?一开始,我们分别为每个业务线定制页面和业务逻辑,我们可以处理1或2个业务线。目前,我们已经发展了十几个业务线,每个业务线都有子业务线。如果个性化开发多了,工作量会更大,系统维护的压力也会很大。于是构思并诞生了——卖魔方低码产品,与其说是低码产品,不如说是一个前后端一体化的解决方案,解决千人业务的个性化建设。本文讨论我如何在成本降低的情况下,以低成本构建产品能力,以支持多业务线和多租户。我会先用小的实际效果来演示,然后过度分享我的设计思路,以供后续升级。什么是LCDP低代码开发平台(Low-CodeDevelopmentPlatform)是一个无需编码(0代码)或少量代码即可快速生成应用程序的开发平台。一种通过可视化进行应用程序开发的方法(请参阅可视化编程语言),它使各种经验水平的开发人员能够使用拖放组件和模型驱动逻辑,通过图形用户界面创建Web和移动应用程序。低代码开发平台(LCDP)的正式名称直到2014年6月才正式确定,但整个低代码开发领域可以追溯到更早的第四代编程语言和快速应用开发工具。魔方核心竞争力产品能力上图展示了魔方1.0MVP版本的基本运行原理,以及上线后的降本增效数据。业务开发从60人天缩短到20人天,每年节省180人天。以上版本基本满足80%以上业务自闭环开发的个性化需求。还有一些小问题。基于该版本,我们不断升级,提升产品体验、能力提升和业务覆盖。未来,我们可以在5分钟内推出新页面。无需为新字段更改模型和发布Java代码。复杂页面的前端也可以做到0代码。根据我们的业务需求,魔方的销售必须具备以下核心能力:千行千面(ThousandsofThousandsofPeopleandThousandsofFaces),包括同一个页面不同的布局,不同的字段,不同的数据模块样式(千千行千面)根据不同身份一键创建不同的业务技术逻辑和业务编排页面,无新业务编队、新模块时无需开发接入,0代码上线,运营商自行配置页面。前端组件被重用。如果没有新的前端组件,前端不需要参与开发,后端只需要编写模块对应的业务接口即可。实现模块复用,模块数据渲染,数据写入,查询条件,浮层,半推页面,页面操作添加字段扩展0代码,模型字段可自定义,动态扩展,可从本地数据库和远程HSF定义接口数据环境可隔离,测试、预发布、生产平台与业务代码分离。业务上线时,只需要关注业务逻辑本身的代码即可。DODTO可定义,动态映射数据枚举动态定义,动态绑定魔方设计产品界面先展示一个示例配置界面,具有个性感通过以上配置,可以个性化页面输出的布局和字段,动态输出个性化页面用户操作:操作可以根据自己的业务标识定义唯一的页面实例,自由选择需要展示和编辑的页面版块和字段产品:配置初始化页面、模块、全字段池技术:定义元素数据,模块,编写模块组逻辑执行单元代码产品模块核心逻辑一般是低代码平台,主要分为两部分,前端页面的渲染和后端的绑定服务接口(服务编排等)。大的逻辑也是类似的,因为我的主要是一个具有行业特点的低代码产品,所以根据行业的特殊性来构建。前端渲染因为我是负责后端的,所以就不过多描述前端了。大体逻辑如下。两张图大致意思就是前端页面渲染就像做饭一样。用户可以根据自己的需要选择食谱和配料。还有烹调方法,不用在意烹调过程,也不用自己动手做。它被称为厨师烹饪。厨师会根据您提供的菜谱和食材进行烹调,最后将食物端上给您。同理,前端渲染引擎会根据数据协议、组件库、渲染方式动态渲染页面。如果有业务数据,就会动态绑定。后端绑定我们这里有一个特殊性。通过后端给页面一个schema,前端根据这个schema渲染页面。后端识别用户身份,通过接口输出个性化的schema给前端,前端通过schema配置动态渲染。这样我们就可以实现我们所说的同一个功能页面,不同的业务标识展示不同的布局和字段。同时还会有业务数据接口,用于绑定前端页面填写业务数据或提交表单数据。每个组件绑定业务数据接口后,就不是一个简单的前端组件,而是一个具有行业业务属性的组件和页面,从而可以直接被该领域的业务系统复用,无需重写业务代码。这里有一个困难。每增加一个新的业务线(租户),就需要新的字段,而且字段差异比较大,约占80%。不修改模型怎么办?带着这个问题,大家可以先想想。一般的解决方案如下:1、通过设计垂直表来扩大领域。缺点是查询复杂度高。2.元数据驱动。缺点是成本高,结构较复杂。3、通过定义一个特殊的特征字段,存储类型为json,约定好,扩展字段根据不同行业存储为json。优点是重量轻。根据我们自己的定位和资源情况,我们最终选择了第三种方案。这里有一个担心是json数据好找吗?表现如何?在mysql5.7上,在相同数据量的情况下,虚拟索引的查询效率与普通索引基本相同。在数据量大的情况下,不建议使用聚合函数计算json数据,但是如果jsonkey有虚拟列,可以使用虚拟索引列进行聚合,效率和普通一样columns.Modeldesign可以看到整个模型,从左到右:DO定义、DTO定义、模块定义(相当于VO定义)、原始页面定义(originalPage)、实例化页面配置(instancePage)这个过程基本描述了一个页面是如何定义的,以及动态模型(model-driven)的扩展,从模型的布局定义到页面组件。行业个性化配置通过将OriginalPage实例化为InstancePage获得。这样就可以动态生成成千上万的行和面。从OriginalPage到InstancePage,这里借鉴了java的思想,类似于Class和实例对象。渲染页面逻辑在渲染页面时,只需要两个接口:1.实例页面数据结构;2.业务数据。前端根据两个界面分两步渲染:1.初始化页面布局;2.填写业务数据。这个借鉴了Spring容器启动时的设计思路,类似于实例化、初始化、属性数据填充。模板定义是页面类型的定义。页面详情页半开页表单提交页页面定义定义了一个前端页面,分为两个阶段:origin;实例。origin是定义的原始页面,可以理解为一个javaClass类,可以构造多个实例页面。实例页面就是最终渲染的运行状态。页面结构:一个页面由N个模块组成,一个模块由N个字段组成。如下图所示:示例页面由以下维度定义1。用户传入参数page_codecustom_dimension2。系统获取参数biz_codesub_biz_codeenviromentmodule来定义页面的组成单元。一个页面由多个模块组成。表示页面展示区域单元,由多个前端组件组成,是页面容器的布局单元。module_code是全局唯一的,实例化的模块可以重用、改写、多态。modules的数据结构是一个B+树,只有叶子节点才有具体的实体数据叶子节点实际上包含了具体的字段和属性。配置module_type定义了模块类型的定义:主列表查询模块main_list_moduleexport模块export_modulepopupupfloat_page_page_modulesearch_are_are_are_moduleclast_module子列表ApplicationContextAware{privatestaticvolatileApplicationContextalc;@ResourceprivateModuleBeanFactorymoduleBeanFactory;@ResourceprivateModuleGroupBeanFactorymoduleGroupBeanFactory;@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{alc=applicationContext;}@PostConstructpublicvoidinit(){setModuleBeanMap();setModuleGroupBeanMap();(McubeModuleExecutor.class);如果(beanMap!=null){beanMap.values().stream().forEach(m->{McubeModulemodule=AnnotationUtils.findAnnotation(m.getClass(),McubeModule.class);if(module!=null){Stringcode=module.code();Stringname=module.name();if(code!=null){moduleBeanFactory.getMcubeBeanMap().put(code,m);}}});}}privatevoidsetModuleGroupBeanMap(){MapbeanMap=alc.getBeansOfType(McubeModuleExecutor.class);如果(beanMap!=null){beanMap.values().stream().forEach(m->{字符串代码=module.code();字符串名称=module.name();moduleGroupBeanFactory.getMcubeBeanMap().put(code,m);}});}}}执行单元(moduleGroupexecutor)为了保证页面的数据填充效率,它不是绑定服务接口的模块,而是一个执行单元对应一个或多个模块,它负责数据的渲染和多个模块的数据写入。moduleGroup执行器是页面计算单元,通过moduleCode动态路由对应的模块组,执行对应的计算单元。每个模块执行列表至少包括读取、添加、编辑和删除。接口页面各模块自动绑定后台业务接口,实现前后端一体化建设。/***hzliuxuan于2022/5/27创建。*@authorhzliuxuan*模块接口*/publicinterfaceMcubeModuleExecutor{/***填充数据,页面渲染,一般读取接口*@paramvalue*@return*/Tpopulate(Vvalue);/***编辑模块*@paramvalue*@return*/voidedit(Vvalue);/***写接口*@paramvalue*@return*/voidadd(Vvalue);/***删除接口*@paramvalue*@return*/voiddelete(Vvalue);}McubeModuleGroup模块执行组注解定义@Inherited@Component@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public@interfaceMcubeModuleGroup{/***moduleGroup代码(必填,唯一标识)*/@NotNullStringcode();/***对应的模块代码值*/@NotNullString[]moduleCodes();/***模块组名*/Stringname();@NotNullModuleGroupTypetype();}field定义了多个字段对应的模块。如果要支持动态扩展,模块需要对应一个实体模型。一个模块只代表一个VO层的部分显示段。为了实现字段的动态扩展,需要定义一层实体模型的映射关系,从而找到统一的特征对象进行解析,完成DO、DTO、VO之间的自动转换。当模块需要动态扩展时,从实体模型中选择定义的字段。因为我们的VO也是动态生成的,所以不需要因为增加一个新的字段而改变模型或者发布代码。即0代码上线。字段数据结构定义[{"key":"equityInvestment","value":null,"label":null,"name":"股权投资记录","text":null,"width":null,"lock":null,"copyEnable":null,"copy":null,"sortable":null,"tooltip":null,"wordBreak":null,"fieldMapper":null,/**数据类型值输入,选择,date,address(地址),switch(开关),staffSelector(花名选择),textArea,upload(上传)**/"dataType":"input",privateStringdataType;“格式”:null,“dataSource”:null,“dataUrl”:null,“required”:null,“unit”:null,“readOnly”:false,“isHidden”:false,“multiple”:false,“功能":null,"showTime":null,"maxLength":null}]页面数据结构publicclassMcubePageBeanDTO{/***页面代码*/@CrmOperateLogBizCodeprivateStringpageCode;/***业务线*/privateStringbizCode;/***配置类型*/privateTemplateTypeEnumtemplateType;/***配置模块*/privateListoriginalModules;/***配置字段*/privateMap>originalFields;/***实例模块*/privateListinstanceModules;私有列表instanceModulesList;/***实例字段*/privateMap>instanceFields;私有字符串subBizCode;/***元页面版本*/privateByteoriginVersion;/***实例版本*/privateByteinstanceVersion;/***模块版本*/privateBytemoduleVersion;/***属性集合*/privateListproperties;///**//*显示的模块//*///privateListinstanceModulesList;私人布尔是缓存;@DatapublicstaticclassProperty{/***property*/privateBooleancheckable;私有布尔值isEdit;私有布尔可选;私有布尔值是叶;私有布尔值是添加;私有布尔值isDelete;私有字符串显示类型;私人整数级别;私有字符串扩展字段;开发只需要实现这个执行器即可快速完成开发上线。整个过程不需要前端参与简单的字段添加和发布。我们会动态扩展映射后面的DO扩展。总结好了,整个魔方的产品设计就基本介绍到这里了。我理解整个产品更多的是贴近业务的前后端一体化,低成本快速搭建的解决方案。如果想做的更大更全面,可以参考salesforce元数据驱动模型。模型关系图:参考:https://www.infoq.cn/article/rwstpgujoxxuw9tlm88t?salesforce官方架构文档:https://www.developerforce.com/media/ForcedotcomBookLibrary/Force.com_Multitenancy_WP_101508.pdf?salesforce它已经超越了元数据驱动架构的模型驱动方法。优点很明显,就是真的很强大,业务不需要建什么表,模型想扩容就扩容。这种架构普遍适用于SAAS产品。缺点也很明显,完全失去业务语义,开发和维护成本高,一套强大的SQL管理和分析,实时ETL数据结构,检索能力,后期制作成本高。所以我的业务团队采用的方案是不适用的,但是这个设计方案也为我打开了一些思路。魔方的建设时间约为50人日。两后台一前端解决了十几个业务线和多租户的个性化接入。我觉得是典型的小投入大产出。希望对面临同样问题的同学有所帮助和启发。限于个人能力。最后还是需要专业的团队支持才能做大。欢迎指正和讨论。