現(xiàn)在大火的低代碼是怎么回事?從實(shí)現(xiàn)原理談?wù)劦痛a(低代碼的概念)
我們?cè)诘痛a領(lǐng)域探索了很多年,從2015 開(kāi)始研發(fā)低代碼前端渲染(amis),從 2018 年開(kāi)研發(fā)后端低代碼數(shù)據(jù)模型,發(fā)布了愛(ài)速搭低代碼平臺(tái),這些年調(diào)研過(guò)了幾乎所有市面上的相關(guān)技術(shù)和產(chǎn)品,發(fā)現(xiàn)雖然每家產(chǎn)品細(xì)節(jié)都不太一樣,但在底層技術(shù)上卻只有少數(shù)幾種方案,因此我們認(rèn)為不同產(chǎn)品間的最大區(qū)別是實(shí)現(xiàn)原理,了解這些實(shí)現(xiàn)原理就能知道各個(gè)低代碼平臺(tái)的優(yōu)缺點(diǎn),所以本文將會(huì)介紹目前已知的各種低代碼實(shí)現(xiàn)方案,從實(shí)現(xiàn)原理角度看低代碼。
— 1 —
本文里的「低代碼」指的是什么?
在討論各個(gè)低代碼方案前,首先要明確「低代碼」究竟是什么?
這個(gè)問(wèn)題不好直接回答,因?yàn)榈痛a是非常寬泛的概念,有很多產(chǎn)品都聲稱自己的低代碼,但我們很容易反過(guò)來(lái)回答另一個(gè)問(wèn)題:「什么是低代碼產(chǎn)品唯一不可缺少的功能?」
我認(rèn)為這個(gè)功能是可視化編輯,因?yàn)榉强梢暬庉嬀褪谴a編輯,而只有代碼編輯的產(chǎn)品不會(huì)被認(rèn)為是低代碼,因此可視化編輯是低代碼的必要條件,低代碼其實(shí)還有另一個(gè)更清晰的叫法是可視化編程。
既然可視化編輯是低代碼的必要條件,那從實(shí)現(xiàn)角度看,實(shí)現(xiàn)可視化編輯有什么必要條件?
我認(rèn)為可視化編輯的必要條件是「聲明式」代碼,因?yàn)?span id="qsh1b7padf" class="candidate-entity-word" data-gid="2374904">可視化編輯器只支持「聲明式」代碼。
解釋一下什么是「聲明式」,除了聲明式之外還有另一種代碼模式是「命令式」,我們分別舉兩個(gè)例子,如果想繪制一個(gè)紅色區(qū)塊,用「聲明式」來(lái)實(shí)現(xiàn),可以使用 HTML CSS,類(lèi)似下面的方法:
<div style="background:red; height:50px"></div>
而換成用「命令式」來(lái)實(shí)現(xiàn),可以使用 Canvas API,類(lèi)似下面的方法:
const ctx = canvas.getContext('2d');ctx.fillStyle = 'red';const rectangle = new Path2D();rectangle.rect(0, 0, 100, 100);ctx.fill(rectangle);
雖然最終展現(xiàn)效果是一樣的,但這兩種代碼在實(shí)現(xiàn)思路上有本質(zhì)區(qū)別:
- 「聲明式」直接描述最終效果,不關(guān)心如何實(shí)現(xiàn)。
- 「命令式」關(guān)注如何實(shí)現(xiàn),明確怎么一步步達(dá)到這個(gè)效果。
從可視化編輯器的角度看,它們的最大區(qū)別是:
- 「聲明式」可以直接從展現(xiàn)結(jié)果反向推導(dǎo)回源碼
- 「命令式」無(wú)法做到反向推導(dǎo)
反向推導(dǎo)是編輯器必備功能,比如編輯器里的常見(jiàn)操作是點(diǎn)選這個(gè)紅色區(qū)塊,然后修改它的顏色,在這兩種代碼中如何實(shí)現(xiàn)?
如果是「聲明式」的 HTML CSS,可以直接改 style 的 background 值,而基于 Canvas 的命令式代碼則無(wú)法實(shí)現(xiàn)這個(gè)功能,因?yàn)闊o(wú)法從展現(xiàn)找到實(shí)現(xiàn)它的代碼,命令式代碼實(shí)現(xiàn)同樣效果的可能路徑是無(wú)數(shù)的,除了前面的示例,下面這段代碼也可以實(shí)現(xiàn)一樣的效果:
const ctx = canvas.getContext('2d');ctx.beginPath();ctx.moveTo(0, 0);ctx.lineTo(50, 0);ctx.strokeStyle = '#ff0000';ctx.lineWidth = 100;ctx.stroke();
甚至有可能這個(gè)顏色是多個(gè)字符串加隨機(jī)數(shù)拼接而成,即便通過(guò)靜態(tài)分析也找不到來(lái)源,從而無(wú)法實(shí)現(xiàn)可視化修改。
「命令式」代碼無(wú)法實(shí)現(xiàn)可視化編輯,而可視化編輯是低代碼唯一不可少的功能,所以我們可以得到結(jié)論:所有低代碼平臺(tái)必然只能采用「聲明式」代碼,這也是為什么所有低代碼平臺(tái)都會(huì)有內(nèi)置的「DSL」。
既然低代碼都是聲明式,那我們可以通過(guò)分析其它「聲明式」語(yǔ)言來(lái)了解低代碼的優(yōu)缺點(diǎn),其實(shí)在專業(yè)研發(fā)里,聲明式語(yǔ)言在部分領(lǐng)域已經(jīng)是主流了:
- HTML CSS 是一種頁(yè)面展現(xiàn)的 DSL
- SQL 是一種數(shù)據(jù)查詢及處理的 DSL
- K8S 的 YAML 是一種服務(wù)部署的 DSL
- NGINX conf 是一種反向代理的 DSL
上面這些方案目前都是主流,但它們?cè)缙诓⒉槐豢春茫热缡畮啄昵斑€曾經(jīng)爭(zhēng)論過(guò)到底是用 B/S 還是 C/S 架構(gòu),CSS 2 的功能主要是面向圖文排版,并不適合用來(lái)構(gòu)建應(yīng)用界面。
SQL 最開(kāi)始也不被看好,下面引用《硅谷簡(jiǎn)史》這本書(shū)里的部分文字:
1970年,IBM研究員特德·科德(Ted Codd)發(fā)表了一篇里程碑式的論文,《大型數(shù)據(jù)庫(kù)的系統(tǒng)模型》,介紹了關(guān)系數(shù)據(jù)庫(kù)理論。
當(dāng)時(shí)大多數(shù)人認(rèn)為關(guān)系數(shù)據(jù)庫(kù)沒(méi)有商業(yè)價(jià)值,因其速度太慢,不能滿足大規(guī)模數(shù)據(jù)處理或者大量用戶存取數(shù)據(jù),雖然關(guān)系數(shù)據(jù)庫(kù)理論上很漂亮而且易于使用,但它的速度太慢。
上面兩段其實(shí)說(shuō)的是 Oracle 的發(fā)家故事,可以看到當(dāng)時(shí)關(guān)系型數(shù)據(jù)庫(kù)并不被看好,因?yàn)榇蠹叶加X(jué)得慢,這點(diǎn)很好理解,數(shù)據(jù)庫(kù)在查詢前還得先解析 SQL語(yǔ)法、估算各種查詢的代價(jià)、生成執(zhí)行計(jì)劃,存儲(chǔ)也只能使用通用的數(shù)據(jù)結(jié)構(gòu),沒(méi)法根據(jù)不同業(yè)務(wù)進(jìn)行定制。
綜合來(lái)看這些「聲明式」語(yǔ)言有以下優(yōu)點(diǎn):
- 容易上手,因?yàn)槊枋龅氖墙Y(jié)果,語(yǔ)法可以做得簡(jiǎn)單,非研發(fā)也能快速上手 HTML 及 SQL。
- 支持可視化編輯,微軟的 HTML 可視化編輯 FrontPage 在 1995 年就有了,現(xiàn)在各種 BI 軟件可以認(rèn)為是 SQL 的可視化編輯。
- 容易優(yōu)化性能,無(wú)論是瀏覽器還是數(shù)據(jù)庫(kù)都在不斷優(yōu)化,比如可以自動(dòng)改成并行執(zhí)行,這是命令式語(yǔ)言無(wú)法自動(dòng)實(shí)現(xiàn)的。
- 容易移植,容易向下兼容,現(xiàn)在的瀏覽器能輕松渲染 30 年前的 HTML,而現(xiàn)在的編譯器沒(méi)法編譯 30 年前的瀏覽器引擎代碼。
而這些語(yǔ)言的缺點(diǎn)是:
1、只適合特定領(lǐng)域,命令式的語(yǔ)言比如 JavaScript 可以用在各種領(lǐng)域,但 HTML CSS 只適合渲染文檔及界面,SQL 只適合做查詢,所有這些語(yǔ)言都。
2、靈活性差,比如 SQL 雖然內(nèi)置了很多函數(shù),但想只靠它實(shí)現(xiàn)業(yè)務(wù)是遠(yuǎn)遠(yuǎn)不夠的,有些數(shù)據(jù)庫(kù)還提供了用戶自定義函數(shù)功能(UDF),通過(guò)代碼來(lái)擴(kuò)展。
3、調(diào)試?yán)щy,遇到問(wèn)題時(shí)如缺乏工具會(huì)難以排查,如果你在Firefox出現(xiàn)前開(kāi)發(fā)過(guò)頁(yè)面就會(huì)知道,由于IE6沒(méi)有開(kāi)發(fā)工具,編寫(xiě)復(fù)雜頁(yè)面體驗(yàn)很差,遇到問(wèn)題要看很久代碼才發(fā)現(xiàn)是某個(gè)標(biāo)簽沒(méi)閉合或者 CSS 類(lèi)名寫(xiě)錯(cuò)了。
4、強(qiáng)依賴運(yùn)行環(huán)境,因?yàn)槁暶魇街幻枋鼋Y(jié)果而不關(guān)注實(shí)現(xiàn),因此強(qiáng)依賴運(yùn)行環(huán)境,但這也帶來(lái)了以下問(wèn)題:
- 功能取決于運(yùn)行環(huán)境,比如瀏覽器對(duì) CSS 的支持程度決定某個(gè)屬性是否有人用,雖然出現(xiàn)了CSS Houdini 提案,但 Firefox 和 Safari 都不支持,而且上手成本太高,預(yù)計(jì)以后也不會(huì)流行。
- 性能取決于運(yùn)行環(huán)境,比如同一個(gè) SQL 在不同數(shù)據(jù)庫(kù)下性能有很大區(qū)別。
- 對(duì)使用者是黑盒,使用者難以知道最終實(shí)現(xiàn),就像很少人知道數(shù)據(jù)庫(kù)及瀏覽器的實(shí)現(xiàn)細(xì)節(jié),完全當(dāng)成黑盒來(lái)使用,一旦遇到性能問(wèn)題就不知所措。
- 技術(shù)鎖定,因?yàn)榧幢闶亲铋_(kāi)放的 HTML 也無(wú)法解決,很多年前許多網(wǎng)站只支持 IE,現(xiàn)在又變成了只支持 Chrome,微軟和 Opera 在掙扎了很多年后也干脆直接轉(zhuǎn)向用 Chromium。同樣的即便有 SQL 標(biāo)準(zhǔn),現(xiàn)在用的 Oracle/SQL Server 應(yīng)用也沒(méi)法輕松遷移到 Postgres/MySQL 上。低代碼行業(yè)未來(lái)也一樣,即便出了標(biāo)準(zhǔn)也解決不了鎖定問(wèn)題,更有可能是像小程序標(biāo)準(zhǔn)那樣發(fā)展緩慢,功能遠(yuǎn)落后于微信。
因?yàn)榈痛a就是一種聲明式編程,所以這些「聲明式」優(yōu)缺點(diǎn),其實(shí)就是低代碼的優(yōu)缺點(diǎn),了解聲明式的歷史及現(xiàn)狀就能更好理解低代碼,因?yàn)椋?/span>
- 低代碼的各種優(yōu)點(diǎn)是「聲明式」所帶來(lái)的。
- 低代碼被質(zhì)疑的各種缺點(diǎn)也是「聲明式」所導(dǎo)致的。
— 2 —
低代碼的實(shí)現(xiàn)方案
說(shuō)完了聲明式,我們就對(duì)低代碼有了全面認(rèn)識(shí),接下來(lái)進(jìn)入正題,開(kāi)始介紹已知的各種低代碼實(shí)現(xiàn)原理,將會(huì)分為前端和后端兩部分。
— 3 —
生成代碼的方案算不算低代碼?
在討論各種方案前,有一種方案比較特別,它雖然也有配置規(guī)范或 DSL,甚至有可視化編輯,但最終應(yīng)用運(yùn)行是通過(guò)生成代碼的方式實(shí)現(xiàn)的,不依賴依賴運(yùn)行環(huán)境。
這個(gè)方案最大的優(yōu)點(diǎn)是可以和專業(yè)開(kāi)發(fā)整合,因此靈活性強(qiáng)、可以使用原有的開(kāi)發(fā)流程,本質(zhì)上和專業(yè)開(kāi)發(fā)一樣。
但也有如下缺點(diǎn):
- 強(qiáng)依賴研發(fā),無(wú)法做到給非研發(fā)使用,因?yàn)楹罄m(xù)代碼需要編譯上線。
- 無(wú)法持續(xù)可視化編輯,因?yàn)榇a無(wú)法可視化編輯,生成代碼后只要有修改就沒(méi)法再反向還原成低代碼的形式,后續(xù)只能代碼編輯。
- 難以實(shí)現(xiàn)完全用低代碼開(kāi)發(fā)應(yīng)用,因?yàn)椴荒苌商珡?fù)雜的代碼,使得這種方案一般不包括交互行為,通常是只有前端界面支持可視化編輯。
- 無(wú)法做到向下兼容,因?yàn)樯傻哪且凰查g代碼依賴的框架版本就固定了,目前還沒(méi)見(jiàn)過(guò)哪款前后前端框架做過(guò)到完全向下兼容。
因此我認(rèn)為生成代碼的方案不算真正的低代碼,本質(zhì)上它還是一種開(kāi)發(fā)輔助方式,一種高級(jí)點(diǎn)的腳手架工具,和大部分IDE的生成樣板代碼能力一樣,使用這種方案無(wú)法做到持續(xù)可視化開(kāi)發(fā),我還沒(méi)見(jiàn)過(guò)有人將 HTML CSS 編譯成 C 代碼后二次開(kāi)發(fā)。
— 4 —
前端代碼實(shí)現(xiàn)原理 – 界面渲染
前面提到前端 HTML CSS 可以看成一種描述界面的低代碼 DSL,因此前端界面實(shí)現(xiàn)低代碼會(huì)比較容易,只需要對(duì) HTML CSS 進(jìn)行更進(jìn)一步封裝,這里以我們的開(kāi)源項(xiàng)目 amis 為例進(jìn)行介紹。
amis 核心原理是將 JSON 轉(zhuǎn)成自研的 React 組件庫(kù),然后使用 React 進(jìn)行渲染。
比如下面這段 JSON:
{ "type": "page", "title": "頁(yè)面標(biāo)題", "subTitle": "副標(biāo)題", "body": { "type": "form", "title": "用戶登錄", "body": [ { "type": "input-text", "name": "username", "label": "用戶名" } ] }}
可以理解 amis 原理就是轉(zhuǎn)成了下面這樣的 React 組件樹(shù),最終由各個(gè) React 組件庫(kù)渲染 HTML:
<Page title="頁(yè)面標(biāo)題" subTitle="副標(biāo)題"> <Form title="用戶登錄"> <InputText name="username" label="用戶名" /> </Form></Page>
雖然也有低代碼平臺(tái)直接使用 HTML CSS 來(lái)實(shí)現(xiàn)更靈活的界面控制,但這樣做會(huì)導(dǎo)致用起來(lái)復(fù)雜度高,因?yàn)橥ǔP枰鄬忧短?HTML 才能實(shí)現(xiàn)一個(gè)組件,使用者還必須熟悉 HTML 及 CSS,上手門(mén)檻過(guò)高,因此大部分低代碼平臺(tái)都是類(lèi)似 amis 那樣使用 JSON 進(jìn)行簡(jiǎn)化。
這里有個(gè)小問(wèn)題,為什么大家?guī)缀跞际褂?json?我覺(jué)得有兩方面原因:
- 低代碼平臺(tái)編輯器幾乎都是基于 Web 實(shí)現(xiàn),JavaScript 可以方便操作 JSON。
- JSON 可以支持雙向編輯,它的讀取和寫(xiě)入是一一對(duì)應(yīng)的。
第二點(diǎn)怎么理解?可以對(duì)比一下 YAML,它有引用功能,導(dǎo)致了不好實(shí)現(xiàn)雙向編輯,比如下面 YAML 示例:
paths: root_path: &root val: /path/to/root/ patha: &a root_path: *root
轉(zhuǎn)成了對(duì)應(yīng)的 JSON 數(shù)據(jù)后,就變成了:
{ "paths": { "root_path": { "val": "/path/to/root/" }, "patha": { "root_path": { "val": "/path/to/root/" } } }}
可以看到之前的引用關(guān)系沒(méi)了,而是復(fù)制出了一部分,如果直接基于這個(gè)數(shù)據(jù)進(jìn)行可視化編輯,編輯器在修改的時(shí)候就只會(huì)改一處,也沒(méi)法再還原成之前的 YAML 了,要想實(shí)現(xiàn) YAML 可視化編輯就不能先轉(zhuǎn)成 JSON,而是要對(duì) YAML 解析后的樹(shù)形結(jié)構(gòu)進(jìn)行操作,前端界面實(shí)現(xiàn)成本很高,因此目前還沒(méi)見(jiàn)過(guò) YAML 的可視化編輯器。
但 JSON 的優(yōu)點(diǎn)就是它的缺點(diǎn),因?yàn)樗挠猛臼菙?shù)據(jù)交換而不是人工編寫(xiě),導(dǎo)致基于 JSON 構(gòu)建 DSL 不方便編輯,會(huì)有以下 3 個(gè)問(wèn)題:
- 不支持注釋
- 不支持多行字符串
- 語(yǔ)法過(guò)于嚴(yán)格,比如不支持單引號(hào),不能在最后多寫(xiě)一個(gè)逗號(hào)
其中我們對(duì)這個(gè)注釋問(wèn)題進(jìn)行了特殊支持,開(kāi)發(fā)了帶注釋的 JSON 解析,存儲(chǔ)的時(shí)候?qū)⒆⑨寖?nèi)嵌到一個(gè)特殊的字段中,在代碼顯示的時(shí)候?qū)⑺崛〕鰜?lái)變成注釋。
另外許多低代碼平臺(tái)會(huì)將這個(gè) JSON 配置隱藏,只提供界面編輯,但在 amis 可視化編輯器里提供了直接修改 JSON 的功能,因?yàn)閷?duì)于熟悉的開(kāi)發(fā)者,直接編寫(xiě) JSON 要比在屬性面板里找半天效率高,還可以直接將 amis 文檔中的示例粘貼進(jìn)來(lái)快速創(chuàng)建。
amis 開(kāi)始編輯器里 JSON 編輯模式
前面提到聲明式容易向下兼容,amis 自己就是最好的例子,在 amis 誕生的 2015 年前端框架和現(xiàn)在有大量區(qū)別:
- Vue 還是 1,現(xiàn)在已經(jīng)到 3 了,不向下兼容。
- Angular 還是 1,現(xiàn)在已經(jīng) 13 了,不向下兼容。
- React 雖然整體用法沒(méi)變,但有大量細(xì)節(jié)不向下兼容,加上 hooks 推出后,許多第三方庫(kù)改成了 hooks 版本,導(dǎo)致舊的類(lèi)組件形式?jīng)]法直接使用。
而 amis 早期的界面配置現(xiàn)在還能繼續(xù)使用,不受框架升級(jí)影響。
— 5 —
交互邏輯的實(shí)現(xiàn)
前面說(shuō)到前端界面低代碼是比較容易,但交互及邏輯處理卻很難低代碼話,目前常見(jiàn)有三種方案:
- 使用圖形化編程
- 固化交互行為
- 使用 JavaScript
先說(shuō)第一種圖形化編程,這是非常自然的想法,既然低代碼的關(guān)鍵是可視化,那直接使用圖形化的方式編程不就行了?
但我們發(fā)現(xiàn)這么做局限性很大,本質(zhì)的原因是「代碼無(wú)法可視化」,這點(diǎn)在 35 年前沒(méi)有銀彈的論文里就提到了。
為什么代碼無(wú)法可視化?首先想一想,可視化的前提條件是什么?
答案是需要具備空間形體特征,可視化只能用來(lái)展現(xiàn)二維及三維的物體,因?yàn)橐痪S沒(méi)什么意義,四維及以上大部人無(wú)法理解,所以如果一個(gè)事物沒(méi)有形體特征,它就沒(méi)法被可視化。
舉個(gè)例子,下面是一段 amis中 代碼,作用是遍歷 JSON 并調(diào)用外部函數(shù)進(jìn)行處理:
function JSONTraverse(json, mapper) { Object.keys(json).forEach(key => { const value = json[key]; if (isPlainObject(value) || Array.isArray(value)) { JSONTraverse(value, mapper); } else { mapper(value, key, json); } });}
雖然只有 10 行代碼,卻包含了循環(huán)、調(diào)用函數(shù)、類(lèi)型檢測(cè)、分支判斷、或操作符、遞歸調(diào)用、參數(shù)是函數(shù)這些抽象概念,這些概念在現(xiàn)實(shí)中都找不到形體的,你可以嘗試一下用圖形來(lái)表示這段代碼,然后給周?chē)丝纯?,我相信任何圖形化的嘗試都會(huì)比原本這段代碼更難懂,因?yàn)槟阈枰韧ㄟ^(guò)不同圖形來(lái)區(qū)分上面的各種概念,其他人得先熟悉這些圖形符號(hào)才能看懂,理解成本反而更高了。
代碼的這些抽象思維難以像積木一樣進(jìn)行拼接,積木拼接這種方式只適合用來(lái)實(shí)現(xiàn)簡(jiǎn)單的邏輯,比如 Scratch。
Scratch
而前面圖形化是低代碼唯一不可少的功能,這就使得低代碼不適合做復(fù)雜的抽象邏輯處理,這是圖形化缺陷決定的,因此在復(fù)雜邏輯處理方面低代碼永遠(yuǎn)無(wú)法徹底取代專業(yè)代碼開(kāi)發(fā)。
但如果是面向特定領(lǐng)域,低代碼平臺(tái)可以先將這個(gè)領(lǐng)域難以圖形化的算法預(yù)置好,讓使用者只需做簡(jiǎn)單的處理,比如在 Blender 中將 PBR 算法封裝了,使用的時(shí)候只需要調(diào)整參數(shù)就行。
Blender 中的材質(zhì)節(jié)點(diǎn)編輯
如果真要用節(jié)點(diǎn)實(shí)現(xiàn)這個(gè)算法會(huì)非常復(fù)雜,大概長(zhǎng)這樣:
在復(fù)雜邏輯下,圖形中的連線反而變成了視覺(jué)干擾,比如下面的例子:
來(lái)自 UE4 Blueprints From Hell 里的一張圖
想象一下假設(shè)客戶做出了上面這個(gè)圖的復(fù)雜邏輯,然后找你排查問(wèn)題,而客戶的程序是部署在內(nèi)網(wǎng)的,沒(méi)法導(dǎo)出,只能通過(guò)微信拍屏幕給你看……
因此我認(rèn)為圖形化不適合用來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯,只適合用來(lái)做更高層次流程控制,比如審批流,審批流是現(xiàn)實(shí)真實(shí)存在的,沒(méi)有復(fù)雜的抽象邏輯,因此適合圖形化。
在愛(ài)速搭中,我們除了實(shí)現(xiàn)流程功能,還實(shí)現(xiàn)了樹(shù)形結(jié)構(gòu)的 API 編排功能,它本質(zhì)上是模仿代碼結(jié)構(gòu),將會(huì)在后面進(jìn)行介紹。
說(shuō)完了圖形化編程,接下來(lái)談第二種方案:固化交互行為,這是不少低代碼平臺(tái)的做法,我們還是以 amis 為例進(jìn)行介紹。
amis 將常用的交互行為固化并做成了配置,比如彈框是下面的配置:
{ "label": "彈框", "type": "button", "actionType": "dialog", "dialog": { "title": "彈框", "body": "這是個(gè)簡(jiǎn)單的彈框。" }}
除了彈框之外還有發(fā)起請(qǐng)求、打開(kāi)鏈接、刷新其它組件等,使用固化交互行為有下面兩個(gè)優(yōu)點(diǎn):
- 可以可視化編輯
- 整合度高,比如彈框里可以繼續(xù)使用 amis 配置,通過(guò)嵌套實(shí)現(xiàn)復(fù)雜的交互邏輯
但這個(gè)方案最大的缺點(diǎn)是靈活性受限,只能使用 amis 內(nèi)置的行為。
要實(shí)現(xiàn)更靈活的控制,還是得支持第三個(gè)方案:JavaScript,目前有的低代碼平臺(tái)只在界面編輯提供可視化編輯,一旦涉及到交互就得寫(xiě) JavaScript,這和 30 年前的 C Builder 本質(zhì)上是一樣的:
RDA Studio 11 的界面編輯
但第三個(gè)方案的最大缺點(diǎn)就是無(wú)法可視化編輯,因此不算是低代碼。
— 6 —
后端低代碼的方案
前端討論完了,接下來(lái)是后端部分,后端低代碼需要解決以下三個(gè)問(wèn)題:
1、如何自定義數(shù)據(jù)存儲(chǔ)?
低代碼平臺(tái)需要支持用戶存儲(chǔ)自定義數(shù)據(jù),因?yàn)槊總€(gè)應(yīng)用所需的字段是不一樣的。
自定義數(shù)據(jù)存儲(chǔ)是后端低代碼最重要的功能,使用什么方案將直接影響這個(gè)產(chǎn)品的適用范圍,目前我們已知有 5 種方案,每種都有自己的優(yōu)缺點(diǎn)。
存儲(chǔ)的實(shí)現(xiàn)方案 1:直接使用關(guān)系型數(shù)據(jù)庫(kù)
這個(gè)方案的原理是將數(shù)據(jù)模型的可視化操作轉(zhuǎn)成數(shù)據(jù)庫(kù) DDL,比如添加了一個(gè)字段,系統(tǒng)會(huì)自動(dòng)生成表結(jié)構(gòu)變更語(yǔ)句:
ALTER TABLE 'blog' ADD 'title' varchar(255) NULL;
這個(gè)方案的優(yōu)點(diǎn)是:
- 所有方案里唯一支持直連外部數(shù)據(jù)庫(kù),可以對(duì)接已有系統(tǒng)。
- 性能高和靈活性強(qiáng),因?yàn)榭梢允褂酶呒?jí) SQL。
- 開(kāi)發(fā)人員容易理解,因?yàn)楹蛯I(yè)開(kāi)發(fā)是一樣的。
但它的缺點(diǎn)是:
- 需要賬號(hào)有創(chuàng)建用戶及 DDL權(quán)限,如果有安全漏洞會(huì)造成嚴(yán)重后果,有些公司內(nèi)部線上帳號(hào)沒(méi)有這個(gè)權(quán)限,導(dǎo)致無(wú)法實(shí)現(xiàn)自動(dòng)化變更。
- DDL 有很多問(wèn)題無(wú)解,比如在有數(shù)據(jù)的情況下,就不能再添加一個(gè)沒(méi)有默認(rèn)值的非 NULL 字段。
- DDL 執(zhí)行時(shí)會(huì)影響線上性能,比如 MySQL 5.6 之前的版本在一個(gè)大數(shù)據(jù)量的表中添加索引字段會(huì)鎖整個(gè)表的寫(xiě)入(但也有數(shù)據(jù)庫(kù)不受影響,比如 TiDB、OceanBase 支持在線表結(jié)構(gòu)變更,不會(huì)阻塞讀寫(xiě))。
- 部分?jǐn)?shù)據(jù)庫(kù)不支持 DDL 事務(wù),比如 MySQL 8 之前的版本,導(dǎo)致一旦在執(zhí)行過(guò)程中出錯(cuò)將無(wú)法恢復(fù)。
- 實(shí)現(xiàn)成本較高,需要實(shí)現(xiàn)「動(dòng)態(tài)實(shí)體」功能,如果要支持不同數(shù)據(jù)庫(kù)還得支持各種方言。
盡管這個(gè)方案有很多缺點(diǎn),但它的優(yōu)點(diǎn)也很突出,因此愛(ài)速搭里實(shí)現(xiàn)了這個(gè)方案,因?yàn)槲覀冇X(jué)得能連已有數(shù)據(jù)庫(kù)是非常重要的,其它方案都只適合用來(lái)做新項(xiàng)目,這個(gè)方案使得可以逐步將已有項(xiàng)目低代碼化,不需要做數(shù)據(jù)遷移。
愛(ài)速搭里的數(shù)據(jù)庫(kù)模型
實(shí)現(xiàn)這個(gè)方案的關(guān)鍵是「動(dòng)態(tài)實(shí)體」,在專業(yè)開(kāi)發(fā)中實(shí)體(Entity)定義都是靜態(tài)的,以 Java 為例,它從 2006 年開(kāi)始就有專門(mén)的 JPA 規(guī)范,但這個(gè)規(guī)范是定義基于 Java 代碼注解,使得需要經(jīng)過(guò)編譯才能使用,畢竟它的定位是面向?qū)I(yè)開(kāi)發(fā),只有寫(xiě)在代碼里才能支持代碼提示,提升開(kāi)發(fā)體驗(yàn)。
而低代碼平臺(tái)中需要將這個(gè)實(shí)體定義抽象成配置,在運(yùn)行時(shí)動(dòng)態(tài)生成實(shí)體,如果使用 JPA 就需要生成 Java 代碼后進(jìn)行編譯,這很容易出錯(cuò),不太適合低代碼平臺(tái),所以使用這個(gè)方案需要實(shí)現(xiàn)「動(dòng)態(tài)實(shí)體」功能,是整個(gè)方案最大難點(diǎn)。
存儲(chǔ)的實(shí)現(xiàn)方案 2:使用文檔型數(shù)據(jù)庫(kù)
文檔型數(shù)據(jù)庫(kù)不需要預(yù)先定義表結(jié)構(gòu),因此它很適合用來(lái)存儲(chǔ)用戶自定義數(shù)據(jù),這個(gè)方案實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,以 MongoDB 為例,可以這樣做:
- 用戶創(chuàng)建一個(gè)自定義表的時(shí)候,系統(tǒng)就自動(dòng)創(chuàng)建一個(gè) collection,所有這個(gè)表的數(shù)據(jù)都存在這個(gè) collection 里。
- 用戶新增字段的時(shí)候,就隨機(jī)分配一個(gè) fileId,后續(xù)對(duì)這個(gè)字段的操作都自動(dòng)映射到這個(gè) fileId 上,用 fileId 的好處是用戶重命名字段后還能查找之前的數(shù)據(jù),因?yàn)樗袛?shù)據(jù)查詢底層都基于這個(gè) fileId。
- 查詢的時(shí)候先找到對(duì)應(yīng)的 collection,再通過(guò) meta 信息查詢字段對(duì)應(yīng)的 fileId,使用這個(gè) fileId 來(lái)獲取數(shù)據(jù)。
這個(gè)方案的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,用戶體驗(yàn)可以做得更好,是目前大部分零代碼平臺(tái)的選擇,使用這個(gè)方案的產(chǎn)品也很好識(shí)別,只要看一下它的私有部署文檔,如果有要求裝 MongoDB 就肯定是。
但這個(gè)方案也有顯著缺點(diǎn):
- 無(wú)法支持外部數(shù)據(jù)庫(kù),數(shù)據(jù)是孤島,外部數(shù)據(jù)接入只能通過(guò)導(dǎo)入的方式。
- MongoDB 在國(guó)內(nèi)發(fā)展緩慢,接受度依然很低,目前還沒(méi)聽(tīng)說(shuō)有哪家大公司里最重要的數(shù)據(jù)存在 MongoDB 里,一方面有歷史原因,另一方面不少數(shù)據(jù)庫(kù)都開(kāi)始支持 JSON 字段,已經(jīng)能取代大部分必須用 MongoDB 的場(chǎng)景了。
- 不支持高級(jí) SQL 查詢。
你可能會(huì)問(wèn),現(xiàn)在 MySQL、Postgres 等數(shù)據(jù)庫(kù)都支持 JSON 字段類(lèi)型了,是否可以用這個(gè)字段來(lái)實(shí)現(xiàn)低代碼?
答案是不太行,只適合數(shù)據(jù)量不大的場(chǎng)景,雖然 JSON 字段可以用來(lái)存用戶自定義數(shù)據(jù),但無(wú)法創(chuàng)建字段索引,比如在 MySQL 要想給 JSON 創(chuàng)建索引,還是得創(chuàng)建一個(gè)特殊的字段,這又需要 DDL 權(quán)限了,沒(méi)有索引會(huì)導(dǎo)致這個(gè)方案無(wú)法支持大量數(shù)據(jù)查詢。
在愛(ài)速搭中我們也實(shí)現(xiàn)這個(gè)方案,目前是基于 MySQL JSON 字段,后續(xù)可能也會(huì)支持存儲(chǔ)使用 MongoDB,目前它的使用場(chǎng)景是流程執(zhí)行過(guò)程中的數(shù)據(jù)存儲(chǔ),因此數(shù)據(jù)量不會(huì)很大,我們希望流程功能用起來(lái)可以更簡(jiǎn)單些。
它的最大特點(diǎn)是界面編輯和數(shù)據(jù)存儲(chǔ)是統(tǒng)一的,當(dāng)你拖入文本框到頁(yè)面后就會(huì)自動(dòng)創(chuàng)建對(duì)應(yīng)的字段,不需要先創(chuàng)建數(shù)據(jù)模型再創(chuàng)建界面,因此用起來(lái)更簡(jiǎn)單。
愛(ài)速搭里的表單模型
存儲(chǔ)的實(shí)現(xiàn)方案 3:使用行代替列
這是很多可擴(kuò)展平臺(tái)里使用的技術(shù),比較典型的是 WordPress,它的擴(kuò)展性很強(qiáng),裝個(gè)擴(kuò)展就能變成電商網(wǎng)站。而整個(gè) WordPress 只有 12 個(gè)表,它是怎么做到的?方法是靠各種 meta 表,比如用于擴(kuò)展文章的 wp_postmeta 表結(jié)構(gòu)如下:
CREATE TABLE wp_postmeta ( meta_id bigint(20) unsigned NOT NULL auto_increment, post_id bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY post_id (post_id), KEY meta_key (meta_key)) DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
其中的關(guān)鍵就是 meta_key 和 meta_value 這兩個(gè)字段,相當(dāng)于將數(shù)據(jù)庫(kù)當(dāng) KV 存儲(chǔ)用了,因此可以任意擴(kuò)展字段名及值。
這個(gè)方案的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,但缺點(diǎn)也很明顯:
- 查詢性能低,如果有 10 個(gè)字段就要查 10 行。
- 無(wú)法支持 SQL 高級(jí)查詢,因?yàn)閿?shù)據(jù)是按行存的。
這個(gè)方案主要用于成熟項(xiàng)目的擴(kuò)展,比如在 CRM 產(chǎn)品中允許用戶擴(kuò)展字段,但因?yàn)樾阅茌^低,并不適合通用低代碼平臺(tái)。
存儲(chǔ)的實(shí)現(xiàn)方案 4:元信息 寬表
早期數(shù)據(jù)庫(kù)不支持 JSON 字段的時(shí)候,有些開(kāi)發(fā)者會(huì)預(yù)留幾個(gè)列來(lái)給用戶擴(kuò)展自定義屬性,比如在表里加上 ext1、ext2、ext3 字段,讓用戶可以存 3 個(gè)定制數(shù)據(jù),基于這個(gè)原理我們可以進(jìn)一步擴(kuò)展,通過(guò)預(yù)留大量列來(lái)實(shí)現(xiàn)應(yīng)用自定義存儲(chǔ)。
這個(gè)方案最早出現(xiàn)在 force.com,具體細(xì)節(jié)可以閱讀它架構(gòu)說(shuō)明文檔[1]。
實(shí)現(xiàn)它有兩個(gè)關(guān)鍵點(diǎn):元數(shù)據(jù)、預(yù)留列,這里簡(jiǎn)單說(shuō)明一下原理,首先系統(tǒng)預(yù)先創(chuàng)建一個(gè) 500 列的表,比如就叫 data:
tenant_id | table_id | uuid | value0 | value1 | …… | value 4000 |
也可以創(chuàng)建更多,但注意有的數(shù)據(jù)庫(kù)對(duì)列的數(shù)量有限制,比如 MySQL 最多是 4096 列。
上面的 data 表里主要有 4 類(lèi)字段:
- tenant_id 是租戶 id,用于隔離不同租戶
- table_id 是自定義表的 id
- uuid 是具體這一行數(shù)據(jù)的 id
- 后面的 value0 到 value500 都是預(yù)留的列,用于存儲(chǔ)實(shí)際數(shù)據(jù),一般使用變長(zhǎng)字符串類(lèi)型
當(dāng)用戶給這個(gè)表新增一個(gè)字段的時(shí)候,怎么知道這個(gè)字段放哪?這就需要另一個(gè)用于描述字段信息的元數(shù)據(jù)表,比如增加一個(gè)「標(biāo)題」字段時(shí),使用另一個(gè) table_fields 表來(lái)描述這個(gè)字段的信息,示例如下:
tenant_id | table_id | field_id | value_index | name | type |
1 | 1 | 0 | 0 | 標(biāo)題 | string |
在這個(gè) table_fields 表里:
- tenant_id 和 table_id 和前面一樣。
- field_id 對(duì)應(yīng)的是給這個(gè)「標(biāo)題」字段分配的 id。
- value_index 對(duì)應(yīng)前面那個(gè) data 表里預(yù)覽列的位置,比如這個(gè)值是 0,就意味著 value0 列被分配給了這個(gè)「標(biāo)題」字段。
- name 用來(lái)存名稱,type 用來(lái)標(biāo)識(shí)類(lèi)型,這樣查詢和寫(xiě)入數(shù)據(jù)的時(shí)候,首先從這里查詢 value_index 是什么,然后再去前面那個(gè)預(yù)留列的表中查詢對(duì)應(yīng)列的值。
最終在實(shí)際查詢的時(shí)候需要根據(jù)元數(shù)據(jù)表做一下轉(zhuǎn)換,比如 select 標(biāo)題 from blog 要轉(zhuǎn)成 select value0 from data where tenal_id = 1 and table_id = 1。
要完全實(shí)現(xiàn)這個(gè)方案還有很多細(xì)節(jié)問(wèn)題得解決,由于篇幅原因這里不詳細(xì)介紹,感興趣可以閱讀前面提到的 force.com 技術(shù)白皮書(shū),這里列舉其中幾個(gè)問(wèn)題:
- 因?yàn)榇鎯?chǔ)只能是字符串,所以對(duì)于日期、數(shù)字等其他類(lèi)型,因此讀取的時(shí)候需要根據(jù)類(lèi)型使用數(shù)據(jù)庫(kù)里的函數(shù)進(jìn)行轉(zhuǎn)換,比如 STR_TO_DATE。
- 需要單獨(dú)處理唯一性功能,因?yàn)檫@個(gè)數(shù)據(jù)表是所有租戶共用的,沒(méi)法設(shè)置表級(jí)別的唯一性索引,這時(shí)就需要新建一個(gè)表來(lái)單獨(dú)做,壞處是數(shù)據(jù)多份容易產(chǎn)生不一致,需要在所有更新操作都加事務(wù)。
- 需要單獨(dú)處理索引功能,同樣是因?yàn)樽侄问亲址虼藳](méi)法直接在 data 表里加索引,如果數(shù)據(jù)存儲(chǔ)的是數(shù)字,排序就是錯(cuò)的,為了解決這個(gè)問(wèn)題需要另外創(chuàng)建一個(gè)一個(gè)包含常見(jiàn)字段的索引表,數(shù)據(jù)更新的時(shí)候。
- 自增字段需要自己實(shí)現(xiàn)。
- 元數(shù)據(jù)信息需要緩存,不然每次查詢前都需要先查詢?cè)獢?shù)據(jù)信息,然后再去查詢真正的數(shù)據(jù)。
這個(gè)方案比前面幾個(gè)方案的優(yōu)點(diǎn)是:
- 比起第一種原生數(shù)據(jù)庫(kù)表方案,它不需要 DDL 操作,不容易出問(wèn)題,跟適合 SaaS 產(chǎn)品。
- 比起第二種文檔型數(shù)據(jù)庫(kù)方案,它的存儲(chǔ)使用更為成熟的關(guān)系型數(shù)據(jù)庫(kù),相關(guān)的運(yùn)維工具多。
- 比起第三種行代替列方案,它的查詢性能好,因?yàn)槭亲x取一行數(shù)據(jù)。
但它也有許多缺點(diǎn):
- 無(wú)法支持 SQL 所有功能,比如 force.com 的 SOQL 無(wú)法 select *、沒(méi)有視圖、不支持寫(xiě)入和更新數(shù)據(jù),通過(guò)這個(gè)特點(diǎn)就能識(shí)別出使用這個(gè)方案的產(chǎn)品,這類(lèi)產(chǎn)品雖然看起來(lái)很像在用傳統(tǒng)數(shù)據(jù)庫(kù),也支持使用 SQL,但這個(gè) SQL 一定是受限的。
- 數(shù)據(jù)泄露風(fēng)險(xiǎn)高,因?yàn)樗凶鈶舻臄?shù)據(jù)都存在一張表里,而數(shù)據(jù)庫(kù)都不支持行級(jí)別權(quán)限的賬號(hào),所以意味著所有租戶其實(shí)共享一個(gè)數(shù)據(jù)庫(kù)賬號(hào),只要有某個(gè)功能的查詢漏了加租戶過(guò)濾就能查到所有租戶數(shù)據(jù)。相比之下前面提到的原生表及文檔型數(shù)據(jù)庫(kù)方案都能直接使用數(shù)據(jù)庫(kù)自帶的賬號(hào)進(jìn)行有效隔離。
- 一些數(shù)據(jù)庫(kù)高級(jí)字段難以支持,比如坐標(biāo)數(shù)據(jù)、二進(jìn)制類(lèi)型等,只能用單獨(dú)的表存,導(dǎo)致了查詢開(kāi)銷(xiāo)。
- 整體實(shí)現(xiàn)成本高,其中很多細(xì)節(jié)需要處理好,比如保證數(shù)據(jù)一致性,因?yàn)闉榱藢?shí)現(xiàn)唯一性、索引等功能需要拷貝數(shù)據(jù),更新的時(shí)候要同時(shí)更新。
愛(ài)速搭中沒(méi)有實(shí)現(xiàn)這個(gè)方案,我們?cè)?jīng)考慮過(guò)但后來(lái)放棄了,我認(rèn)為這個(gè)方案雖然很適合 SaaS 類(lèi)的低代碼產(chǎn)品,但它的用戶定位比較尷尬,一方面是有一定復(fù)雜度導(dǎo)致不能做到零代碼平臺(tái)那樣的易用性,另一方面是有不少限制導(dǎo)致專業(yè)研發(fā)不喜歡,所以最終是兩邊都不討好,這種產(chǎn)品想做成需要依賴廣泛使用的平臺(tái)。
因此 Salesforce 才能做成,而國(guó)內(nèi)類(lèi)似情況我能想到的唯一成功案例是微信小程序,盡管有很多限制,但因?yàn)槲⑿艔V泛使用,所以才成功了,如果是一個(gè)獨(dú)立的小程序平臺(tái)肯定沒(méi)人用。
這里說(shuō)一段小歷史,在十幾年前,當(dāng)時(shí)云計(jì)算領(lǐng)域最先推出的是谷歌 2008 年發(fā)布的 App Engine,這是谷歌的第一個(gè)云產(chǎn)品,而當(dāng)時(shí)類(lèi)似 AWS EC2 那樣的虛機(jī)產(chǎn)品國(guó)內(nèi)都還沒(méi)有,畢竟 KVM 也才剛發(fā)布。
如果你當(dāng)時(shí)問(wèn)云計(jì)算的專家,云計(jì)算的未來(lái)是 App Engine 還是虛擬機(jī),我聽(tīng)到不少專家的回答是 App Engine,因?yàn)檫@看起來(lái)更有前景,你只需要寫(xiě)代碼,不用操心運(yùn)維,平臺(tái)會(huì)自動(dòng)水平擴(kuò)展,這才是云該有的樣子,當(dāng)時(shí)國(guó)內(nèi)不少公司都推出了類(lèi)似產(chǎn)品。
但 13 年后的今天,國(guó)內(nèi) App Engine 平臺(tái)幾乎都關(guān)閉了,而虛機(jī)不但是主流,還更進(jìn)一步出現(xiàn)了物理機(jī)產(chǎn)品。這個(gè)元信息方案給我的感覺(jué)和當(dāng)年 App Engine 很像,看上去能完成增刪改查的簡(jiǎn)單應(yīng)用,但如果深入就發(fā)現(xiàn)缺少很多功高級(jí)功能,導(dǎo)致兩邊不討好:
- 技術(shù)薄弱的開(kāi)發(fā)者不會(huì)用,比如因?yàn)?App Engine 是分布式部署,導(dǎo)致上傳文件不能放本地,必須改成對(duì)象存儲(chǔ),所以沒(méi)法直接用 WordPress 沒(méi)法用,對(duì)于小站長(zhǎng)來(lái)說(shuō)還不如用虛擬主機(jī)。
- 對(duì)于有技術(shù)實(shí)力的開(kāi)發(fā)者,又會(huì)覺(jué)得平臺(tái)能力受限,不利于自己后續(xù)發(fā)展,比如谷歌的 App Engine 直到 2019 年才支持 WebSocket。
整體而言我不看好這個(gè)方案在國(guó)內(nèi)的發(fā)展。
存儲(chǔ)的實(shí)現(xiàn)方案 5:使用單文件
這個(gè)方案目前只在「仿 Excel」的零代碼平臺(tái)中見(jiàn)過(guò),它和 Excel 類(lèi)似,數(shù)據(jù)全都放一個(gè)文件里,查詢過(guò)濾完全靠前端,優(yōu)點(diǎn)是:
- 實(shí)現(xiàn)簡(jiǎn)單,部署成本低,因?yàn)楸淼拇鎯?chǔ)就是單文件。
- 容錯(cuò)性強(qiáng),數(shù)據(jù)類(lèi)型都是靠前端處理的,不會(huì)出現(xiàn)存數(shù)據(jù)庫(kù)導(dǎo)致。
缺點(diǎn)是:
- 如果要支持行列級(jí)別權(quán)限校驗(yàn),還得在后端實(shí)現(xiàn)一遍過(guò)濾,而每次都加載一個(gè)巨大的 JSON 文件對(duì)服務(wù)器內(nèi)存有較高要求。
- 難以支持事務(wù)操作,尤其是支持行級(jí)別的操作。
- 目前看十萬(wàn)級(jí)別數(shù)據(jù)處理可以只靠前端,但再大量的數(shù)據(jù)就不合適了,一次性加載太多對(duì)帶寬和瀏覽器內(nèi)存要求比較高。
- 只能當(dāng)成 Excel 的替代品,數(shù)據(jù)是孤島,不能直連外部數(shù)據(jù)庫(kù)。
這個(gè)方案比較特殊,主要工作量在前端,有大量細(xì)節(jié)體驗(yàn)優(yōu)化,在愛(ài)速搭中沒(méi)實(shí)現(xiàn),后續(xù)可能會(huì)考慮。
2、后端業(yè)務(wù)邏輯的實(shí)現(xiàn)
說(shuō)完了存儲(chǔ),接下來(lái)是第二個(gè)問(wèn)題:如何實(shí)現(xiàn)后端業(yè)務(wù)邏輯?
前面提到過(guò)代碼難以圖形化,這在后端也是一樣的,因此大概有這幾種方案:
- 邏輯圖形化,這個(gè)目前看各個(gè)產(chǎn)品效果都不太理想,看上去還不如代碼易讀。
- 固定行為,主要是對(duì)數(shù)據(jù)存儲(chǔ)提供增刪改查操作。
- 支持 JavaScript 自定義。
- 簡(jiǎn)化 DSL 語(yǔ)言,類(lèi)似 Excel 中的公式。
前面兩種方案之前介紹過(guò)了,這里只討論后面兩種。
后端支持使用 JavaScript 是種常見(jiàn)做法,主要原因是 JavaScript 引擎容易被嵌入,而且啟動(dòng)速度快,了解的人多,比如市值超過(guò) 1200 億美元的 ServiceNow 后端自定義業(yè)務(wù)邏輯就是基于 Rhino 引擎實(shí)現(xiàn)的。
簡(jiǎn)化 DSL 語(yǔ)言的主要是使用場(chǎng)景是做表達(dá)式計(jì)算,比如在流程中的分支流轉(zhuǎn)規(guī)則判斷,需要用戶能自定義表達(dá)式,比如金額大于多少換成總監(jiān)審批,這時(shí)用公式會(huì)比 JavaScript 會(huì)更簡(jiǎn)單,因?yàn)橄到y(tǒng)可以自動(dòng)轉(zhuǎn)換數(shù)據(jù)類(lèi)型,并自動(dòng)處理異步函數(shù)的調(diào)用,目前愛(ài)速搭的流程里有實(shí)現(xiàn),同時(shí)在 amis 里也提供了。
另外除了上面提到這四種,我們?cè)趷?ài)速搭中還設(shè)計(jì)了另一個(gè)方案:執(zhí)行樹(shù),它長(zhǎng)這個(gè)樣子:
左側(cè)是樹(shù)形結(jié)構(gòu),右側(cè)是點(diǎn)中某個(gè)節(jié)點(diǎn)時(shí)的參數(shù)配置,左側(cè)的樹(shù)形結(jié)構(gòu)其實(shí)是直接參考代碼的樹(shù)形結(jié)構(gòu):
- 默認(rèn)從上往下執(zhí)行,但有個(gè)特殊的「并行執(zhí)行」節(jié)點(diǎn)可以并行執(zhí)行。
- 對(duì)于循環(huán)和分支會(huì)創(chuàng)建子節(jié)點(diǎn),并且子節(jié)點(diǎn)可以無(wú)限嵌套,相當(dāng)于代碼里的花括號(hào)。
- 節(jié)點(diǎn)可以折疊,這樣就能先將復(fù)雜的邏輯折疊起來(lái)方便看主流程,這是使用圖模式難以實(shí)現(xiàn)的,在圖里收起后無(wú)法修改其它節(jié)點(diǎn)的位置,導(dǎo)致空出一塊。
為了方便實(shí)現(xiàn)簡(jiǎn)單邏輯處理,我們還增加了 JavaScript 節(jié)點(diǎn)和 SQL 節(jié)點(diǎn)。
但執(zhí)行樹(shù)這個(gè)方案目前的定位是聚合多接口,將多個(gè)后端接口數(shù)據(jù)合并后給前端,類(lèi)似于 BFF 的作用,我們推薦復(fù)雜的后端邏輯還是用 Spring Boot 吧,成熟穩(wěn)定且好招人。
3、流程的實(shí)現(xiàn)
接下來(lái)是第三個(gè)問(wèn)題:如何實(shí)現(xiàn)流程?這是大部分低代碼平臺(tái)標(biāo)配的功能,流程的邏輯不像普通代碼那么抽象,因此適合用可視化編輯。
流程可視化存在很久了,著名的 BPMN 規(guī)范最早版本在 2004 就發(fā)布了,因此大部分產(chǎn)品都會(huì)支持 BPMN 2.0 規(guī)范。
但 BPMN 本質(zhì)上是一種圖形規(guī)范,它的最大作用是給事件、動(dòng)作及分支條件這些抽象概念分配了不同的形體,使得熟悉這個(gè)規(guī)范的用戶有了共同語(yǔ)言。
BPMN 不能解決平臺(tái)鎖定問(wèn)題,在一個(gè)平臺(tái)開(kāi)發(fā)的流程無(wú)法直接遷移到另一個(gè)平臺(tái)。
流程的核心是實(shí)現(xiàn)流程流轉(zhuǎn)引擎,以愛(ài)速搭為例,流程可視化布局后最終存儲(chǔ)的格式是有向圖,比如下面這個(gè)最簡(jiǎn)單流程:
簡(jiǎn)化后的存儲(chǔ)數(shù)據(jù)格式是兩條連線和三個(gè)節(jié)點(diǎn):
{ "lines": [ { "id": "d4ffdd0f6829", "to": "4a055392d2e1", "from": "e19408ecf7e3" }, { "id": "79ccff84860d", "to": "724cd2475bfe", "from": "4a055392d2e1" } ], "nodes": [ { "id": "e19408ecf7e3", "type": "start", "label": "開(kāi)始" }, { "id": "4a055392d2e1", "type": "examine-and-approve-task", "label": "審批節(jié)點(diǎn)" }, { "id": "724cd2475bfe", "type": "end", "label": "結(jié)束" } ]}
流程流轉(zhuǎn)算法的核心就是根據(jù)當(dāng)前狀態(tài)和這個(gè)有向圖,判斷出下個(gè)節(jié)點(diǎn)是什么,然后執(zhí)行那個(gè)節(jié)點(diǎn)的操作。
同時(shí)因?yàn)橹饕嫦虻氖菍徟?,所以還需要處理審批場(chǎng)景特有的邏輯,比如有的審批是全部通過(guò)才算通過(guò),有的審批是只需要一個(gè)人通過(guò)就算通過(guò),還有回退、加簽等功能,并處理各種邊界條件,比如找不到審批人的時(shí)候怎么辦。
雖然目前業(yè)界有開(kāi)源的流程引擎,但這些引擎大多是面向代碼開(kāi)發(fā),不太好改造成平臺(tái)模式,因此在愛(ài)速搭里自己實(shí)現(xiàn)了流程引擎,這樣才能更好定制功能。
— 7 —
低代碼平臺(tái)未來(lái)會(huì)怎樣?
前面提到了各種低代碼的實(shí)現(xiàn)方案細(xì)節(jié),這里拋開(kāi)具體細(xì)節(jié),來(lái)整體討論一下未來(lái)低代碼平臺(tái)會(huì)怎樣。
最開(kāi)始提到過(guò)低代碼唯一不可缺少的功能是可視化編輯,這是低代碼的最大優(yōu)勢(shì),但是低代碼的最大缺陷,因?yàn)榭梢暬y以表達(dá)復(fù)雜的抽象邏輯,因此長(zhǎng)遠(yuǎn)看低代碼并不會(huì)在所有領(lǐng)域取代專業(yè)開(kāi)發(fā),更多是和專業(yè)開(kāi)發(fā)配合來(lái)提升效率。
從技術(shù)方案上看低代碼平臺(tái)主要有兩個(gè)方向:
1、偏向零代碼的方案,它的特點(diǎn)是:
- 易用性強(qiáng)
- 靈活性差
- 適合小公司,客單價(jià)低,但客戶數(shù)多
- 標(biāo)準(zhǔn)化程度高,導(dǎo)致功能都很類(lèi)似,將面臨同質(zhì)化競(jìng)爭(zhēng)
- 產(chǎn)品使用簡(jiǎn)單,客戶支持成本低
2、偏向?qū)I(yè)開(kāi)發(fā)的方案,它的特點(diǎn)是:
- 易用性弱
- 靈活性強(qiáng)
- 適合中大型公司,客戶數(shù)少,但客單價(jià)高
- 標(biāo)準(zhǔn)化程度低,每家都有各自的特點(diǎn)
- 產(chǎn)品使用復(fù)雜,客戶支持成本高
未來(lái)會(huì)怎樣呢?我的想法是:
偏向零代碼方案,因?yàn)楣δ茴?lèi)似支持成本低,可以同時(shí)支持很多用戶,容易出現(xiàn)贏者通吃的情況,但由于 toB 領(lǐng)域發(fā)展速度慢,所以還是有不少機(jī)會(huì)。
可以類(lèi)比 BI 數(shù)據(jù)可視化產(chǎn)品,BI 這個(gè)領(lǐng)域的軟件出現(xiàn)至少 20 年了,比如 Qlik 1994 就發(fā)布了,現(xiàn)在市面上的 BI 軟件在基本功能上都大體相同,但沒(méi)有哪個(gè)產(chǎn)品占據(jù)絕大部分市場(chǎng)份額,我們的 Sugar 產(chǎn)品雖然兩年前才推出,但依然得到了不少優(yōu)質(zhì)客戶,所以只要產(chǎn)品優(yōu)秀就有機(jī)會(huì)。
零代碼產(chǎn)品有好幾種形態(tài),和去年一樣,我更看好「在線 Excel」,因?yàn)榧热皇敲嫦蚍情_(kāi)發(fā)者,類(lèi) Excel 是上手成本最低的方案,而且這一年來(lái)許多「在線 Excel」的產(chǎn)品都加上了低代碼功能,比如 Airtable 的 Interface,在功能上和表單驅(qū)動(dòng)的零代碼越來(lái)越接近了。
而偏向?qū)I(yè)開(kāi)發(fā)的方案,因?yàn)橹С殖杀靖邔?dǎo)致沒(méi)法同時(shí)支持很多客戶,因此更難出現(xiàn)一家獨(dú)大的情況,而偏向研發(fā)會(huì)導(dǎo)致細(xì)節(jié)方案有很多區(qū)別,沒(méi)太多可比性。
以我們的愛(ài)速搭為例,目前產(chǎn)品選擇的方案是偏向?qū)I(yè)開(kāi)發(fā),現(xiàn)有客戶都是知名企業(yè),但也導(dǎo)致了支持成本很高,因?yàn)榭蛻魡?wèn)的問(wèn)題都很專業(yè),大多只有核心研發(fā)才能解答,在功能方面我們的特點(diǎn)是前端使用了我們開(kāi)源的 amis 框架,這個(gè)其它家是不會(huì)提供的。
— 8 —
總結(jié)
前面字太多了,總結(jié)一下主要觀點(diǎn):
- 低代碼都是一種「聲明式」編程,因?yàn)橹挥新暶魇讲拍芸梢暬庉?,而可視化編輯是低代碼唯一不可少的功能。
- 低代碼的優(yōu)缺點(diǎn)其實(shí)來(lái)自于「聲明式」本身。
- 編寫(xiě)代碼是一種抽象思維,因此并不適合可視化,導(dǎo)致低代碼只能面向特定領(lǐng)域,復(fù)雜應(yīng)用需要和專業(yè)開(kāi)發(fā)配合。
- 前端界面的 HTML CSS 可以認(rèn)為是一種低代碼 DSL,因此界面的低代碼比較容易實(shí)現(xiàn),只需要在 HTML CSS 基礎(chǔ)上抽象一層。
- 后端存儲(chǔ)的低代碼有幾種方案,但沒(méi)有哪個(gè)方案是完美的,它們都有各自的優(yōu)缺點(diǎn),這將決定一個(gè)低代碼平臺(tái)的適用范圍,建議在選型時(shí)重點(diǎn)關(guān)注。
— 9 —
在了解原理之后
前面介紹了各種低代碼實(shí)現(xiàn)原理,看起來(lái)都不難,但真正要實(shí)現(xiàn)還需要大量細(xì)節(jié)工作,以我們的 amis 為例,從 2015 年啟動(dòng)至今一直在持續(xù)更新,下面是 amis 開(kāi)源這兩年半來(lái)的提交歷史[2],基本除了春節(jié)和國(guó)慶之外都在提交:
amis 的 contributors 頁(yè)面
但今天 amis 現(xiàn)在仍然有大量功能要做,比如本周將發(fā)布的 1.6.0 版本終于開(kāi)始初步增強(qiáng)移動(dòng)端 UI,下面是新版移動(dòng)端日期選擇:
amis 1.6.0 里的日期選擇
除了無(wú)盡的功能要加,還有許多基礎(chǔ)工作要做,比如組件單元測(cè)試覆蓋率只有 40%,此刻還有 360 issues 要處理,感謝閱讀到這,有什么問(wèn)題歡迎留言交流,我要去處理 issue 了……
作者介紹
吳多益,百度智能云主任架構(gòu)師,超過(guò)14年從業(yè)經(jīng)驗(yàn),參與過(guò)百度貼吧、搜索、空間等產(chǎn)品的研發(fā),曾長(zhǎng)期負(fù)責(zé)百度前端基礎(chǔ)技術(shù)團(tuán)隊(duì),從2015年起開(kāi)始探索低代碼方向,并開(kāi)源了前端低代碼框架amis,目前在百度智能云負(fù)責(zé)低代碼產(chǎn)品愛(ài)速搭的研發(fā)。
相關(guān)鏈接:
- https://developer.salesforce.com/wiki/multi_tenant_architecture
- https://github.com/baidu/amis/graphs/contributors
作者:吳多益
來(lái)源:https://zhuanlan.zhihu.com/p/45134099