低代码这个概念不知道是什么时候出现的,不过在我印象中类似的说法似乎已经有很久的历史。

对我来说真正开始思考是在大概三年前开发一款相对复杂的流程审批系统开始的,当时需要设计在一个较大的组织架构中多角色与用户的流程审批功能。在最终选择使用activiti6做底层实现后开始了符合实际业务场景的改造,这主要包括流程设计器对接外部的角色与用户系统,流程设计器的节点对接外部接口调用(比如通知),流程跳转节点与内置表单组合后支持表达式的判断。而后端代码增加了动态的节点权限赋值,任意节点取消,运行中的流程图修改,主业务异常后的流程数据补偿等。

等上线后发现实际业务和流程有非常强的耦合。业务上线是先各方面定下一个业务流程,有些流程可能非常长非常多的审批人员,或是复杂的节点逻辑。随后前后端开发人员实现,并按照业务绘制流程图,发布流程图,业务上线后在配置中选择与最新的流程图编码(编码一致,版本可升级)绑定。运行一段时间后我发现业务流程虽然支持绘制流程图进行版本升级,进而变更流程执行逻辑,但是实际这样用的场景非常少,因为流程的变更需要具体业务代码的支持。而很多业务模块在设计中有非常强的上下文依赖关系,无论是框架还是上下游接口都无法支持调整顺序。导致最终这个流程编辑功能如果要变更必须伴随着业务系统的变更,也只有对外部系统的调用是达到预期的。

那时候开始我就在考虑,是否可以从业务节点的“原子化”封装与上下游接口的统一来达到业务节点的执行顺序变更。非常直接的思考就是,如果一个业务能封装与拆分的足够好,理论上是可以任意组装的(当然跑起来能不能符合业务流程是另外的问题)。

这里就简单聊一下近期接触的“低代码”平台原理与实现方式,并说一下我个人最近针对低代码的探索。

TL;DR点击列表跳转

  1. 低代码
  2. 脚手架与代码生成器
  3. Saas自定义
  4. 其他
  5. 我的实现
  6. 下一步

1. 低代码

在之后接触到的或多或少和低代码有些关系的项目也只是从某一方面来考虑,从更大的范围来说,2B针对具体行业或特定业务来实现“低代码”是相对容易的事情,而更宽泛的一般意义上的,不管是不是要编码的“低代码”相对来说困难很多。而目前接触的大多数可用的“低代码”平台很多的都是基于特定开发脚手架的封装,目的的快速上手,属于脚手架项目的更高层的封装。所以我也在想按照这种趋势发展下这些“快速开发平台”,将来是否可以发展成为真正的低代码甚至是无代码平台?

目前我接触的比较实用基本的“低代码”实现有以下几种。

2. 脚手架与代码生成器

我认为这种方式是目前最为可行方法。

这种方法是在某一个脚手架与基础设施代码库的前提下生产大部分固定代码,这些代码作为整个的基础框架。开发人员在这个基础上做业务开发。所以任何一个项目与功能对于开发人员都相当于一切都准备好了,只需要使用它们。

这类系统从无到有是这么样运作的:

  1. 核心框架:框架的基础核心如 xxx-framework-core,里面是不引用任何外部依赖的干净代码
  2. 业务支持:在core基础上增加MVC与数据库类操作的支持。到了这一步再加上一个大框架如spring,开发一个传统单体应用就足够了。
  3. 基础设施:对常用组件与外部设施的封装,如自己的MVC规范,对MQ,缓存的封装。
  4. 分布式基础:微服务,分布式系统的基础设施,如微服务间调用规范,统一接口管理等。
  5. 微服务管理:基于微服务的封装,如自己的SSO与网关基础上做的全局认证管理,微服务治理等。

上述五个部分全部完成后这些是代码生成器的基础,代码生成器在此基础上根据数据库设计与配置生成MVC模板代码。

  1. 生成MVC代码:针对数据库的几乎所有操作与接口全部完成。在不考虑前端代码和业务操作时数据操作与rest接口可以直接使用。
  2. 打包:代码生成器代码 + 业务支持代码 + 核心架构代码作为依赖库通过maven等工具添加到项目中。
  3. 业务开发:开发人员摒弃掉“面向数据库编程”的思想以业务的角度做功能开发,在业务功能中调用上述基础设施。
  4. 融入框架:开发完的项目接入分布式与微服务设施中,接入网关,SSO,治理等功能。
  5. 完成:在这个基础上任何功能的开发相当于向一个已有系统中做升级,新的业务,新的微服务对整个系统来说也仅仅是一个新的功能模块。需要额外处理的可能就只剩下在全局服务治理或SSO中做一些配置升级

