作者|很多,每个业务线对同一个页面都有个性化的布局和不同的字段需求,而我的团队只有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(){Map
