淺談低代碼平臺涉及的一些技術選型
前段時間,我寫過一篇文章,側重從產品角度去談低代碼平臺的一些能做和適合做的事情,以及做這些事情的方式。
在本篇中,會嘗試從幾個大方向出發,簡單談談如何從技術視角看待模型驅動的低代碼平臺的整體架構,以及相關的部分選型。
側重點
如果說,低代碼平臺與通常的技術開發框架有什麼差異,那就是它們對於代碼和配置的側重不同。
- 代碼優先
- 配置優先
很顯然,如果表達能力不損失,那麼,低代碼平臺是配置優先的,並且其中的配置表達能力還需要比較強大才行。
所以,我們在技術選型的時候,要時刻牢記,這與我們通常寫代碼用的技術框架不同,整體不是為了寫代碼寫得舒服,而是為了打造一套能讓不同技術能力的人合理協作的機制。
還是用 Excel 來類比:有的人傾向於組合現有的公式;有的人則無視這些東西,從無到有自己寫一些代碼實現所需功能。 前者,是我們要服務的主要物件。
類型
類型體系是一種非常有趣的東西,它可以用來表達結構的組合與變換,從而初步驗證變換的安全性。
基於元數據的類型體系
在低代碼平臺中,我們可以藉助類型體系,來實現業務實體的結構與關聯表達,並且為規則的擴展提供一定程度的便利性,這是一種很有用的工具,也可能是整個模型驅動的低代碼平臺的底層方法論的核心部分。
通常,我們會在某種程式設計語言中,藉助這種語言提供的能力來表達原子與複合類型。 Web 前端最常用的類型描述體系是 TypeScript,但是需要注意到,TypeScript 能夠幫助我們驗證的東西,是在編譯期確定的,而在低代碼平臺中,有很多東西是編譯期不確定,要在運行時檢驗。
比如說:用戶創造了「學校」模型,並且為其動態添加了一些字段。
整個這個過程,是完全在運行期完成的,如果我們想要為它添加類型驗證,就需要從另外一些角度來解決問題。
領域模型驅動的低代碼平臺,天然擁有實體的元數據,並且可以依託它,傳遞到運行期的每個角落,相當於建立了一套基於 Schema 的類型機制。 如果能把這套類型機制顯式表達出來,會同時對平臺和低代碼的開發者有好處。
低代碼平臺的運行期,實際上相當於是常規軟體開發流程的開發期,所以,我們要在這個層面提供類型支援,又因為整個這樣的實現跨越了多端,貫穿從服務端到多個用戶端的完整流程,其中至少有一個環節經歷過序列化和反序列化過程,所以,這個驗證過程需要能夠同時確保數據的合法性。
這樣一來,這套類型系統的職責就是貫穿數據的生命週期了。
類型運算
在已經具備的類型結構上,可以擴展出一套校驗機制來。 比如,我們知道類型的 Schema 是怎樣的描述,就可以生成對應的校驗函數來驗證對應數據的合法性。
甚至因為存在類型的組合與變換關係,可以動態生成複合類型的詳細訊息,並且以此作為更複雜數據結構的校驗依據。
需要注意的是,這裡的組合與變換,已經不是 TypeScript 裡面的那種了,這是平臺需要實現的能力,即使平臺本身不使用 TypeScript 開發,也無損於這種變換與驗證能力。
編碼提示
同理,基於已有的類型結構,可以在擴展的動作與方法上,提供一些額外的程式設計便利。 不同語言之間的類型結構,是存在一定程度的轉化性的,我們基於描述構建的這套類型結構,可以很方便映射到主流程式設計語言中。
存儲
存儲是整個系統可用性的一個關鍵組成部分。
從程式設計框架的角度,針對結構化數據存儲,出現過幾種不同維度的抽象:
- 面向資料庫連接:JDBC
- 面向實體:ORM
前者主要側重於遮罩不同資料庫之間的差異,一般還要輔以特定的程式設計語言或者框架所約定的程式設計方式,比如 Java 體系中的基類、抽象類、實現類等等一套機制。
這種方式暴露的方式比較底層,控制能力強大,沒有用這種方式實現不出的需求,但是在低代碼平臺中,大部分情況下抽象層次過低了。
而廣義的 ORM 則是一個比較適中的抽象層,它首先提供了實體視角,可以面向領域模型中的物理層模型去組織數據的存取。
其次,底層的存儲未必就一定要是本系統內部的結構化存儲,完全可以基於外系統提供的讀寫 API,映射出一個虛擬的實體,此後,可以把這個虛擬實體當作真實實體一樣進行關聯或者組合操作。
所以從這個角度,我們可以設計一套分散式 ORM,來抽象整個廣義的存儲層。 ORM 中的實體定義作為貫穿整個系統的元數據描述,可以與類型系統結合,貫穿整個系統的始終。
邏輯
低代碼平臺最難以"無代碼化",或者可以說,唯一無法全部配置化的就是業務邏輯了,而且很多時候不是無法,而是不適合。
比如說,我們通常會把比較大粒度的邏輯設計為流程,然後用序列或者狀態機流程圖的方式去編排,但是很難把更細粒度的邏輯也用這種方式編排,因為性價比太低了,徒增理解成本。
但是需要認識到,邏輯也是可以分類的,比如,從觸發方式上,可以分為:
- 主動
- 被動
主動的邏輯,通常可以組織為某種服務,供某些主動調用方使用,或者由定時器、生命週期觸發。 被動的邏輯,通常承擔的是攔截、驗證等職能,一般可以歸類為規則。
視圖
在一般的應用系統中,視圖層是抽象程度最低,而在研發流程中耗時又最多的。
從最基礎的視角出發,有沒有視圖層,提供精緻或者簡陋的視圖層,都不會十分影響一般業務的實質。
基礎組成
視圖層的基礎組成部分包括如下:
- 原子化交互
- 佈局與編排
- 狀態管理
- 數據請求
除了原子化交互,是一種可以隔離在外的東西,其他部分都具有一個共同特徵:需要使用到實體結構,而這種實體結構,正是在後端存儲部分的元數據描述之一,如果我們能讓這三塊內容盡量複用元數據,則有可能最大可能地降低所需額外編寫的代碼量。
從這個角度,我們可以從元數據結構為中心,重新組織我們的前端元件體系,以及典型的狀態結構場景,並且圍繞它們,去解釋元數據,生成不同形態的交互系統。
從這個角度出發,再反過來看待之前的幾個問題:
最沒有爭議的是數據請求,它可以表達為基於元數據描述的請求,在下一節詳細敘述。
佈局和編排
這裡通常會存在幾個流派:
- JSX,約束少,人工可讀性高,解析難度高
- 某種範本語言,約束多,人工可讀性中上,解析難度中低
- JSON,約束多,人工可讀性低,解析難度低
需要注意的是,在低代碼平臺中,通常會附帶額外的視圖可視化搭建工具,因此:
- JSX 並不是最合適的承載視圖編排的工具,主要因為解析難度過高,而自身的程式設計友好,在低代碼平臺中是次要因素
- 範本的人工和機器處理能力都適中,既可以作為可視化編輯的底層存儲結構,也可以手工編寫
- JSON 可以比較容易支撐可視化編輯,但是如果熟練工想要手工編寫,難度是很高的
所以在這裡,很主流的選擇是某種範本語法,至於這種範本是基於標籤的,還是基於形如 Markdown 這樣的結構表達,這是次要的,並不是很重要,兩者基本可以視為等價。
狀態管理
狀態管理的需求可以由內置的典型狀態結構,外加可擴展的控制邏輯去支撐。 在這個地方,我們可以來看一下近兩年來爭議很大的 Redux,它的實質是什麼呢?
Redux 的實質是:將自身作為一種數據的持有者,並且提供了反覆運算器模式,讓外層可以提供映射器,來處理每次的數據變更。 通常在我們手寫代碼進行程式設計的時候,這種模式是比較複雜的,約束很多,格式代碼也多,但從另外一個角度,它是一種很好的用來承載代碼操作配置化的表達方式。
約束多,對程式設計不利,一般對於配置化都會比較有利。 可以以這樣的狀態結構為藍本,提供一些內置的動作,然後再提供額外的動作擴展能力,這樣就可以很容易實現狀態與邏輯的擴展了。
跨端
視圖需要解決的另外一個問題是跨端。 如果意識到,在我們這個體系下,並不需要為每種元件都尋求相同的跨端表達,而是只要語義等價就可以,那完全可以為各端單獨設計原子交互,然後用相同的編排層去解決問題。
唯一需要考慮的是,引擎層在各端與視圖層的通信或集成方式。 可以嘗試在 PC Web 端把邏輯引擎遷移到 Worker 中,互動層使用類似本地 RPC 的通信協定與引擎傳輸數據,在其他端使用類似的方式,這樣,各端的交互都是本地化的,但是邏輯引擎共用一套。
介面
注意到我們把介面放在這麼靠後的位置來討論,因為如果不在跨系統集成語義下,介面其實是個不值得過於關注的部分,因為它是一種系統內部行為。
從視圖角度看,由於平臺提供的能力,導致它調用的是一種封裝過的 RPC 服務,至於其內部是如何傳輸的,並不十分重要。
但是需要考慮到一點,因為我們之前的考量,把視圖整體配置化了,在視圖層中,復用了"編排",並且使其貫穿到"狀態結構"與"讀寫",因此,介面層必須能夠靈活相應視圖層的各種靈活結構調用,例如:
- Partial<Entity>
- Array<Partial<Entity>>
以及更複雜的它們的組合。 在當前,最成熟的可以回應這類請求結構的方案就是 GraphQL,它提供的兩個能力恰好符合我們的需求:
- 能夠把層次化的請求,打平到實體維度的原子化讀寫介面
- 能夠在單個原子化介面的讀寫結構上做裁剪
因此,在這裡選用 GraphQL 是非常合適的,並且它還便於實現更加複雜而強大的能力,因為下層實體關係的圖狀結構被凸顯了,可以在這一層去做一些許可權之類的編排定義。
從這個角度看,我們又可以發現,在整個體系中,前端才是真正的 ORM 的消費者,數據的生命週期一直貫穿到用戶端,因此這其中會涉及很有意思的思考,在我之前的某些文章有過比較詳細的闡述。
另外一個角度,也可以給介面層提供多套 API 出口,以適配不同的跨系統集成方式。
小結
作為一篇概要性的論述,本文只是簡單提及了低代碼平臺開發過程中的一些主要方面的技術選型,細節內容是非常龐雜的,足夠寫幾十篇來詳細闡述,此處空間太小,不一一展開。
總的原則,還是要以可組合性、配置化為最高出發點,在此基礎上首先構建出可運行的技術框架,然後再做上層的產品包裝。
後記:上次被兔子同學說,我的技術觀點和選型方式一貫比較奇怪,比如說:
- 傾向於範本而不是 JSX
- 認為視圖層的類型重要性不高
主要原因是我的視角站在低代碼平臺這種產品形態上,考慮的一種前提是要讓元數據表達的類型成為整個應用的主題,側重於各種編排能力,因此寫這麼一篇簡單提一下一些粗略的思考點。