低代码开发在企业软件开发中的应用技巧3:低代码开发与快速开发平台的关系

无聊的时候,常去逛oschina.net 上逛,oschina.net 有个码云(https://gitee.com)的项目,类似于github.com ,我观察 码云上的企业应用,有很多是快速开发平台性质的项目,例如:JFinal、Gun、Jeesite 等等,比较奇葩的是 JFinal,企业后台在Spring Boot一统江湖的今天,JFinal 也能一枝独秀,处处能看到其身影,无论是B2C的网上商城,还是微信小程序,还是其他企业应用,都能看到基于JFinal的应用,没用过,也不知道其奥秘在哪里。 这些快速开发平台往往侧重于Admin后台管理,例如,用户注册、角色、权限、菜单,一般是基于Spring Boot/Spring MVC+Hibernate/Mybatis+Spring Security/Shiro等组合,对于企业应用,都有一些自己的特色。不过,快速开发平台与低代码开发平台还是完全不同的两回事。

一、面向受众不同,即客户不同。快速开发平台的受众(客户)是程序员,程序员基于这些平台做二次开发,比如:用户权限等都是现成的,所以往往能有效提高项目的交付速度,能给程序员节省不少时间放在企业逻辑的梳理和开发上,但是核心还是源代码的开发。而低代码开发平台的受众是实施人员(不是程序员), 低代码开发平台的目标是任何需求都实施出来(理论上),而不是开发出来,国内比较典型的例如泛微,通过自己的实施平台,到处做20-30万项目,一个实施人员做一个月,今年拿到投资人钱的还有搭搭云等等,国内做类似平台的还有好几家,国外例如微软也投入到了这个市场上。对于Salesforce和SAP,其产品核心交付人员也是实施人员而不是程序员,当然, Salesforce和SAP实施人员的工资很高,甚至比程序员还高,达不到降低费用的目的,这是另外一个话题。

二、理念不同,快速开发平台的核心还是开发,总比不过提供了很多快速开发工具,适应客户需求的多变,是源代码级的交付。不过代码写的越多,即使是自动生成出来的,理论上Bug也不少。而实施平台交付的是产品和基于这个产品上搭建的模型,新开发的代码量是很少的,往往是模型驱动。

三、低代码平台的核心目标是快速交付,降低客户费用,使得客户能够快速看到原型,而快速开发平台的目标理论上也是差不多的这个目标,但是由于底层价值观完全不一样,所以实现方式上也完全不同, 快速开发平台由于还是程序员+开发模式,交付风险并不能有效降低。程序员对业务的理解不可能有实施人员理解的那么深入,虽然快速做出来了,但是很可能不是用户想要的。

低代码开发在企业软件开发中的应用技巧2:忘记O/R Mapping

还是在那个大厂做项目的过程当中,甲方架构师力推Hibernate/JPA,极力反对MyBatis,在这里,我并不想比较JPA与MyBatis的孰优孰劣,这种低层次的比较,就跟比较Java、.Net、PHP、Python、React、VUE等语言孰优孰劣一样,离开使用上下文,说哪个语言是最牛B的语言,只能说自己too native,too young。

就低代码开发而言,我真的不喜欢JPA,就拿最常用的Excel数据上传来讲,Excel表格数据上传,最常用的就是POI,但是POI有个问题,就是性能不佳,Excel数据超过30万,就会内存溢出,虽然也有很多别的技巧,例如采用异步调用等等,但总是有各种各样的问题。所以寻来寻去,就看到了阿里巴巴的开源项目,easyexcel,下载后试用了一下,性能确实就如其宣称的那样强劲,也宣称支持百万条记录以上,所以非常满意。但是将其应用到项目当中时,发现 easyexcel 有个很大的问题,就是其接口支持的形式是:

List data = EasyExcelFactory.read(is, new Sheet(1, 2, Xhd.class));

那个Xhd.class就是自定义的ValueObject或者Entity实体类,跟表结构一致或者自定义的值对象或者O/R Mapping对象,为啥我说低代码开发在企业软件开发中的应用要忘记O/R Mapping呢?企业应用开发,往往表结构很多,很复杂或者很随意,比较动态,虽然针对某个企业来讲,表结构设计好后,很少会在大规模改变,但是代码在从项目往产品转变,或者在做下一个客户的下一个项目时,表结构就千差万别,完全不一样了。如果这里绑定死了某个Entity的名字,那么下一个项目该段代码就得完全重新开发,那就不是低代码开发了,就是完全订制化了。

而使用MyBatis,我们完全可以动态注入SQL,而不是绑定死表结构或者绑定死O/R Mapping对象。虽然 O/R Mapping对象我们也可以采用生成器之类的工具虽然生成这些对象,但是我确实也不喜欢纯粹为了O/R Mapping而 生成一大堆无意义的Entity。 也许Hibernate/JPA也支持动态注入 SQL而我不知道,即使真的支持,这也与 Hibernate/JPA=O/R Mapping工具的初衷相违背,失去了初心,使用它就是其门绝技了,不值得提倡。

低代码开发在企业软件开发中的应用技巧:开篇

企业软件追求的是性价比,即在预定时间内保质按时完成,而不是代码质量高。这里的保质是业务使用上无Bug,性能满足用户平时工作要求。这里并不是暗示程序员可以随意写烂代码,不讲究架构,而是优先级让位于开篇讲的保质按时完成。

我曾在某大厂与TW同事一起参与某个项目的短暂开发,在这里,我并不想做戳穿TW的敏捷开发无用论的皇帝新衣的那个小男孩,事实上,短暂的与TW架构师一起合作开发,还是学到了敏捷开发的理念并深以为有用,不过,在实践中过分强调技术上的代码质量和代码技巧,个人并不认同。个人的理念是在企业软件尤其是业务相关的管理类软件,应该采用低代码开发技巧而不是强调敏捷开发或者高质量代码开发技巧。当然,如果团队成员素质普遍较高,资金、时间又比较充裕,严格要求代码质量,采用敏捷开发过程,对团队和交付都是有好处的。不过,企业软件开发的现状绝大部分都是时间紧迫,预算少,成员普遍工作经验少,或者没有在大公司和互联网公司工作过。这种情况下,再强调代码技巧和质量项目很可能就做不下去了。

当然,开发过程中代码质量如果真的比较低,对客户,对参与开发项目的企业、成员也是极大的损失和浪费时间、浪费金钱,也是不能接受的。所以,这里强调的是如何使用低代码开发(不是低质量)技巧在预算有限,成员素质参差不齐情况下,按时保质完成任务。

String boot @Scheduled和数据库连接池的 APPARENT DEADLOCK各种异常

一般来说,采用Spring Boot+MyBatis,无论是使用c3p0这样的老牌数据库连接池,还是使用Druid这样的阿里出品的数据库连接池,一般都不会有问题。我用Spring Boot做了一个基于JWT统一登录的微服务,采用的就是c3p0,几个月不登陆,也没出现数据库 APPARENT DEADLOCK!!! Complete Status:,No operations allowed after connection closed等等这样的异常,但是使用了@Scheduled后,数据库连接池频繁出现上述两种异常,天天死给我看,搞得我非常不爽,我想肯定是@Scheduled的问题,但是究竟出了什么问题?网上搜索了一阵资料后,大致明白了其中道理。

1)无论是APPARENT DEADLOCK!!!还是No operations allowed after connection closed都是数据库连接池超时或者使用了已经关闭的连接池造成的。那么为什么会出现这样的问题呢?