在见过不少开源或者商用的以及公司内项目采用类似的开发模式后,我认为这种开发模式是灵活性与定制性的一种折中方案,并且在逻辑上和技术上都有很完善的理论和实操作为基础。同时我在经过一段时间的代码与工具的积累后,自己从零开始写了个实验性质的项目。发现在有开发人员的前提下整体的项目流程非常的清晰,可控性相当的高。

2.1 其他

其它很多类似部分开源与付费升级维护类的项目,在我看来模式也属于这类方式。都是在一个迭代的很久的基础框架上做功能升级,区别可能就在于后续的工具或者说工具链的完善程度,以及基础框架的规模上。

3. Saas自定义

这里实际上又分为两大类

3.1 功能选择

功能选择类一般是行业内系统,说的更直观一点2C类的产品离我们生活比较近的类似与定制微信小程序,各类小商城小商家系统。

这个类系统的特点是只做某个垂直行业,极端的说系统已经有了这个行业内的“所有”业务对应的功能。有些类似于云服务厂商的功能性收费,就是你想要什么功能自己选择然后以类似租户隔离的方式在整个大系统中给你分配资源,以保证业务与数据的隔离。

这类系统比如ERP类(几乎什么都有就看你怎么用与用那些)初看起来似乎和低代码没什么关系,也许是过于行业化过于规模化。当规模大到如ERP类系统我想这就违背低代码的初衷了,而变成系统定制与升级维护了。

但是话又说回来如果在SaaS上存在一个“包罗万象”的系统,那么向广大用户开放功能选择和定制,是不是也算是一种低代码?这么说很多系统上云服务,SaaS化从一次付费的买断私有部署转化为类似于服务的订阅制付费模式也算是一种低代码吗?

3.2 功能封装

这类项目在我看来是比较接近理想的低代码方式,对使用者来说所有业务功能似乎都隐藏在与用户交互的UI中。

非常有幸在疫情前参与了这样的项目,并在后续大概半年的时间对项目有了初步理解。在我看来这样的项目首先是经过长期迭代,基于UI设计器与表单驱动。为了达到尽可能的定制性,在设计阶段表单设计是完全暴露给终端客户的,而这个表单实际上即使数据库中的表。

这类系统非为以下几个部分:

  1. UI设计器:用于项目定制界面,设计器中有巨量的任然不断迭代与添加的控件。
  2. UI DSL层:前端设计器设计完成的内容通过一个转换程序变成与任何语言无关的UI描述脚本,以JSON方式存储。
  3. UI渲染层:实际上是一个每个终端的渲染容器,如web,移动端,甚至后端渲染。这里后端渲染可以理解为之前servlet/JSP的方式。为了适应前后端分离的结构,每个终端都实现了针对自己平台的渲染引擎,用于将通用的描述语言转换为平台语言。
  4. 后端支持:后端类似于我前面写的一个基础架构与设施层,不过区别在于我的思路是MVC + 数据层是通过生成器产生,而这种是完全通用的。

这类系统看起来似乎的完美的开发平台,基于UI驱动的所见即所得,但是这类有浓重的后端思维以及过于复杂的项目初始化过程,注定是无法推广的。不过后来我想在这个基础上再增加几层封装,做成前面提到过的“功能选择”类系统。做成类似于“项目模板”的方式快速初始化一个数据库设计与应用的表单模板,也许是个很不错的方式。这类系统对于新项目的开发流程如下。

  1. 新建项目:浓重的后端思维,根据设置可能要做数据库的初始化。
  2. 数据源设置:数据表设计,看起来是前端的表单设计。
  3. UI设计:首先增加一个页面,配置跳转,参数传递等信息。在页面上拖控件,控件和表单数据绑定。当然这里存在大量的组合式控件,这也是体现功能封装的地方。如联系人选择,组织机构信息选择,各类通用表格表单等。
  4. 提交项目:设计完成后提交审核人员处理。
  5. 发布项目:项目审核通过后获得一个URL可在浏览器访问。当然移动端要更复杂一些。

