本文剖析了不同措辞下实现低代码平台后真个优缺陷,如果你想引进低代码平台做数字化系统,或者从零开拓一个低代码平台都可以参考本文。
入围选手
低代码平台后端要实现大量功能,以是不能选择太偏门的措辞,本文重点谈论的是如下几个:
至于 Python、PHP、Ruby 等措辞没入选紧张是由于和 Node 比没明显上风,性能还更差,目前也没见有哪个低代码平台利用,因此动态措辞只选了 Node。

低代码平台要实现哪些功能
如果只是实现大略的业务逻辑和 CRUD,所有这些措辞中只有 Rust 比较繁芜,Rust 下的 Web 框架比如 Axum 接口比较底层,文档很少,须要通过示例和源码来学习,而且中间件写起来也更为麻烦,比如一个 timeout 中间件就须要上百行代码,须要节制 Rust 异步的实现事理导致门槛高。
除了业务逻辑和 CRUD 之外更主要的是低代码平台中相对繁芜的功能:
前端 JSON DSL 处理数据查询,对接数据源或实现宽表功能JavaScript 引擎,支持自定义后端代码表达式引擎,实现大略的定制逻辑API 编排,实现对接外部 API 接口流程引擎,实现流转功能接下来我们剖析这些功能在不同措辞下的情形
实现低代码平台功能的措辞比拟前端 JSON DSL 处理
低代码前端都是基于 JSON 的自定义 DSL,因此低代码后端须要常常对 JSON 进行剖析或二次处理,比如爱速搭在输出 amis 的时会将个中的 api 转换为代理地址,这时须要遍历 JSON 找到这些地址。
在处理 JSON 上动态措辞 Node 有天然上风,由于 JSON 便是 JS 工具,还能直接复用前真个类型定义。
而其它措辞相比拟较麻烦,紧张有两种做法:
定义数据构造,自动转换为不同措辞下的数据构造,好处是后续都是强类型,但对付繁芜嵌套定义非常繁琐,还有部分框架在碰着不太规范数据时的容错性不好,可能会直接报错。利用 JSON 库的接口动态遍历和处理,好处是不须要定义类型,但操作起来没有类型保护,随意马虎运行时报错。早期我很喜好用第二种方法,由于 amis 语法非常多变,尤其是很多属性是多类型的,导致在 Java 中定义类型就只能用 Object,完备失落去了强类型的上风,其余便是有些 JSON 库支持延迟解析,以是如果只修正部分字段时这种做法性能最好。
而如果不是 amis 那么多变的措辞,我更推举第一种用法,在定义字段的时候避免多类型,解析 JSON 在不同措辞下的情形如下:
Rust,Serde 是事实上的标准,由于 Rust 的 Enum 支持嵌套以是还能办理前面提到的多类型问题,使得它乃至还能像做到 Node 那样不须要定义类型,只用 serde_json::Value 就能解析。Go,官方有供应 JSON 解析库,但 Go 语法逼迫哀求首字母大写的字段才能公开,而 JSON 中没人会把字段名首字母大写,以是在 Go 中就得写成类似 Name string json:"name"的形式,导致比其它措辞麻烦。Java,Jackson 是事实上的标准,但官方文档很少,全靠第三方文档学习,每次碰着问题我都靠搜索。Kotlin,由于有 data class 且自带 JSON 解析库,因此代码比较简洁。以是结论是:Node 最好,其次是 Rust,接下来是 Kotlin,然后是 Java,末了是 Go 写起来最麻烦。
数据查询专业低代码平台常日支持连接用户自己的数据库,这时就须要对应的数据库驱动,在这方面 JDBC 上风明显,所有数据库厂商都会供应 JDBC 驱动,基于 JDBC 可以轻松抹平数据库差异,比如获取表构造信息等不须要查阅各个数据库的 INFORMATION_SCHEMA 构造或分外的 SQL 语句。
整理了一下目前数据库驱动支持情形,个中国产数据库的选自墨天轮 Top 10 中非 MySQL 和 Postgres 兼容的数据库:
数据库
Rust
Go
Node
MySQL
只有社区驱动,一人开拓
只有社区驱动,两三人开拓
有官方驱动,只有一个开拓,不如社区的 mysql2
Postgres
只有社区驱动,上个版本是一年前
只有社区驱动,已经快一年没更新了
只有社区驱动,两个开拓
Oracle
只有社区驱动,一人开拓
只有社区驱动,一人开拓
有官方驱动,两人开拓
SQL Server
只有社区驱动,最近提交频率较低
有个社区转官方掩护的驱动,一个人开拓
只有社区驱动,社区相对生动
HANA
只有社区驱动,只有 32 个 Star
有官方驱动
有官方驱动
达梦
无
有官方驱动
有官方驱动
GBASE
无
无
无
可以看到大部分措辞都没有官方驱动,而社区驱动常日只有一个人开拓,因此在这方面 JDBC 有巨大护城河,接下来轻微好点便是 Node,有许多官方驱动,非官方的驱动我们用过也很稳定。
然而 Node 没有 JDBC 这一层抽象,导致很多驱动表现不一致,比如预编译语句 PreparedStatement,在 JDBC 统一利用 ? 来声明变量,但在 Node 的各种驱动中有五花八门的实现:
在 node-postgres 中是用 $1在 oracle 中是用 :1在 mssql 中是用 @除此之外更上层的根本举动步伐还有连接池管理等,在 Java 下有比较成熟的方案,而其它措辞都比较低级。
因此在数据查询这方面 Java/Kotlin 是最好选择,其次是 Node,接下来是 Go 有少数几个官方驱动,而 Rust 没有任何官方驱动,质量难以担保。
其余你可能会想很多数据库都供应了 C 措辞驱动,Rust 直接通过 FFI 用不就行了么?答案是没那么大略,由于 Rust 下目前盛行 Web 框架都是异步的,而 C 措辞驱动是同步的会导致线程卡住,以是 Rust 中比较盛行的 SQL 实行器 sqlx 乃至自己实现了 MySQL 和 Postgres 的连接协议,导致开拓本钱很高,以是他们还打算将 MSSQL 和 Oracle 等主要数据库的支持放在商业版本中。
JavaScript 引擎在低代码平台中为了让用户实现更灵巧的功能,常日须要支持自定义代码,这个代码常日是 JavaScript,因此低代码平台后端须要包含 JavaScript 引擎。
JavaScript 引擎目前能选的就只有四种方案:
原生措辞实现的 JavaScript 引擎,这里唯一成熟的只有 Java 中的实现。接入基于 C++ 实现的成熟引擎,优点是兼容性最好,缺陷是须要措辞良好支持 FFI。Deno/Node 里实现的沙箱机制,优点是性能最好,缺陷是有安全风险。进程沙盒,优点是除了 JavaScript 之外还能支持其它措辞,缺陷是性能差,强依赖系统内核,比如 NsJail 只能在 Linux 下利用,不同系统下得利用不同方案,开拓本钱高,后面要支持信创系统可能会坑。以是不同措辞下的情形如下:
Rust,Rust 的 FFI 虽然没有 Zig 那样直接,但也是这几个措辞中支持最好的Rusty V8,Deno 团队掩护的,生动度较高。javascriptcore,Tauri 团队掩护的,生动度一样平常。Boa,纯 Rust 实现的 JavaScript 阐明器,目前还不成熟Go,由于 Go 的 CGO 有不小性能损耗,导致这方面的库不多也不怎么生动v8go,两年没怎么更新了。otto,纯 Go 实现的 JavaScript 阐明器,不支持 ES6,正则利用 re2 导致和 JavaScript 规范不一致,用不了。Java,Java 生态下大家为了跨平台都不喜好用原生库,以是类似 J2V8 这种库很少有人用,但 Java 下有成熟的 JavaScript 引擎实现:Nashorn,JDK 8 中内置的 JavaScript 引擎,只支持 ES5 并在 JDK 15 中删除了。graaljs,GraalVM 中供应的引擎,也能运行在 JDK 11 中,支持不少最新的 JavaScript 语法,目前最推举利用这个。Rhino,Mozilla 开拓的 JavaScript 引擎,从 1999 年开始就有了,支持部分 ES6 语法,市值超过 1400 亿的低代码平台 ServiceNow 便是利用它,如果想支持 JDK8 又想有一些 ES6 语法,这个是唯一选择,但缺陷是性能较差。Node,前面提到了紧张问题是安全风险,比如早起我们利用过 vm2,但它后来碰着一个安全漏洞无法办理,现在推举用 isolated-vm,但也可能有一天这个也没法用了。整体来说在 JavaScript 引擎方面 Node 最有上风但有风险,其次是 Java,Rust 也能用,Go 基本没法用。
表达式引擎在低代码产品中有时须要一些大略的条件判断或打算,比如下面的场景:
在流程中止定金额大于多少且级别小于多少。进行公式打算,比如打算毛利率之类的,类似 Excel 中的公式。第一种情形可以通过可视化界面比如 amis 的条件组合来实现,但第二种用界面就不太得当了,比起可视化,数学公式写起来更直不雅观。
因此低代码产品的后端须要实现一个表达式引擎,这个引擎虽然用前面的 JavaScript 引擎也能部分实现,但为什么不直策应用 JavaScript 引擎?紧张有以下几方面缘故原由:
性能,初始化 JavaScript 引擎比较至少毫秒级别,而表达式引擎大略打算可以在微秒级别。安全性,内嵌 JavaScript 引擎须要做沙箱处理,有可能涌现逃逸等安全问题,相对来说自己实现的表达式引擎会安全得多。语法更随意马虎掌握,比如可以忽略大小写方便非研发用户利用。支持大数打算,自己实现的引擎可以默认都利用 BigNumber,可以避免 JavaScript 精度问题。实现表达式引擎的核心由两部分组成:语法解析、实现内置函数。
个中实现内置函数比较大略,以是紧张难点是语法解析,语法解析可以手动实现或利用工具自动天生代码。
手动实现虽然看起来繁芜,但如果理解了类似 Top Down Operator Precedence 的事理实在并不难,不过由于大部分人来说有工具还是更方便点,因此接下来紧张剖析不同措辞下利用第三库或工具实现表达式引擎的思路:
Rust现成的:零散找到几个但看起来都不成熟解析工具:可以用 lalrpop 或 pest,没实际用个不愿定成熟度Go现成的:Expr 和 Cel 等解析工具:可以利用 ANTLR 或 peg,但不愿定成熟度Java现成的:非常多,可以参考这里,值得一提的是 Spring 内置的 SpEL,代码质量高,还支持转成字节码来提升性能解析工具:ANTLR,非常成熟,官方示例中有大量实现可供参考Node现成的:没有很著名的,大概是由于 Node 这种动态措辞可以直接用 eval 实行,用不上解析工具:有很多,除了 ANTLR 这种天生代码的办法,还有 Chevrotain 这种动态解析,成熟度比较高整体来说如果你知道怎么手写解析,这几个措辞差异不大,但如果不知道怎么写解析,用现成的话 Java 最成熟,Go 其次,解析工具的话 Node 和 Rust 也都有,而 Node 中的相对成熟点,不过学 Rust 的人均大神,因此该当写个解析难度不大,可以参考这里。
逻辑编排逻辑编排可以用来实现大略的后端业务逻辑,使得完备不写代码就能完成业务逻辑开拓,因此是低代码平台中的主要功能。
实现逻辑编排须要实现两种类型的节点:
掌握类节点,比如循环、分支条件、事务等,类似代码中的掌握构造实行节点,比如实行数据查询、调用 HTTP 接口等拥有了这些节点后,许多大略的业务逻辑就能完备通过可视化的办法实现。
在有 GC 的措辞中实现这个功能不难,但 Rust 会有点问题,由于它没有 GC,只有引用计数机制,而这些节点底层数据构造常日是树或图,为了方便操作常日会有相互引用的情形,比如引用父级节点,在 Rust 下须要利用 Weak 引用来避免内存无法开释,或者利用 arena 这种古老的内存池技能。
其余这部分涉及到数据库和 HTTP 这些外部要求,如果要想用异步机制来提升并发性能,比如接口可能是下载个大文件,这时 Node 和 Go 就比 Java 更有上风,Java 相应式代码写起来太难懂了,而 Kotlin 有协程相对好点。
整体来说这几个措辞我更方向于用 Node 实现这个功能,不过低代码平台常日不须要太高并发,利用 Java 实现问题也不大,但 Rust 下要实现这个功能会相对更繁芜。
流程引擎流程引擎和逻辑编排看起很相似,有部分低代码产品中还将这两部分合并了,流程引擎和逻辑编排有个最大不同是流程引擎有些分外的流转功能,比如:
如果审批人是自己就自动跳过多人审批是只要一个通过就行还是必须全部回退是要回到上个节点还是最初节点流程引擎的数据存储更适宜用图来表示,还常常要找父节点,因此随意马虎形成循环引用,导致 Rust 下编写起来更加繁琐。
可选方案综合各个措辞下的优缺陷,目前可选方案有:
Java/Kotlin上面所有功能 Java 都能胜任,而且是唯一支持所有国产数据库的方案,紧张缺陷是内存占用比较大,异步编写麻烦。Kotlin 虽然对异步有更好支持,但目前做事端很少人利用。Node仅次于 Java 的方案,除了国产数据库之外都能胜任,而且很随意马虎实现异步 IO,紧张缺陷是性能相对较差。Java + Node结合这两个措辞的特点同时利用,数据部分用 Java,异步及 JSON 处理用 Node,缺陷是有通讯代价。Rust + JavaRust 在数据库方面还不成熟,须要合营 Java 利用,优点是性能和内存占用都是最好,但 Rust 开拓业务逻辑实现本钱较高。Go + JavaGo 在数据查询、JavaScript 引擎方面不太适宜,须要合营 Java 利用,这个方案整体优点是比 Rust 大略很多。如果不考虑团队成员熟习情形,让我选择的话,我个人方向于 Node+Kotlin 或 Rust+Kotlin。
选择 Kotlin 的紧张考虑是海内普遍利用的 JDK 8,而 Java 8 短缺很多主要特性,代码写起来冗余,Kotlin 丰富的语法可以大幅简化,它的缺陷是有许多随意马虎导致其他人看不懂的写法,多人开拓时须要禁止炫技。
Rust 虽然上手门槛高,但它有个独特上风,便是能轻松嵌入到其它措辞中,如果想做低代码平台根本举动步伐,让底层能力可以供应给各种措辞利用,除了 C/C++ 之外 Rust 便是目前唯一成熟可靠的措辞,其它都差得更远,比如 Zig 虽然语法简洁,但它做不到内存安全,更随意马虎运行时出报错。