A)数据库连接池也是客户端/服务器端两端通信的问题,要么客户端连接服务器时,服务器无响应(例如mysql8小时的问题,mysql数据库把来自客户端的请求关闭了),要么

B) 是客户端自身本身的Connection 已经关闭了,但是新的Object Instance 还是使用了该Connection,则就出错了,这时可通过配置

<property name=”maxPoolSize” value=”300″ />
<property name=”minPoolSize” value=”10″ />
<property name=”maxIdleTime” value=”1800″ />
<property name=”maxStatements” value=”0″ />
<property name=”maxStatementsPerConnection” value=”100″/>
<property name=”initialPoolSize” value=”10″ />
<property name=”idleConnectionTestPeriod” value=”1800″ />
<property name=”testConnectionOnCheckin” value=”true” />
<property name=”automaticTestTable” value=”Test” />
<property name=”testConnectionOnCheckout” value=”false” />
<property name=”breakAfterAcquireFailure” value=”false” />

调整这些参数解决,Connection 即该关闭的及时关闭,不要长时间打开不关

C)Spring Boot @Scheduled的坑,@Scheduled长期打开的是同一个Class的同一个Object Instance,即Java的 JVM并没有把该Object销毁并进行内存回收,那么同一个Object Instance—–>Mybatis –>JDBC–>Database获取数据库连接池的路径,C3P0还是返回原来的那老一个,但是那老一个早已经被销毁,所以就会出现 APPARENT DEADLOCK!!!的问题。

解决的办法要么通过Http Client请求该定时器,例如采用Quartz或者Elstic JOB等外部中间件,Http调用每次都会生成一个新的线程,新的线程会生成新的Object,这时新的Object 从C3P0数据库连接池获取Connection时,就会返回新的Connection

要么定制一下Spring @Scheduled源代码(待续)