这里说一下这类系统的弊端:

  1. 通用语言跨平台:试图使用一个中间语言统一各个平台描述。越是通用且规范细节的方式最终都无法通用,特别是和用户有直接接触的终端语言。首先UI设计器本身是一个web端产物,使用前端语言开发如Vue或React,设计之初是为了满足web容器渲染。那么在考虑到封装大量组件,大量自定义属性,大量样式的时候,又考虑到项目工期与开发进度,这个前端设计器的最终产物都无法严格的翻译成与平台无关的中间语言。由于设计器与渲染容器使用相同技术栈,难免为了实际情况做各种功能上的妥协,导致中间语言对细节的关于可有可无。
  2. 终端渲染容器:终端渲染容器,web端采用相同技术栈,最终呈现效果还可以。后续我参与进去后负责本地渲染实现,在调研测试过发现可行的时候做一些高级功能渲染,如CSS和ES的本地转换时发现了问题。就像上面说的那样这个通用跨平台语言实际上并通用,且不说框架内使用的外部组件如element和ant等。就算是内部组件也隐藏了大量的默认与约定实现,这些完全体现不到“通用语言”上。且设计器默认运行在web浏览器这一环境中,大量的JS与CSS无法直接翻译成中间语言,并翻译成其他平台语言。
  3. UI设计器:UI设计器在实现一个基本表单或页面跳转后,要想做深层次定制那么非常困难。且不说有大量的自定义控件,自定义布局容器需要从API级别有所了解,还有即便是前端或后端开发人员亲自来设置也摸不着头脑的各类属性,以及需要写CSS和JS代码,有巨大的学习成本。
  4. 后端规模:前端的复杂性在直接体验过UI设计器就有所了解,而后端请求由于完全隐藏在UI组件中使用通用接口。数据存储也完全交给用户定制,很多基础设施管理实际上是云服务厂商的管理方式,这类后端系统管理和维护的复杂的可想而知。

这类系统理论上是“万能”系统,但是其复杂的操作以及需要开发思维甚至开发者参与的高门槛,注定其在2C类项目中没有市场。

4. 其他

还有一些其他的比如脚本编程只是看过几个框架性质的项目,没见过足够多的使用案例就不做多说了。另外还有一类“儿童编程”在我看来属于“业务流程编排”,这种属于项目中已有流程的排序与定制,有一定的借鉴意义。

这里还有一个问题就是前端的低代码实现,在前后端不分离的时代基于servlet/JSP以及一些模板引擎把UI的动态性隐藏起来。而在前后端分离的当下前后端的沟通又多了一层restfull接口后无疑又增加了封装层级与复杂度,特别是现在流行的基于webpack类工具全量下载到浏览器后,后端对前端的控制力又弱了不少。

虽然前端有按需加载,动态路由等一系列动态手段,但是目前UI设计器唯一有过可用的也只有移动端,甚至更小范围的小程序范畴。移动端的动态性无论是混合还是原生至少是有可用方案,当然越是定制性越强越是限制范围越大。

最后我觉得即便是从一个开发者角度来说,我从最开始对低代码或者说可视化编程从最开始的不写代码,慢慢转变为关注核心业务。在我看来通用性与定制性是无法调和的矛盾,为了达到通用性反而会使系统过于复杂,同时又无法针对特定问题做优化。能解决这个问题的方案我想也只有增加系统规模,来达到整体的通用性与局部的定制性。

5. 我的实现

在过去的一段时间我经过不少实际开发与调研,有了一些对微服务场景下的低代码开发的的理解。

当然作为一个开发者,我的理解必须要能够做到代码的可控性,即针对业务级功能都必须有直接修改代码的能力。也正是基于此那种通用MVC框架就被我抛弃了,而是选用MVC框架与基础规范前提下的代码生成器生成独一无二的定制化代码。然后基于系统功能设计出通用模块,并使用服务内业务级别的的服务编排功能做业务逻辑的动态定制。然后在微服务(分布式)级别做服务间接口层级的调用配置。

在此理论的引导下最近几个月在工作之余,我把之前写的各种小工具收藏的代码片段全部进行了整合形成了各个层级的基础框架,设计了MVC与持久层的统一规范并在9月处完成了第一版可用的基础框架层,当然这个过程中还写了命令行级别的代码生成器。首次测试是在9月初当时由于系统需要调用外部服务,外部服务是线上生产版本每次做核心代码修改都无法做全流程测试。有时候需要其他公司同事协助,有时候会根本就找不到对接人。于是我按照线上运行数据大致推测了一下对方系统的内部运行逻辑,准备写一个外部服务模拟程序。目标是只修改一下目标系统连接设置对接到我的测试系统,然后整个测试环境的项目可以模拟生成环境测流程。

由于只是为了测试核心逻辑,那么就不需要考虑各种极端环境,异常数据,特殊场景了。于是我简单设计了几张表,用代码生成器生成出基础数据操作接口。随后按照文档在下班后写了对应的20多个接口,在做数据整理后调用代码生成器生成的接口执行数据库操作。整个过程的速度快的超乎我的想象,第二天在测试环境测了一下,对于一般流程竟然一下通过了。在这之后更坚定了我对于低代码的理解,于是在这些基础上我开始思考服务内业务流程编排的实现。

5.1 服务内业务编排

在定制完activiti6后我真正的开始思考如何做业务级别的流程定制,由于没找到更贴切的称呼我就暂时称呼这种为“服务内业务编排”。

