前言:這篇文章原本是去年 12 月份 GMTC全球大前端技術大會(深圳站)分享《低代碼平台架構深度剖析》的總結文章,由於某些原因一直鴿到了現在。
今年五月份,本人有幸再次受邀成為 GMTC 北京站低代碼專題的出品人,於是趕在北京站開幕之前將這篇拖延了很久的文章正式產出,為北京站的低代碼專題拋磚引玉。
距離 https://github.com/baidu/amis 第一行代碼的提交已有七年,距其開源也過去了將近三年的時間,amis 可視化搭建平台早已成為百度內部使用最廣的低代碼平台,在此基礎上孵化出的對外商業版本「愛速搭」在金融、政企、工業等行業也有了自己的首批客戶。
Forrest 在 2014 年正式定義了「低代碼」這個名詞。而早在這個名詞出現之前,我們就已經開始着力於相關領域的研究,可以說經歷過「低代碼」在國內發展的全部時期。作為「低代碼」領域的歷史見證者和踐行者,我們探索過最初的「無人區」也走過不少彎路,並取得了一些階段性的沉澱成果。
低代碼化是現今各企業技術部門調研和踐行的熱門方向。低代碼能夠解決哪些問題?低代碼是實際能解決問題的技術革新,還是資本炒作起來的概念?市面上的意見五花八門,有質疑,有看好,也有觀望。而對於低代碼是什麼,低代碼平台應當是什麼樣,我們也能看到許多不同的解釋和看法。
要從這些觀點的海洋中找到對於企業團隊而言正確的決策,需要對「低代碼平台」的全貌有清晰的認識。故而在這篇文章中,作者將儘可能以不帶任何個人傾向的方式,為各位讀者客觀鋪陳一個相對成熟的低代碼平台的架構。對於企業而言,如果打算採購低代碼平台,可以將本文作為平台能力逐項評估的參照,如果打算自研低代碼平台,也可將本文作為項目設計的借鑑。
接下來的內容會基於百度智能雲低代碼方向已對外披露的兩個主要項目,這兩個項目分別是:
開源項目 amis(https://github.com/baidu/amis)
低代碼平台愛速搭(https://aisuda.bce.baidu.com/)
其中 amis 是低代碼前端框架,而愛速搭平台則在包含了 amis 的前提下拓展了數據模型、API 編排等後端能力。
本文將以 平台概覽 - amis 框架介紹和細節展開 - 愛速搭可視化編輯器概述 - 後端服務通覽 的脈絡展開,為各位讀者揭示一個在生產環境成熟運轉的低代碼平台的全貌。
首先我們通過一張圖來了解百度愛速搭平台的全貌。如圖所示,前端用戶感知最多的「頁面拖拽編輯」部分,主要由 amis 和 amis-editor 來承擔;而「後端業務」則包括一個低代碼平台必不可少的 API Proxy、權限、數據模型、流程引擎等;「愛速搭平台功能」指的是頁面管理列表、數據模型列表等平台常規能力。
接下來我們將挑選一些重要的模塊,為大家逐一剖析這些構成低代碼平台的關鍵元素。
愛速搭的整個歷史要從低代碼框架 amis 的誕生說起,amis 框架(https://github.com/baidu/amis)是百度愛速搭低代碼平台整個前端能力的基礎設施,它承載了「將 JSON 配置轉換成對應的 React 組件並在頁面上渲染出來」的基礎能力。
在低代碼平台的發展完善過程中,最初提供的能力通常是前端頁面的拖拽搭建。在現今低代碼平台開發的通常過程中,低代碼平台的開發首先建設的必然是類似 amis 這樣能夠將 JSON 轉為組件渲染的前端框架。而平台能夠支持「多複雜」的前端頁面搭建,則取決於這個前端框架的設計細節。
下面我們來具體聊一聊 amis 的整個設計初衷和一些重要細節。
一個技術項目的產生,必然是為了解決某些客觀存在的核心問題,然後基於這個核心去不斷地完善細節以覆蓋更多場景。在深入細節之前,我們首先花一點時間來了解 amis 框架最初想要解決的核心問題。
下面以一個前端最常見的例子來說明這個「核心問題」。比方說我們要實現一個最簡單的表單組件數據聯動效果,如下圖:
如果用傳統的前端組件來實現,開發者需要關注的點可能包括:
如何進行異步請求發送
如何進行狀態管理
組件間的數據共享和界面狀態更新
.....
而在前端龐大的生態圈中,上述的每個點可能都有非常多的「可選方案」,開發者需要逐一去調研並甄選出每個環節的「最適」方案,並將其組合起來形成一套團隊內部的研發技術棧,同時還需要分出精力來時刻關注技術棧中每個組成因子的升級適配問題。
而在 amis 中,我們將技術棧的更新升級細節封裝到了「低代碼框架」本身的更新升級範圍中,無論前端技術棧如何升級變遷,對於 amis 框架的用戶而言,實現「表單組件數據聯動效果」都只需要通過下面的配置方式來實現:
JSON 配置:
{ "type": "page", "body": { "title": "", "type": "form", "mode": "horizontal", "body": [ { "label": "選項 1", "type": "radios", "name": "a", "inline": true, "options": [ { "label": "選項 A", "value": 1 }, { "label": "選項 B", "value": 2 }, { "label": "選項 C", "value": 3 } ] }, { "label": "選項 2", "type": "select", "size": "sm", "name": "b", "source": "/amis/api/mock2/options/level2?a=${a}", "description": "切換<code>選項 1</code>的值,會觸發<code>選項 2</code>的<code>source</code> 接口重新拉取" } ], "actions": [] }}在這裡,最重要的是 select 組件配置中的"source": "/amis/api/mock2/options/level2?a=${a}"這一行,通過 a=${a}這樣的語法,將下拉框的接口請求和 name 為 a 的 radios 組件進行「關聯」,當 radio a 的值發生 change 的時候,就會觸發 select 組件數據接口的請求,並將當前 select a 的取值以 url 參數的形式隨接口請求發送給後端。
而常見的基礎組件、CRUD 列表等就不在此一一贅述,有興趣的讀者可以自行前往閱讀 amis 官方文檔。
一言以蔽之,「低代碼框架」的組件體系,本質上都是在通常的「UI 組件」之上增加了「通用業務邏輯」的實現,將一些常用的業務邏輯提取並固化到組件層面,然後提供相應的配置項,讓用戶得以通過配置的方式來復用這些「固化」的業務邏輯。
故而低代碼組件體系,及其配套的低代碼可視化編輯器 - 低代碼平台,其價值都在於能夠提供不斷累積的業務邏輯。
從長遠來看,不同領域都會孵化出貼合自身需要的「低代碼」組件和框架體系,而像 amis 這樣應用於企業中後台業務場景的類型,就目前而言是認知程度和普及率最高的。
amis 的核心能力是「將 JSON 配置轉換成對應的 React 組件並在頁面上渲染出來」。乍一看這非常容易實現的,但是將低代碼框架放到生產實踐中,就會發現各種各樣需要處理的細節問題。這裡挑選了幾個從各種業務場景中提煉出來的處理方案,和各位讀者共享,細節上的處理,是 amis 框架「能夠承接更複雜頁面設計」的關鍵所在。
這裡同樣以一個簡單的例子來展示在 amis 將 JSON 配置轉換成 React 組件渲染的實際效果(來自 amis 官網「概念 - 配置與組件」 https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/schema),在這個例子中定義了一個簡單的組件,它的 amis 配置是這樣的。
{ "type": "page", "body": { "type": "tpl", "tpl": "Hello World!" }}其渲染結果如下,這段配置描述的是在一個頁面容器中以 tpl 組件的形式渲染一段 「Hello World!」 文本。
那麼這個最簡單的例子,是如何從 JSON 配置被轉化為 React 組件並渲染到頁面上的呢?在 amis 的官方文檔中對此有所說明 https://aisuda.bce.baidu.com/amis/zh-CN/docs/extend/internal,簡單來說就是先通過 json 的 type 找到對應的 Component,然後把其他屬性作為 props 傳遞過去完成渲染。
render 方法是實現配置到組件轉換的關鍵方法。在 amis 框架的快速開始文檔 https://aisuda.bce.baidu.com/amis/zh-CN/docs/start/getting-started#react 中,可以看到通過調用框架提供的 render 方法,能夠將符合要求的 JSON 格式配置轉化為相應的 React 組件輸出。
(註:這裡需要插入說明一點,amis 框架下的全部組件狀態管理是基於 Mobx https://mobx.js.org/README.html 和 mobx-state-tree 來實現的,本文後續中提到的「store」指的都是 MST 中的數據 store)。
下圖展示了 render 方法處理組件 schema(JSON 配置)的大致過程,當然整個方法包含了許多細節上的處理,這裡僅展示 schema 到組件的主流程部分)。
這裡的 render 方法是 amis 核心功能的「入口」,那麼它具體做了什麼事情呢?
在已註冊的組件池中找到組件並將其依次渲染到頁面上
數據 store 的創建
這裡的 SchemaRenderer 在 amis 代碼中是一個 React 組件,其部分代碼如下,其中 resolveRenderer 這行代碼是根據 schema 查找組件的關鍵操作。
amis 的組件分為容器型組件和非容器型組件,容器型組件的 body 中可包含非容器型組件及多層嵌套容器型組件。在 amis 的官方文檔中闡述了組件樹的概念(https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/schema)。
利用這種樹形結構的設計,amis 可以實現複雜頁面製作,下面這個例子展示了一個利用容器組件嵌套來實現分欄布局的報表頁面。
嵌套組件的渲染原理和單層組件類似,其區別在於需要遞歸地獲取容器組件 body 的內容並逐層渲染到嵌套的內層。
數據是前端頁面構成的核心部分。無論是傳統原生模式還是 MVVM 框架,抑或是低代碼模式,對數據變更(即狀態)的管理都是前端開發的重要課題。
在 amis 中,頁面數據使用「數據域」和「數據鏈」來進行管理(https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/datascope-and-datachain)這是使得 amis 能夠支持複雜動態頁面的一個重要設計。
這裡同樣用一個直觀的例子來說明「數據域」和「數據鏈」在實際場景中是如何生效的。
在下圖所示的頁面中配置了一個頁面初始化接口,該接口在返回結果的 data 域中包含了兩個字段,「date」和「title」。
在頁面中,我們可以使用 ${title}這樣的模板語法或類似 this.title === xxx 這樣的表達式語法來獲取到當前頁面 data 域下的值,模板語法主要用於展示,表達式語法一般用於顯隱條件判斷等。
在上面的例子中,我們用 ${}語法來獲取到了 data 域下的值並在頁面中渲染出來。
在一個 amis/ 愛速搭的頁面中,「數據」來源主要包括:
頁面常量,如當前用戶信息等,僅限愛速搭平台,由後端服務在返回頁面配置時進行統一替換
地址欄參數
運行態:
編輯態:
在組件配置 「data」 域中配置的初始靜態數據
接口返回的數據,最常用的方式,包括表單提交後接口返回的數據(如保存結果等)。在本節開始的例子中已有展示說明,存在同名字段的情況下,「後來」 的數據會覆蓋 「先來」 的數據。
這些不同來源的數據被統一歸併到數據域中以樹狀的結構進行管理。
上面我們說到,數據域是以樹狀結構進行管理的,其繼承和覆蓋規則的設計如下:
當頁面中出現類似 ${}的語法時:
首先會先嘗試在當前組件的數據域中尋找變量,當成功找到變量時,通過數據映射完成渲染,停止尋找過程;
在當前數據域中沒有找到變量時,則向上尋找,在父組件的數據域中,重複步驟 1 和 2;
一直尋找,直到頂級節點,也就是 page 節點,尋找過程結束;
但是如果 url 中有參數,還會繼續向上查找這層,所以很多時候配置中可以直接 ${id} 取地址欄參數。
傳統開發模式下,在發起 API 請求時,開發者通常需要按需收集頁面當前的數據,並將其按照和後端約定好的格式拼接起來隨請求發送給後端,而在後端返回數據的時候,如果後端數據和前端的需求不一致,前端通常也需要對返回數據來進行一定的格式化處理來滿足前端的進一步需求。
而在 amis(或者其他的低代碼框架)中,每類組件對數據格式的要求通常是確定的,而出於對儘可能少地編寫代碼的訴求,API 請求通常以配置方式來聲明觸發,如下圖的 form 組件:
其對應的配置如下,可以看到表單是通過配置 form 組件的 api 字段下的 url 屬性來指定表單提交接口的。
{ "type": "page", "body": { "type": "form", "api": { "method": "post", "url": "/amis/api/mock2/form/saveForm" }, "body": [ { "type": "input-text", "name": "name", "label": "姓名:" }, { "name": "email", "type": "input-email", "label": "郵箱:" } ] }}在沒有特殊約定的情況下,當提交動作被觸發時,表單組件會收集當前數據域下的全部數據,整理成一個對象發送給後端(有關數據域的說明,參見上一章)。
而在舊業務過渡到低代碼平台的過程中,許多用 amis/ 愛速搭新建的頁面都需要和一些老接口進行通信,而這些老接口通常來說不太可能為了對接低代碼平台而進行出參 / 入參格式的二次改造。這也是在企業內部落地低代碼方案時必然會遇到,也必須解決的問題。
面對這類場景,低代碼平台 / 框架需要前後端數據 「對齊」 的能力。為此,amis 在前端層面設計了數據映射和 apiAdaptor 等機制(後端也有類似的機制,具體見後文 API Proxy)。
當然,這些方案並非此類問題的唯一解決方案,如果有更好的設計方案,也歡迎大家集思廣益為我們的開源項目添磚加瓦。
數據映射的實現原理非常簡單,主要是在請求提交前先攔截一下然後將數據處理成指定的格式,其語法為 "目標字段名": ${原字段名}。
具體可查閱官方文檔中有關數據映射的說明:https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/data-mapping
API Adaptor 簡單而言就是由用戶自行實現一段用於請求數據 / 返回數據格式轉換的代碼,並將其貼到組件 api 的配置中。組件在發起請求時會調用這段函數代碼來對相關數據進行處理。
其具體使用方法如下(api 字段下的 requestAdaptor 方法),總的來說就是在請求發送之前調用開發者在配置中定義好的函數修改請求內容,然後將修改後的結果作為實際的請求內容發送給後端。接收適配器也類似,只不過修改的是返回內容。
const schema = { type: 'form', api: { method: 'post', url: '/amis/api/mock2/form/saveForm', requestAdaptor: function (api) { return { ...api, data: { ...api.data, // 獲取暴露的 api 中的 data 變量 foo: 'bar' // 新添加數據 } }; } }, body: [ { type: 'input-text', name: 'name', label: '姓名:' }, { name: 'text', type: 'input-email', label: '郵箱:' } ]};注意:在愛速搭平台的頁面 runtime 環境中,用戶填寫的 API 如果沒有指定為 raw: 類型,會被轉化為平台統一的 Proxy API,由愛速搭平台的後端服務統一進行轉發。API 轉發相關的內容詳見後文後端服務 -API Proxy。前端 amis 的映射 /adaptor 和後端的 api proxy 共同構成了愛速搭平台的接口請求發送體系。
雖然 amis 提供了豐富的默認組件,但是默認的組件必然無法覆蓋各種未知的使用場景。
在低代碼平台 / 框架的選型和建設過程中,通常平台 / 框架的維護者需要不斷地抽取通用業務邏輯並將其作為默認組件固化下來,而自定義組件是需求收集和能力延展的有效補充機制。
amis/ 愛速搭同樣支持多種形式的自定義組件接入方法,包括 SDK、React 方式等,詳見官方文檔(https://aisuda.bce.baidu.com/amis/zh-CN/docs/extend/custom-react)自定義組件的整套體系相對比較龐大,所以本文僅列舉,具體細節會在後續的其他文章中進行披露。
amis 作為一個相對成熟的低代碼前端框架,提供了通過 JSON 配置生成複雜頁面的全套能力。然而 amis 畢竟是一個前端框架,單靠其本身必然無法實現 「讓不懂前端,不會 JavaScript 的人也能製作頁面」 這個目的。故而在 amis 之上,我們還開發了一個可視化編輯器。
對整個平台而言,組件的具體行為和頁面渲染引擎由 amis 提供,而可視化編輯器主要做的事情是:
提供拖拽組件到頁面並生成相應 schema 的能力
調用 amis 框架基於頁面當前編輯中的 schema 來渲染預覽視圖
從直觀的產品形態而言,愛速搭平台的頁面可視化編輯器,對應的是平台中最常用的 「編輯」 界面,如下圖。
此類可視化編輯器的實現原理在現今已經不是一個時髦的話題,在低代碼框架已經 ready 的情況下,可視化編輯器本質上就是一個 「比較友好地修改組件配置的界面」。一般來說在低代碼平台的研發過程中需要投入大量人力去處理可視化編輯器的各種細節問題(如配置項的對齊,界面交互友好程度等),但經過業界多年的探索和沉澱,研發層面的基本思路已經相對固化,此處也就不再贅述。
有興趣的讀者可自行前往研究(https://github.com/aisuda/amis-editor-demo)
愛速搭平台的前端主要部分由 amis 及其配套的可視化編輯器組成,而作為一個能夠提供完整服務的低代碼平台,除前端頁面搭建能力外,後端的一系列服務也是必不可少。
愛速搭平台的後端服務目前沒有對外開源,但對於低代碼平台而言,配套的後端服務是整個平台能力中不可或缺並且重要度逐步提升的一部分。
由於篇幅有限,在此對愛速搭平台的後端服務體系只做一個業務問題 - 解決方案的概要式列舉,以供各位讀者一窺低代碼平台的「全貌」。
為解決前後端通信中最常見的跨域問題,以及提供 header/body/query 信息的拼裝轉化能力,愛速搭平台提供了 API Proxy 機制。在 amis 中,許多組件都支持以 API 的形式配置數據源 / 提交接口,如下圖的 form 保存接口,在配置時填寫了原接口。
在運行態時實際訪問的接口是經過平台統一 proxy 的類似 api/proxy/:apiId 這樣的路徑。
在愛速搭平台中發起一個 API 請求的處理流程如下圖,關於前端的數據映射和 adaptor,見上一章《對 API 請求的前置 / 後置處理》(詳見 2021 年 GMTC 深圳站分享 PPT:https://ppt.infoq.cn/slide/download?cid=94&pid=3681)。
在實際開發過程中,類似 「以接口 A 的返回值作為參數去請求接口 B」 這類場景是很常見的,在傳統開發模式中,往往不得不為此開發一個專門用於 「聚合」 這兩個接口的接口 C,而可視化的 API 編排系統則完美地解決了此類問題。API 編排(https://baidu.gitee.io/aisuda-docs/API%E5%AF%B9%E6%8E%A5/API%E7%BC%96%E6%8E%92)本質上是以可視化的方式來編寫後端代碼,它可以用於不複雜的接口聚合以及增刪改查的場景。
API 編排在愛速搭平台中的產品形態如下圖,主要做的事情是順次執行各節點操作並將最終結果輸出到返回值中。
然後在前端頁面中就可以通過選擇的方式調用編輯好的 API 編排。
其執行原理如下圖(詳見 2021 年 GMTC 深圳站的分享 PPT:https://ppt.infoq.cn/slide/download?cid=94&pid=3681)。
實體模型主要提供數據庫統一訪問和建模的能力,在基於 API/API 編排的數據連接方式中,一個完整的應用仍需以來於後端提供的 API,但隨着業務的發展,會產生大量類似 「對數據庫中的數據進行增刪改查」 「往數據庫中新增一個字段」 的重複性高,難度低,但又需要耗費大量溝通成本的後端需求。
低代碼平台中的實體模型通常是以可視化的方式提供數據增刪查改能力以及更高權的 DDL 操作。將這些重複勞動可視化是為了讓不熟悉 CRUD 編程的人也能定製自己的數據庫操作,也讓寶貴的後端研發人力不必過多浪費在編寫 CRUD 代碼上。
以下是愛速搭平台中的 「實體管理」 建模功能的截圖示例。
實體模型整體架構比較複雜,由於篇幅原因,不在本文具體描述,僅列舉以供作為 「低代碼平台能力」 的參考,有興趣的讀者可前往觀看本人在 GMTC 深圳站的分享以及期待後續的文章。
流程引擎主要用於審批流和業務流的編排,如請假審批,預算審批等。在低代碼平台中,流程引擎通常由 流程可視化編輯器 + 集成待辦中心兩個主要部分組成。
以下是百度愛速搭平台中流程可視化編輯器的截圖示例,圖中展示了一個 「審批」 節點的配置細節。
而下圖展示了待辦中心審批頁,該審批頁的內容是由上圖編輯器在設計階段指定的。
流程引擎也是一個比較複雜的獨立體系,本文僅列舉,有興趣的讀者也可在本人的 GMTC 深圳站分享中找到相關內容。
後續也會有獨立文章進行詳細說明。
低代碼平台這樣事物嚴格來說並不能算是新興產物,而這個概念突然「火」起來,卻似乎是近一兩年的事。面對這樣的現狀,我們大可不必偏激地認定它是解決研發困局的銀彈或是炒作起來的泡沫。就我們在百度內外的實踐經驗而言,它是「實際解決了不少業務問題的」「有用的」解決方案。在低代碼這個領域,未來也必定還有很多值得不斷深挖的細分課題。
在此,也期待 GMTC 北京站的各位分享者能給我們帶來更多精彩的分享。
潘征,百度資深研發工程師。2013 年加入百度,百度 FEX 前端團隊資深成員,低代碼方向技術專家,先後負責和參與多款低代碼產品的研發,在低代碼內部推廣使用和外部商業化落地方面,均有豐富的成功及不成功實踐經驗。GMTC 全球大前端技術大會(北京站)2022「低代碼」專題出品人。
大會將於今年 8 月舉辦,「低代碼」專題已確認上線多個演講:《基於 LowCodeEngine 的阿里低代碼組件體系的建設和實踐》《中國工商銀行低代碼可視化建設探索與實踐》。還有前端 DevOps、前端框架新體驗、大前端監控、移動端性能與效率優化、B 端研發效能、IoT 動態應用開發、大前端技術融合與跨界、大前端設計協同、雲研發實踐、TypeScript 等十餘個熱點專題,點擊底部【閱讀原文】直達日程頁查看。
大會門票限時 9 折,組團購買更優惠,感興趣的同學聯繫票務經理:+8613269078023(同微信)。