從實現原理談談低代碼
我們在低代碼領域探索了很多年,從 2015 開始研發低代碼前端渲染(amis),從 2018 年開研發後端低代碼數據模型,發佈了愛速搭低代碼平臺,這些年調研過了幾乎所有市面上的相關技術和產品,發現雖然每家產品細節都不太一樣,但在底層技術上卻只有少數幾種方案,因此我們認爲不同產品間的最大區別是實現原理,瞭解這些實現原理就能知道各個低代碼平臺的優缺點,所以本文將會介紹目前已知的各種低代碼實現方案,從實現原理角度看低代碼。
1 本文裏的「低代碼」指的是什麼?
在討論各個低代碼方案前,首先要明確「低代碼」究竟是什麼?
這個問題不好直接回答,因爲低代碼是非常寬泛的概念,有很多產品都聲稱自己的低代碼,但我們很容易反過來回答另一個問題:「什麼是低代碼產品唯一不可缺少的功能?」
我認爲這個功能是可視化編輯,因爲非可視化編輯就是代碼編輯,而只有代碼編輯的產品不會被認爲是低代碼,因此可視化編輯是低代碼的必要條件,低代碼其實還有另一個更清晰的叫法是可視化編程。
既然可視化編輯是低代碼的必要條件,那從實現角度看,實現可視化編輯有什麼必要條件?
我認爲可視化編輯的必要條件是「聲明式」代碼,因爲可視化編輯器只支持「聲明式」代碼。
解釋一下什麼是「聲明式」,除了聲明式之外還有另一種代碼模式是「命令式」,我們分別舉兩個例子,如果想繪製一個紅色區塊,用「聲明式」來實現,可以使用 HTML+CSS,類似下面的方法:
<div style="background:red; height:50px"></div>
而換成用「命令式」來實現,可以使用 Canvas API,類似下面的方法:
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
const rectangle = new Path2D();
rectangle.rect(0, 0, 100, 100);
ctx.fill(rectangle);
雖然最終展現效果是一樣的,但這兩種代碼在實現思路上有本質區別:
-
「聲明式」直接描述最終效果,不關心如何實現。
-
「命令式」關注如何實現,明確怎麼一步步達到這個效果。
從可視化編輯器的角度看,它們的最大區別是:
-
「聲明式」可以直接從展現結果反向推導回源碼
-
「命令式」無法做到反向推導
反向推導是編輯器必備功能,比如編輯器裏的常見操作是點選這個紅色區塊,然後修改它的顏色,在這兩種代碼中如何實現?
如果是「聲明式」的 HTML+CSS,可以直接改 style 的 background 值,而基於 Canvas 的命令式代碼則無法實現這個功能,因爲無法從展現找到實現它的代碼,命令式代碼實現同樣效果的可能路徑是無數的,除了前面的示例,下面這段代碼也可以實現一樣的效果:
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(50, 0);
ctx.strokeStyle = '#ff0000';
ctx.lineWidth = 100;
ctx.stroke();
甚至有可能這個顏色是多個字符串加隨機數拼接而成,即便通過靜態分析也找不到來源,從而無法實現可視化修改。
「命令式」代碼無法實現可視化編輯,而可視化編輯是低代碼唯一不可少的功能,所以我們可以得到結論:所有低代碼平臺必然只能採用「聲明式」代碼,這也是爲什麼所有低代碼平臺都會有內置的「DSL」。
既然低代碼都是聲明式,那我們可以通過分析其它「聲明式」語言來了解低代碼的優缺點,其實在專業研發裏,聲明式語言在部分領域已經是主流了:
-
HTML+CSS 是一種頁面展現的 DSL
-
SQL 是一種數據查詢及處理的 DSL
-
K8S 的 yaml 是一種服務部署的 DSL
-
NGINX conf 是一種反向代理的 DSL
上面這些方案目前都是主流,但它們早期並不被看好,比如十幾年前還曾經爭論過到底是用 B/S 還是 C/S 架構,CSS 2 的功能主要是面向圖文排版,並不適合用來構建應用界面。
SQL 最開始也不被看好,下面引用《硅谷簡史》這本書裏的部分文字:
1970 年,IBM 研究員特德 · 科德(Ted Codd)發表了一篇里程碑式的論文,《大型數據庫的系統模型》,介紹了關係數據庫理論。
當時大多數人認爲關係數據庫沒有商業價值,因其速度太慢,不能滿足大規模數據處理或者大量用戶存取數據,雖然關係數據庫理論上很漂亮而且易於使用,但它的速度太慢。
上面兩段其實說的是 Oracle 的發家故事,可以看到當時關係型數據庫並不被看好,因爲大家都覺得慢,這點很好理解,數據庫在查詢前還得先解析 SQL 語法、估算各種查詢的代價、生成執行計劃,存儲也只能使用通用的數據結構,沒法根據不同業務進行定製。
綜合來看這些「聲明式」語言有以下優點:
-
容易上手,因爲描述的是結果,語法可以做得簡單,非研發也能快速上手 HTML 及 SQL。
-
支持可視化編輯,微軟的 HTML 可視化編輯 FrontPage 在 1995 年就有了,現在各種 BI 軟件可以認爲是 SQL 的可視化編輯。
-
容易優化性能,無論是瀏覽器還是數據庫都在不斷優化,比如可以自動改成並行執行,這是命令式語言無法自動實現的。
-
容易移植,容易向下兼容,現在的瀏覽器能輕鬆渲染 30 年前的 HTML,而現在的編譯器沒法編譯 30 年前的瀏覽器引擎代碼。
而這些語言的缺點是:
1、只適合特定領域,命令式的語言比如 JavaScript 可以用在各種領域,但 HTML+CSS 只適合渲染文檔及界面,SQL 只適合做查詢,所有這些語言都。
2、靈活性差,比如 SQL 雖然內置了很多函數,但想只靠它實現業務是遠遠不夠的,有些數據庫還提供了用戶自定義函數功能(UDF),通過代碼來擴展。
3、調試困難,遇到問題時如缺乏工具會難以排查,如果你在 Firefox 出現前開發過頁面就會知道,由於 IE6 沒有開發工具,編寫複雜頁面體驗很差,遇到問題要看很久代碼才發現是某個標籤沒閉合或者 CSS 類名寫錯了。
4、強依賴運行環境,因爲聲明式只描述結果而不關注實現,因此強依賴運行環境,但這也帶來了以下問題:
-
功能取決於運行環境,比如瀏覽器對 CSS 的支持程度決定某個屬性是否有人用,雖然出現了 CSS Houdini 提案,但 Firefox 和 Safari 都不支持,而且上手成本太高,預計以後也不會流行。
-
性能取決於運行環境,比如同一個 SQL 在不同數據庫下性能有很大區別。
-
對使用者是黑盒,使用者難以知道最終實現,就像很少人知道數據庫及瀏覽器的實現細節,完全當成黑盒來使用,一旦遇到性能問題就不知所措。
-
技術鎖定,因爲即便是最開放的 HTML 也無法解決,很多年前許多網站只支持 IE,現在又變成了只支持 Chrome,微軟和 Opera 在掙扎了很多年後也乾脆直接轉向用 Chromium。同樣的即便有 SQL 標準,現在用的 Oracle/SQL Server 應用也沒法輕鬆遷移到 Postgres/MySQL 上。低代碼行業未來也一樣,即便出了標準也解決不了鎖定問題,更有可能是像小程序標準那樣發展緩慢,功能遠落後於微信。
因爲低代碼就是一種聲明式編程,所以這些「聲明式」優缺點,其實就是低代碼的優缺點,瞭解聲明式的歷史及現狀就能更好理解低代碼,因爲:
-
低代碼的各種優點是「聲明式」所帶來的。
-
低代碼被質疑的各種缺點也是「聲明式」所導致的。
2 低代碼的實現方案
說完了聲明式,我們就對低代碼有了全面認識,接下來進入正題,開始介紹已知的各種低代碼實現原理,將會分爲前端和後端兩部分。
3 生成代碼的方案算不算低代碼?
在討論各種方案前,有一種方案比較特別,它雖然也有配置規範或 DSL,甚至有可視化編輯,但最終應用運行是通過生成代碼的方式實現的,不依賴依賴運行環境。
這個方案最大的優點是可以和專業開發整合,因此靈活性強、可以使用原有的開發流程,本質上和專業開發一樣。
但也有如下缺點:
-
強依賴研發,無法做到給非研發使用,因爲後續代碼需要編譯上線。
-
無法持續可視化編輯,因爲代碼無法可視化編輯,生成代碼後只要有修改就沒法再反向還原成低代碼的形式,後續只能代碼編輯。
-
難以實現完全用低代碼開發應用,因爲不能生成太複雜的代碼,使得這種方案一般不包括交互行爲,通常是隻有前端界面支持可視化編輯。
-
無法做到向下兼容,因爲生成的那一瞬間代碼依賴的框架版本就固定了,目前還沒見過哪款前後前端框架做過到完全向下兼容。
因此我認爲生成代碼的方案不算真正的低代碼,本質上它還是一種開發輔助方式,一種高級點的腳手架工具,和大部分 IDE 的生成樣板代碼能力一樣,使用這種方案無法做到持續可視化開發,我還沒見過有人將 HTML+CSS 編譯成 C++ 代碼後二次開發。
4 前端代碼實現原理 - 界面渲染
前面提到前端 HTML+CSS 可以看成一種描述界面的低代碼 DSL,因此前端界面實現低代碼會比較容易,只需要對 HTML+CSS 進行更進一步封裝,這裏以我們的開源項目 amis 爲例進行介紹。
amis 核心原理是將 JSON 轉成自研的 React 組件庫,然後使用 React 進行渲染。
比如下面這段 JSON:
{
"type": "page",
"title": "頁面標題",
"subTitle": "副標題",
"body": {
"type": "form",
"title": "用戶登錄",
"body": [
{
"type": "input-text",
"name": "username",
"label": "用戶名"
}
]
}
}
可以理解 amis 原理就是轉成了下面這樣的 React 組件樹,最終由各個 React 組件庫渲染 HTML:
<Page title="頁面標題" subTitle="副標題">
<Form title="用戶登錄">
<InputText />
</Form>
</Page>
雖然也有低代碼平臺直接使用 HTML+CSS 來實現更靈活的界面控制,但這樣做會導致用起來複雜度高,因爲通常需要多層嵌套 HTML 才能實現一個組件,使用者還必須熟悉 HTML 及 CSS,上手門檻過高,因此大部分低代碼平臺都是類似 amis 那樣使用 JSON 進行簡化。
這裏有個小問題,爲什麼大家幾乎全都使用 JSON?我覺得有兩方面原因:
-
低代碼平臺編輯器幾乎都是基於 Web 實現,JavaScript 可以方便操作 JSON。
-
JSON 可以支持雙向編輯,它的讀取和寫入是一一對應的。
第二點怎麼理解?可以對比一下 YAML,它有引用功能,導致了不好實現雙向編輯,比如下面 YAML 示例:
paths:
root_path: &root
val: /path/to/root/
patha: &a
root_path: *root
轉成了對應的 JSON 數據後,就變成了:
{
"paths": {
"root_path": {
"val": "/path/to/root/"
},
"patha": {
"root_path": {
"val": "/path/to/root/"
}
}
}
}
可以看到之前的引用關係沒了,而是複製出了一部分,如果直接基於這個數據進行可視化編輯,編輯器在修改的時候就只會改一處,也沒法再還原成之前的 YAML 了,要想實現 YAML 可視化編輯就不能先轉成 JSON,而是要對 YAML 解析後的樹形結構進行操作,前端界面實現成本很高,因此目前還沒見過 YAML 的可視化編輯器。
但 JSON 的優點就是它的缺點,因爲它的用途是數據交換而不是人工編寫,導致基於 JSON 構建 DSL 不方便編輯,會有以下 3 個問題:
-
不支持註釋
-
不支持多行字符串
-
語法過於嚴格,比如不支持單引號,不能在最後多寫一個逗號
其中我們對這個註釋問題進行了特殊支持,開發了帶註釋的 JSON 解析,存儲的時候將註釋內嵌到一個特殊的字段中,在代碼顯示的時候將它提取出來變成註釋。
另外許多低代碼平臺會將這個 JSON 配置隱藏,只提供界面編輯,但在 amis 可視化編輯器裏提供了直接修改 JSON 的功能,因爲對於熟悉的開發者,直接編寫 JSON 要比在屬性面板裏找半天效率高,還可以直接將 amis 文檔中的示例粘貼進來快速創建。
amis 開始編輯器裏 JSON 編輯模式
前面提到聲明式容易向下兼容,amis 自己就是最好的例子,在 amis 誕生的 2015 年前端框架和現在有大量區別:
-
Vue 還是 1,現在已經到 3 了,不向下兼容。
-
Angular 還是 1,現在已經 13 了,不向下兼容。
-
React 雖然整體用法沒變,但有大量細節不向下兼容,加上 hooks 推出後,許多第三方庫改成了 hooks 版本,導致舊的類組件形式沒法直接使用。
而 amis 早期的界面配置現在還能繼續使用,不受框架升級影響。
5 交互邏輯的實現
前面說到前端界面低代碼是比較容易,但交互及邏輯處理卻很難低代碼話,目前常見有三種方案:
-
使用圖形化編程
-
固化交互行爲
-
使用 JavaScript
先說第一種圖形化編程,這是非常自然的想法,既然低代碼的關鍵是可視化,那直接使用圖形化的方式編程不就行了?
但我們發現這麼做侷限性很大,本質的原因是「代碼無法可視化」,這點在 35 年前沒有銀彈的論文裏就提到了。
爲什麼代碼無法可視化?首先想一想,可視化的前提條件是什麼?
答案是需要具備空間形體特徵,可視化只能用來展現二維及三維的物體,因爲一維沒什麼意義,四維及以上大部人無法理解,所以如果一個事物沒有形體特徵,它就沒法被可視化。
舉個例子,下面是一段 amis 中 代碼,作用是遍歷 JSON 並調用外部函數進行處理:
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 行代碼,卻包含了循環、調用函數、類型檢測、分支判斷、或操作符、遞歸調用、參數是函數這些抽象概念,這些概念在現實中都找不到形體的,你可以嘗試一下用圖形來表示這段代碼,然後給周圍人看看,我相信任何圖形化的嘗試都會比原本這段代碼更難懂,因爲你需要先通過不同圖形來區分上面的各種概念,其他人得先熟悉這些圖形符號才能看懂,理解成本反而更高了。
代碼的這些抽象思維難以像積木一樣進行拼接,積木拼接這種方式只適合用來實現簡單的邏輯,比如 scratch。
Scratch
而前面圖形化是低代碼唯一不可少的功能,這就使得低代碼不適合做複雜的抽象邏輯處理,這是圖形化缺陷決定的,因此在複雜邏輯處理方面低代碼永遠無法徹底取代專業代碼開發。
但如果是面向特定領域,低代碼平臺可以先將這個領域難以圖形化的算法預置好,讓使用者只需做簡單的處理,比如在 Blender 中將 PBR 算法封裝了,使用的時候只需要調整參數就行。
Blender 中的材質節點編輯
如果真要用節點實現這個算法會非常複雜,大概長這樣:
在複雜邏輯下,圖形中的連線反而變成了視覺干擾,比如下面的例子:
來自 UE4 Blueprints From Hell 裏的一張圖
想象一下假設客戶做出了上面這個圖的複雜邏輯,然後找你排查問題,而客戶的程序是部署在內網的,沒法導出,只能通過微信拍屏幕給你看……
因此我認爲圖形化不適合用來實現業務邏輯,只適合用來做更高層次流程控制,比如審批流,審批流是現實真實存在的,沒有複雜的抽象邏輯,因此適合圖形化。
在愛速搭中,我們除了實現流程功能,還實現了樹形結構的 API 編排功能,它本質上是模仿代碼結構,將會在後面進行介紹。
說完了圖形化編程,接下來談第二種方案:固化交互行爲,這是不少低代碼平臺的做法,我們還是以 amis 爲例進行介紹。
amis 將常用的交互行爲固化並做成了配置,比如彈框是下面的配置:
{
"label": "彈框",
"type": "button",
"actionType": "dialog",
"dialog": {
"title": "彈框",
"body": "這是個簡單的彈框。"
}
}
除了彈框之外還有發起請求、打開鏈接、刷新其它組件等,使用固化交互行爲有下面兩個優點:
-
可以可視化編輯
-
整合度高,比如彈框裏可以繼續使用 amis 配置,通過嵌套實現複雜的交互邏輯
但這個方案最大的缺點是靈活性受限,只能使用 amis 內置的行爲。
要實現更靈活的控制,還是得支持第三個方案:JavaScript,目前有的低代碼平臺只在界面編輯提供可視化編輯,一旦涉及到交互就得寫 JavaScript,這和 30 年前的 C++ Builder 本質上是一樣的:
RDA Studio 11 的界面編輯
但第三個方案的最大缺點就是無法可視化編輯,因此不算是低代碼。
6 後端低代碼的方案
前端討論完了,接下來是後端部分,後端低代碼需要解決以下三個問題:
1、如何自定義數據存儲?
低代碼平臺需要支持用戶存儲自定義數據,因爲每個應用所需的字段是不一樣的。
自定義數據存儲是後端低代碼最重要的功能,使用什麼方案將直接影響這個產品的適用範圍,目前我們已知有 5 種方案,每種都有自己的優缺點。
存儲的實現方案 1:直接使用關係型數據庫
這個方案的原理是將數據模型的可視化操作轉成數據庫 DDL,比如添加了一個字段,系統會自動生成表結構變更語句:
ALTER TABLE 'blog' ADD 'title' varchar(255) NULL;
這個方案的優點是:
-
所有方案裏唯一支持直連外部數據庫,可以對接已有系統。
-
性能高和靈活性強,因爲可以使用高級 SQL。
-
開發人員容易理解,因爲和專業開發是一樣的。
但它的缺點是:
-
需要賬號有創建用戶及 DDL 權限,如果有安全漏洞會造成嚴重後果,有些公司內部線上帳號沒有這個權限,導致無法實現自動化變更。
-
DDL 有很多問題無解,比如在有數據的情況下,就不能再添加一個沒有默認值的非 NULL 字段。
-
DDL 執行時會影響線上性能,比如 MySQL 5.6 之前的版本在一個大數據量的表中添加索引字段會鎖整個表的寫入(但也有數據庫不受影響,比如 TiDB、OceanBase 支持在線表結構變更,不會阻塞讀寫)。
-
部分數據庫不支持 DDL 事務,比如 MySQL 8 之前的版本,導致一旦在執行過程中出錯將無法恢復。
-
實現成本較高,需要實現「動態實體」功能,如果要支持不同數據庫還得支持各種方言。
儘管這個方案有很多缺點,但它的優點也很突出,因此愛速搭裏實現了這個方案,因爲我們覺得能連已有數據庫是非常重要的,其它方案都只適合用來做新項目,這個方案使得可以逐步將已有項目低代碼化,不需要做數據遷移。
愛速搭裏的數據庫模型
實現這個方案的關鍵是「動態實體」,在專業開發中實體(Entity)定義都是靜態的,以 Java 爲例,它從 2006 年開始就有專門的 JPA 規範,但這個規範是定義基於 Java 代碼註解,使得需要經過編譯才能使用,畢竟它的定位是面向專業開發,只有寫在代碼裏才能支持代碼提示,提升開發體驗。
而低代碼平臺中需要將這個實體定義抽象成配置,在運行時動態生成實體,如果使用 JPA 就需要生成 Java 代碼後進行編譯,這很容易出錯,不太適合低代碼平臺,所以使用這個方案需要實現「動態實體」功能,是整個方案最大難點。
存儲的實現方案 2:使用文檔型數據庫
文檔型數據庫不需要預先定義表結構,因此它很適合用來存儲用戶自定義數據,這個方案實現起來比較簡單,以 MongoDB 爲例,可以這樣做:
-
用戶創建一個自定義表的時候,系統就自動創建一個 collection,所有這個表的數據都存在這個 collection 裏。
-
用戶新增字段的時候,就隨機分配一個 fileId,後續對這個字段的操作都自動映射到這個 fileId 上,用 fileId 的好處是用戶重命名字段後還能查找之前的數據,因爲所有數據查詢底層都基於這個 fileId。
-
查詢的時候先找到對應的 collection,再通過 meta 信息查詢字段對應的 fileId,使用這個 fileId 來獲取數據。
這個方案的優點是實現簡單,用戶體驗可以做得更好,是目前大部分零代碼平臺的選擇,使用這個方案的產品也很好識別,只要看一下它的私有部署文檔,如果有要求裝 MongoDB 就肯定是。
但這個方案也有顯著缺點:
-
無法支持外部數據庫,數據是孤島,外部數據接入只能通過導入的方式。
-
MongoDB 在國內發展緩慢,接受度依然很低,目前還沒聽說有哪家大公司裏最重要的數據存在 MongoDB 裏,一方面有歷史原因,另一方面不少數據庫都開始支持 JSON 字段,已經能取代大部分必須用 MongoDB 的場景了。
-
不支持高級 SQL 查詢。
你可能會問,現在 MySQL、Postgres 等數據庫都支持 JSON 字段類型了,是否可以用這個字段來實現低代碼?
答案是不太行,只適合數據量不大的場景,雖然 JSON 字段可以用來存用戶自定義數據,但無法創建字段索引,比如在 MySQL 要想給 JSON 創建索引,還是得創建一個特殊的字段,這又需要 DDL 權限了,沒有索引會導致這個方案無法支持大量數據查詢。
在愛速搭中我們也實現這個方案,目前是基於 MySQL JSON 字段,後續可能也會支持存儲使用 MongoDB,目前它的使用場景是流程執行過程中的數據存儲,因此數據量不會很大,我們希望流程功能用起來可以更簡單些。
它的最大特點是界面編輯和數據存儲是統一的,當你拖入文本框到頁面後就會自動創建對應的字段,不需要先創建數據模型再創建界面,因此用起來更簡單。
愛速搭裏的表單模型
存儲的實現方案 3:使用行代替列
這是很多可擴展平臺裏使用的技術,比較典型的是 WordPress,它的擴展性很強,裝個擴展就能變成電商網站。而整個 WordPress 只有 12 個表,它是怎麼做到的?方法是靠各種 meta 表,比如用於擴展文章的 wp_postmeta 表結構如下:
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;
其中的關鍵就是 meta_key 和 meta_value 這兩個字段,相當於將數據庫當 KV 存儲用了,因此可以任意擴展字段名及值。
這個方案的優點是實現簡單,但缺點也很明顯:
-
查詢性能低,如果有 10 個字段就要查 10 行。
-
無法支持 SQL 高級查詢,因爲數據是按行存的。
這個方案主要用於成熟項目的擴展,比如在 CRM 產品中允許用戶擴展字段,但因爲性能較低,並不適合通用低代碼平臺。
存儲的實現方案 4:元信息 + 寬表
早期數據庫不支持 JSON 字段的時候,有些開發者會預留幾個列來給用戶擴展自定義屬性,比如在表裏加上 ext1、ext2、ext3 字段,讓用戶可以存 3 個定製數據,基於這個原理我們可以進一步擴展,通過預留大量列來實現應用自定義存儲。
這個方案最早出現在 force.com,具體細節可以閱讀它架構說明文檔 [1]。
實現它有兩個關鍵點:元數據、預留列,這裏簡單說明一下原理,首先系統預先創建一個 500 列的表,比如就叫 data:
也可以創建更多,但注意有的數據庫對列的數量有限制,比如 MySQL 最多是 4096 列。
上面的 data 表裏主要有 4 類字段:
-
tenant_id 是租戶 id,用於隔離不同租戶
-
table_id 是自定義表的 id
-
uuid 是具體這一行數據的 id
-
後面的 value0 到 value500 都是預留的列,用於存儲實際數據,一般使用變長字符串類型
當用戶給這個表新增一個字段的時候,怎麼知道這個字段放哪?這就需要另一個用於描述字段信息的元數據表,比如增加一個「標題」字段時,使用另一個 table_fields 表來描述這個字段的信息,示例如下:
在這個 table_fields 表裏:
-
tenant_id 和 table_id 和前面一樣。
-
field_id 對應的是給這個「標題」字段分配的 id。
-
value_index 對應前面那個 data 表裏預覽列的位置,比如這個值是 0,就意味着 value0 列被分配給了這個「標題」字段。
-
name 用來存名稱,type 用來標識類型,這樣查詢和寫入數據的時候,首先從這裏查詢 value_index 是什麼,然後再去前面那個預留列的表中查詢對應列的值。
最終在實際查詢的時候需要根據元數據表做一下轉換,比如 select 標題 from blog 要轉成 select value0 from data where tenal_id = 1 and table_id = 1。
要完全實現這個方案還有很多細節問題得解決,由於篇幅原因這裏不詳細介紹,感興趣可以閱讀前面提到的 force.com 技術白皮書,這裏列舉其中幾個問題:
-
因爲存儲只能是字符串,所以對於日期、數字等其他類型,因此讀取的時候需要根據類型使用數據庫裏的函數進行轉換,比如 STR_TO_DATE。
-
需要單獨處理唯一性功能,因爲這個數據表是所有租戶共用的,沒法設置表級別的唯一性索引,這時就需要新建一個表來單獨做,壞處是數據多份容易產生不一致,需要在所有更新操作都加事務。
-
需要單獨處理索引功能,同樣是因爲字段是字符串,因此沒法直接在 data 表裏加索引,如果數據存儲的是數字,排序就是錯的,爲了解決這個問題需要另外創建一個一個包含常見字段的索引表,數據更新的時候。
-
自增字段需要自己實現。
-
元數據信息需要緩存,不然每次查詢前都需要先查詢元數據信息,然後再去查詢真正的數據。
這個方案比前面幾個方案的優點是:
-
比起第一種原生數據庫表方案,它不需要 DDL 操作,不容易出問題,跟適合 SaaS 產品。
-
比起第二種文檔型數據庫方案,它的存儲使用更爲成熟的關係型數據庫,相關的運維工具多。
-
比起第三種行代替列方案,它的查詢性能好,因爲是讀取一行數據。
但它也有許多缺點:
-
無法支持 SQL 所有功能,比如 force.com 的 SOQL 無法 select *、沒有視圖、不支持寫入和更新數據,通過這個特點就能識別出使用這個方案的產品,這類產品雖然看起來很像在用傳統數據庫,也支持使用 SQL,但這個 SQL 一定是受限的。
-
數據泄露風險高,因爲所有租戶的數據都存在一張表裏,而數據庫都不支持行級別權限的賬號,所以意味着所有租戶其實共享一個數據庫賬號,只要有某個功能的查詢漏了加租戶過濾就能查到所有租戶數據。相比之下前面提到的原生表及文檔型數據庫方案都能直接使用數據庫自帶的賬號進行有效隔離。
-
一些數據庫高級字段難以支持,比如座標數據、二進制類型等,只能用單獨的表存,導致了查詢開銷。
-
整體實現成本高,其中很多細節需要處理好,比如保證數據一致性,因爲爲了實現唯一性、索引等功能需要拷貝數據,更新的時候要同時更新。
愛速搭中沒有實現這個方案,我們曾經考慮過但後來放棄了,我認爲這個方案雖然很適合 SaaS 類的低代碼產品,但它的用戶定位比較尷尬,一方面是有一定複雜度導致不能做到零代碼平臺那樣的易用性,另一方面是有不少限制導致專業研發不喜歡,所以最終是兩邊都不討好,這種產品想做成需要依賴廣泛使用的平臺。
因此 Salesforce 才能做成,而國內類似情況我能想到的唯一成功案例是微信小程序,儘管有很多限制,但因爲微信廣泛使用,所以才成功了,如果是一個獨立的小程序平臺肯定沒人用。
這裏說一段小歷史,在十幾年前,當時雲計算領域最先推出的是谷歌 2008 年發佈的 App Engine,這是谷歌的第一個雲產品,而當時類似 AWS EC2 那樣的虛機產品國內都還沒有,畢竟 KVM 也纔剛發佈。
如果你當時問雲計算的專家,雲計算的未來是 App Engine 還是虛擬機,我聽到不少專家的回答是 App Engine,因爲這看起來更有前景,你只需要寫代碼,不用操心運維,平臺會自動水平擴展,這纔是雲該有的樣子,當時國內不少公司都推出了類似產品。
但 13 年後的今天,國內 App Engine 平臺幾乎都關閉了,而虛機不但是主流,還更進一步出現了物理機產品。這個元信息方案給我的感覺和當年 App Engine 很像,看上去能完成增刪改查的簡單應用,但如果深入就發現缺少很多功高級功能,導致兩邊不討好:
-
技術薄弱的開發者不會用,比如因爲 App Engine 是分佈式部署,導致上傳文件不能放本地,必須改成對象存儲,所以沒法直接用 WordPress 沒法用,對於小站長來說還不如用虛擬主機。
-
對於有技術實力的開發者,又會覺得平臺能力受限,不利於自己後續發展,比如谷歌的 App Engine 直到 2019 年才支持 WebSocket。
整體而言我不看好這個方案在國內的發展。
存儲的實現方案 5:使用單文件
這個方案目前只在「仿 Excel」的零代碼平臺中見過,它和 Excel 類似,數據全都放一個文件裏,查詢過濾完全靠前端,優點是:
-
實現簡單,部署成本低,因爲表的存儲就是單文件。
-
容錯性強,數據類型都是靠前端處理的,不會出現存數據庫導致。
缺點是:
-
如果要支持行列級別權限校驗,還得在後端實現一遍過濾,而每次都加載一個巨大的 JSON 文件對服務器內存有較高要求。
-
難以支持事務操作,尤其是支持行級別的操作。
-
目前看十萬級別數據處理可以只靠前端,但再大量的數據就不合適了,一次性加載太多對帶寬和瀏覽器內存要求比較高。
-
只能當成 Excel 的替代品,數據是孤島,不能直連外部數據庫。
這個方案比較特殊,主要工作量在前端,有大量細節體驗優化,在愛速搭中沒實現,後續可能會考慮。
2、後端業務邏輯的實現
說完了存儲,接下來是第二個問題:如何實現後端業務邏輯?
前面提到過代碼難以圖形化,這在後端也是一樣的,因此大概有這幾種方案:
-
邏輯圖形化,這個目前看各個產品效果都不太理想,看上去還不如代碼易讀。
-
固定行爲,主要是對數據存儲提供增刪改查操作。
-
支持 JavaScript 自定義。
-
簡化 DSL 語言,類似 Excel 中的公式。
前面兩種方案之前介紹過了,這裏只討論後面兩種。
後端支持使用 JavaScript 是種常見做法,主要原因是 JavaScript 引擎容易被嵌入,而且啓動速度快,瞭解的人多,比如市值超過 1200 億美元的 ServiceNow 後端自定義業務邏輯就是基於 Rhino 引擎實現的。
簡化 DSL 語言的主要是使用場景是做表達式計算,比如在流程中的分支流轉規則判斷,需要用戶能自定義表達式,比如金額大於多少換成總監審批,這時用公式會比 JavaScript 會更簡單,因爲系統可以自動轉換數據類型,並自動處理異步函數的調用,目前愛速搭的流程裏有實現,同時在 amis 裏也提供了。
另外除了上面提到這四種,我們在愛速搭中還設計了另一個方案:執行樹,它長這個樣子:
左側是樹形結構,右側是點中某個節點時的參數配置,左側的樹形結構其實是直接參考代碼的樹形結構:
-
默認從上往下執行,但有個特殊的「並行執行」節點可以並行執行。
-
對於循環和分支會創建子節點,並且子節點可以無限嵌套,相當於代碼裏的花括號。
-
節點可以摺疊,這樣就能先將複雜的邏輯摺疊起來方便看主流程,這是使用圖模式難以實現的,在圖裏收起後無法修改其它節點的位置,導致空出一塊。
爲了方便實現簡單邏輯處理,我們還增加了 JavaScript 節點和 SQL 節點。
但執行樹這個方案目前的定位是聚合多接口,將多個後端接口數據合併後給前端,類似於 BFF 的作用,我們推薦複雜的後端邏輯還是用 Spring Boot 吧,成熟穩定且好招人。
3、流程的實現
接下來是第三個問題:如何實現流程?這是大部分低代碼平臺標配的功能,流程的邏輯不像普通代碼那麼抽象,因此適合用可視化編輯。
流程可視化存在很久了,著名的 BPMN 規範最早版本在 2004 就發佈了,因此大部分產品都會支持 BPMN 2.0 規範。
但 BPMN 本質上是一種圖形規範,它的最大作用是給事件、動作及分支條件這些抽象概念分配了不同的形體,使得熟悉這個規範的用戶有了共同語言。
BPMN 不能解決平臺鎖定問題,在一個平臺開發的流程無法直接遷移到另一個平臺。
流程的核心是實現流程流轉引擎,以愛速搭爲例,流程可視化佈局後最終存儲的格式是有向圖,比如下面這個最簡單流程:
簡化後的存儲數據格式是兩條連線和三個節點:
{
"lines": [
{
"id": "d4ffdd0f6829",
"to": "4a055392d2e1",
"from": "e19408ecf7e3"
},
{
"id": "79ccff84860d",
"to": "724cd2475bfe",
"from": "4a055392d2e1"
}
],
"nodes": [
{
"id": "e19408ecf7e3",
"type": "start",
"label": "開始"
},
{
"id": "4a055392d2e1",
"type": "examine-and-approve-task",
"label": "審批節點"
},
{
"id": "724cd2475bfe",
"type": "end",
"label": "結束"
}
]
}
流程流轉算法的核心就是根據當前狀態和這個有向圖,判斷出下個節點是什麼,然後執行那個節點的操作。
同時因爲主要面向的是審批流,所以還需要處理審批場景特有的邏輯,比如有的審批是全部通過纔算通過,有的審批是隻需要一個人通過就算通過,還有回退、加簽等功能,並處理各種邊界條件,比如找不到審批人的時候怎麼辦。
雖然目前業界有開源的流程引擎,但這些引擎大多是面向代碼開發,不太好改造成平臺模式,因此在愛速搭裏自己實現了流程引擎,這樣才能更好定製功能。
7 低代碼平臺未來會怎樣?
前面提到了各種低代碼的實現方案細節,這裏拋開具體細節,來整體討論一下未來低代碼平臺會怎樣。
最開始提到過低代碼唯一不可缺少的功能是可視化編輯,這是低代碼的最大優勢,但是低代碼的最大缺陷,因爲可視化難以表達複雜的抽象邏輯,因此長遠看低代碼並不會在所有領域取代專業開發,更多是和專業開發配合來提升效率。
從技術方案上看低代碼平臺主要有兩個方向:
1、偏向零代碼的方案,它的特點是:
-
易用性強
-
靈活性差
-
適合小公司,客單價低,但客戶數多
-
標準化程度高,導致功能都很類似,將面臨同質化競爭
-
產品使用簡單,客戶支持成本低
2、偏向專業開發的方案,它的特點是:
-
易用性弱
-
靈活性強
-
適合中大型公司,客戶數少,但客單價高
-
標準化程度低,每家都有各自的特點
-
產品使用複雜,客戶支持成本高
未來會怎樣呢?我的想法是:
偏向零代碼方案,因爲功能類似支持成本低,可以同時支持很多用戶,容易出現贏者通喫的情況,但由於 toB 領域發展速度慢,所以還是有不少機會。
可以類比 BI 數據可視化產品,BI 這個領域的軟件出現至少 20 年了,比如 Qlik 1994 就發佈了,現在市面上的 BI 軟件在基本功能上都大體相同,但沒有哪個產品佔據絕大部分市場份額,我們的 Sugar 產品雖然兩年前才推出,但依然得到了不少優質客戶,所以只要產品優秀就有機會。
零代碼產品有好幾種形態,和去年一樣,我更看好「在線 Excel」,因爲既然是面向非開發者,類 Excel 是上手成本最低的方案,而且這一年來許多「在線 Excel」的產品都加上了低代碼功能,比如 Airtable 的 Interface,在功能上和表單驅動的零代碼越來越接近了。
而偏向專業開發的方案,因爲支持成本高導致沒法同時支持很多客戶,因此更難出現一家獨大的情況,而偏向研發會導致細節方案有很多區別,沒太多可比性。
以我們的愛速搭爲例,目前產品選擇的方案是偏向專業開發,現有客戶都是知名企業,但也導致了支持成本很高,因爲客戶問的問題都很專業,大多隻有核心研發才能解答,在功能方面我們的特點是前端使用了我們開源的 amis 框架,這個其它家是不會提供的。
8 總結
前面字太多了,總結一下主要觀點:
-
低代碼都是一種「聲明式」編程,因爲只有聲明式才能可視化編輯,而可視化編輯是低代碼唯一不可少的功能。
-
低代碼的優缺點其實來自於「聲明式」本身。
-
編寫代碼是一種抽象思維,因此並不適合可視化,導致低代碼只能面向特定領域,複雜應用需要和專業開發配合。
-
前端界面的 HTML+CSS 可以認爲是一種低代碼 DSL,因此界面的低代碼比較容易實現,只需要在 HTML+CSS 基礎上抽象一層。
-
後端存儲的低代碼有幾種方案,但沒有哪個方案是完美的,它們都有各自的優缺點,這將決定一個低代碼平臺的適用範圍,建議在選型時重點關注。
9 在瞭解原理之後
前面介紹了各種低代碼實現原理,看起來都不難,但真正要實現還需要大量細節工作,以我們的 amis 爲例,從 2015 年啓動至今一直在持續更新,下面是 amis 開源這兩年半來的提交歷史 [2],基本除了春節和國慶之外都在提交:
amis 的 contributors 頁面
但今天 amis 現在仍然有大量功能要做,比如本週將發佈的 1.6.0 版本終於開始初步增強移動端 UI,下面是新版移動端日期選擇:
amis 1.6.0 裏的日期選擇
除了無盡的功能要加,還有許多基礎工作要做,比如組件單元測試覆蓋率只有 40%,此刻還有 360+ issues 要處理,感謝閱讀到這,有什麼問題歡迎留言交流,我要去處理 issue 了……
作者介紹
吳多益,百度智能雲主任架構師,超過 14 年從業經驗,參與過百度貼吧、搜索、空間等產品的研發,曾長期負責百度前端基礎技術團隊,從 2015 年起開始探索低代碼方向,並開源了前端低代碼框架 amis,目前在百度智能雲負責低代碼產品愛速搭的研發。
相關鏈接:
-
https://developer.salesforce.com/wiki/multi_tenant_architecture
-
https://github.com/baidu/amis/graphs/contributors
作者:吳多益
來源:https://zhuanlan.zhihu.com/p/451340998
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Jihxe4Gvvv_u-h9HG81s_A