首选要做要做流程定时必须要抛弃以往“面向数据库编程”的思想,将针对数据的MVC代码当做框架类库来使用。要基于业务与功能点左开发,无论是DDD还是什么的都可以。之前的类似设计可能是在封装好某个独立功能后做代码级别的引用,现在需要根据业务级流程引擎做更高层次的封装,由流程引擎控制这些独立的模块代码的执行关系。

而对于业务节点的发现又分为静态发现与运行时发现,静态发现可以在代码生成器阶段扫描静态源码,识别到流程引擎的实现类或特殊接口注解等。运行时实现就比较直观,由于这些节点一般会当做Component注入到容器中,那么通过容器直接搜索会快很多。然后就想画流程图一样将扫描到的流程节点串联起来生成流程引擎可识别的配置文件,再做静态后动态的配置文件发布。

5.2 微服务业务编排

“服务内业务编排”理解为单个微服务内业务的流转,那么“微服务业务编排”就可以理解为微服务间的业务流转关系。其实在玩耍之后是可以在逻辑上打通的。

当前真实的微服务间的方法调用,无论是openFeign/restful还是dubbo/socket其目的是将RPC的方式封装达到使用者
不关注服务间通信的目的。因此调用方和被调用方都是当做方法来做设计的,即尽量做到唯一性。而我在咨询了一些朋友对于这方面的探索经验后,发现不少的设计思想是基于系统对外统一接口的API_METHOD方式,即对外只有一个接口内部设计二级协议即api_method来实现。由于这种方式存在传参的复杂性,即接收方在一个接口内靠考虑兼容所有消息格式。当然也有在获得二级协议后做内部路由尽心分类,但是实际开发中如果把控不好容易变成一个巨型对象传值,甚至更直接的使用Map传值。

对于这类实现方式由于缺少实际场景与开发支持,目前我还没有找到能说服自己的“最佳实践”。

5.3 我的实践

在国庆前几天我把刚参加工作时写的swing框架代码翻出来,换上最新的样式风格,删除了大量花里胡哨的主题定制,并在经过几乎每天一遍重构的工作后写出来这个单机版的低代码平台。

低代码平台

在这个平台(最终可能是web版),有如下功能:

  1. 框架设置:这就是上面说的基础框架设置,微服务基础设施设置
  2. 服务编排:微服务间调用设置(暂定)
  3. 项目:单个微服务
    1. 项目编排:就是前面说的“服务内业务编排”
    2. 建模:当前项目的数据库设计,界面navcat 的方式
    3. 模块:代码生成器,扫描给定数据库的所有表,并设置单个表的MVC生成规则。

就像上文说的那样,这个UI实际上是一个极端性产物,为了实现这个工具需要一系列层层依赖的项目,这整个项目暂时用xxx代替。

项目一:

xxx-cloud:

  1. xxx-cloud-framework:
    1. xxx-cloud-framework-common: 最底层工具类
    2. xxx-cloud-framework-core: 与业务相关的工具
    3. xxx-cloud-framework-mvc:
      1. xxx-cloud-framework-mvc-core: mvc核心定制,MVC各个层级的顶层接口
      2. xxx-cloud-framework-dao: 数据库核心定义
      3. xxx-cloud-framework-mvc-support: 连接框架层向外部提供整个MVC功能的封装
    4. xxx-cloud-framework-support:基于微服务的功能封装
  2. xxx-cloud-gateway
  3. xxx-cloud-sso:

项目二:

xxx-cloud-ui:ui核心项目

项目三:

xxx-low-code:代码生成器 + UI

在上述三个项目的基础上才有个这一个很初级,很简陋的页面。

6. 下一步

下一步的任务是

  1. 将代码生成器的功能全部在UI层面实现。
  2. 项目编排,也就是上文说的“服务内业务编排”。
  3. web与移动端基础框架,脚手架与编排

这其中最大的工作量实际是业务流程引擎的使用,先选择一个比较轻量化的纯流程工具,在此基础上设计一套API作为扫描依据。并在处理后转化为对应流程框架可识别的配置信息。这个过程中规范制定要难于技术实现,因为技术上已经有很多方案,但是真正满足自己的还要经历多次迭代与开发过程中比较才能判断。

关于前端与移动端就比较难办,因为前端特别是移动端与用户直接相关,触手可及。如果只考虑2B场景那么就是数据操作加一些部分控件优化,但是2C的方案我自己都说服不了自己。

而微服务的业务流编辑也仅仅有一个理论基础,需要上手实践一下才会有一个感性的认识。

最后我认为无代码除非针对某个应用场景突然火起来了,否则其市场非常有限。可预见的未来的很长一段时间低代码都是能解决实际问题的工具,如果不只是考虑给开发者使用的工具与平台,那么出现针对某个应用场景的低代码封装突然爆火我认为也是非常有可能的。