邏輯編排在優酷可視化搭建中的實踐 - 邏輯與 Runtime

從可視化搭建說起

頁面可視化搭建系統從 16 年開始如雨後春筍般湧現而出,從活動頁搭建到中後臺搭建,有開源有僅公司內部使用的,都致力於將前端從繁複的體力勞動中解脫出來,提高頁面生產效率。優酷內部也有一套營銷活動搭建系統,每年生產 2K + 活動頁;能夠滿足這麼多頁面的需求,除了沉澱了大量可複用的組件外,圍繞着搭建系統的前端研發每天都在不停地維護升級老的組件,同時生產新的組件。

痛點

頁面生產能力上去了,研發還是一直埋頭在組件開發需求中。這些需要都是從哪裏來的呢?其實上面也有提到,就是兩點:

需求是永遠也做不完的,爲了提高開發效率,研發側不斷地沉澱通用的基礎庫,與服務端商定標準化的接口,以此來減少維護成本,但基於現有模式錦上添花的優化遠遠不夠。說白了,現有的可視化搭建效率和研發效率都已經達到瓶頸了,我們急需一種新的生產模式,給我們帶來生產效率的突破性提升。

解法

我們思考一下,頁面可視化搭建是如何解放生產效率的。它將完整的頁面進行拆解,拆分爲可以複用的組件,研發負責組件生產,產品運營負責組件配置,形成了一種簡單的流水線作業的模式,這種模式的好處在於:

現在的瓶頸已從頁面開發效率轉變到組件開發效率上,我們做一個設想,讓非研發角色也能介入到組件生產過程中,進一步提高生產效率。在此之前,我們需要先基於現有模式進一步拆分組件結構:

組件可以拆解爲 UI + 邏輯。UI 可以通過細粒度的文字、圖片、slider 等組件搭建出來;邏輯主要涉及到接口請求、數據處理、能力調用,我們將一些常用的 api 調用比如跳轉、常用的接口請求比如查詢登錄態等進行封裝沉澱,加上語義化的描述,非研發人員可以將他們拖拽繪製成流程圖,完成業務邏輯的編排。兩者結合,就可以生產出完整的業務組件。而對於業務邏輯的組合編排,我們稱之爲邏輯編排。

我現在有個需求,需要實現一個簡單的抽獎活動。頁面分爲兩塊,第一塊是獎池,五個獎品橫鋪開,第二塊是抽獎按鈕,點擊按鈕,如果用戶沒登錄,拉起登錄面板,如果用戶已登錄,則進行抽獎並高亮展示對應的獎品。

我現在把 UI 撘出來了,就差邏輯來讓對應的坑位展示了,我們把欠缺的邏輯部分拆成兩段:

除了開始和結束以外的這種邏輯片段都是我們已經沉澱下來的,可以直接拖拽進來,繪製流程圖。繪製完了後,我們選擇模塊的 頁面主動觸發(設計給非研發用的,他們肯定不理解 didMount),將它和 查詢獎品 關聯上;選擇抽獎按鈕的 點擊事件, 關聯上 抽獎 邏輯,這樣模塊就能在不同的時機做正確的事情了。可能你還有點小疑惑,我查詢了獎品後,數據是如何映射到 UI 展示上的呢?要想知道如何映射,你得先知道邏輯是怎麼運行的。

下圖是我們在寒假戰役中的一個應用場景:

Logic is Core

扯一點遠的,不願意看可以直接跳到 “邏輯如何運行” 小節。前端的邏輯編排相比服務編排要更復雜一點,並不是說前端邏輯編排更難,而是因爲:場景更加多樣化,C 端模塊搭建和中後臺搭建的編排差異就很大;人員更加多樣化,除了服務研發人員還要服務非研發人員;除此以外,還需要和 UI 進行結合,可能性就更多了。

這段時間比較火的 iMove ,初始被設計服務優酷活動搭建平臺的邏輯編排,後面服務 imgcook 後,與優酷的邏輯編排在形態上已經截然不同,所以在前端比較難像服務編排一樣,平臺化、中心化然後去服務多種多樣的業務。如果你真的很想去服務多樣化的場景及業務,提供一個輕量級的庫,讓它足夠靈活、可定製,這也是 iMove 正在走的路。

前面講了很多,都是在說,邏輯編排需要 follow 不同的平臺做對應的定製,但是不管形態怎麼變,它的核心是不會變的,我們一定是在圍繞着 “邏輯” 去做包裝,將之打造爲不同的產品形態。所以,邏輯纔是核心!

邏輯如何運行

說了半天邏輯多重要,假使我現在抽離了一個函數出來,把它發佈了,我又用它拖了個流程圖,它怎麼才能執行呢?

邏輯最後想要運行,無論你是出碼 (to code) 還是在線執行,你都需要一個 “邏輯編排解釋器”,這個解釋器可以讀懂編排後的邏輯並且去執行邏輯,這個解釋器我們稱之爲 Runtime。代碼是冰冷的,解釋器是沒有智商的,它並不能真的讀懂邏輯,只是因爲我們約定了一個規則,定好了編排後的邏輯可能有幾種情況,每種情況應該如何做,Runtime 只是在依章辦事,而這個規則就是 DSL 。所以邏輯想要運行,它依賴於 DSL + Runtime ,要做一個邏輯編排平臺,也一定是先定好 DSL,實現 runtime ,後面纔是去做平臺。

每個沉澱下來的邏輯,我們將其定義爲邏輯元件,加上語義化的元件名稱及詳細的描述,然後發佈到元件市場。使用者只需要根據元件名稱來挑選需要的邏輯,通過連接線將他們連接起來,就可以組合成一個流程圖,這個流程圖也就是一段完整的邏輯。

可是流程圖導出來的 graph json 都是點和線的集合:

這種 json 如果直接用 runtime 執行有兩個問題:

所以我們需要先去約定 DSL ,保證足夠直觀且只包含必要信息,所以我們設計了一個轉換器將 graph json 轉換爲 DSL 。每個邏輯流程圖對應一個 DSL 產物,藉助 runtime 的 interpret  方法,它可能會運行在 useEffect 中,也有可能運行在某個元素的點擊事件中,或者是頁面的滾動事件中。

邏輯元件生產與消費的分工

邏輯元件從被編排到被運行的過程,也是它被消費的過程。文章到這裏,相信大家可以感受到這個消費的過程,非研發同學確實是可以進入的,因爲我們設計的足夠簡單。我們把整個頁面的生產當做一條流水線的話,以前是 模塊生產 --> 模塊市場 --> 頁面搭建,有了邏輯編排後,我們的流水線比之前劃分的更細了。

這張圖是產品進入到中期的一個形態,前期的時候產品的角色更多是由研發來承擔的,然後逐漸過渡到產品,到了後期呢,產研聯合給運營同學做培訓,中間這部分會逐漸的過渡到運營同學那邊。

因爲整條線上大家需要關注的事情越來越細了,頁面生產線出錯的幾率也會低一些。

邏輯編排

上面一直在從業務角度去聊,接下來就要深入到邏輯編排裏去了,講一講邏輯編排的設計思想。邏輯編排最主要是分爲三塊,元件、編排器、runtime,我將它們稱作邏輯編排三板斧。那問題就轉換成了:

這三塊各自拆解出基本要素,用基本要素來描述它們,互相之間建立起連接關係,這些構成了邏輯編排的規範協議。這樣不管什麼業務進來,只要遵循這套規範,它們的底層就是一致的,各業務也不會顯得散亂。

DSL

在探討我們的 DSL 之前,一定要先聊一聊 DAG(有向無環圖),因爲我們的 DSL 設計本質上就是 DAG 。

DAG

DAG 的 G 指 Graph(圖),圖是數據結構中最爲複雜的一種,我們在瞭解 DAG 之前,回顧圖的幾個要點:

  1. 頂點 (vertices):圖中的一個點

  2. 邊 (edge): 連接兩個頂點的線段

  3. 度數 (degree): 從一個頂點出發有幾條邊,這個頂點的度數就是幾

圖就是由一些頂點和邊組成,邊就是頂點間的關聯關係。DAG 中的 D 是 Directed,代表是有方向的,就是說頂點之間的邊帶箭頭,常見的比如食物鏈,就是有向的;A 是 Acyclic,代表無環,從某個頂點出發,無論走那條路,都不會回到那個頂點。

基於有向無環圖來約定我們的 DSL 正合適不過:

基於 DAG 的 DSL

每一個頂點都是邏輯元件的實例,繼承自邏輯元件,也可以修改自身屬性。邏輯元件分了兩大類 - 基礎元件和業務元件。基礎元件目前只有開始和結束,其他所有需要研發開發的都是業務元件,這麼設計是爲了降低編排使用門檻,你不需要有任何編程基礎。

跟 Flow-based programming 不一樣的是,我們移除了頂點之間的值的傳遞,運營不是研發,他們很難理解計算值如何流動以及如何操作它們。

Runtime

前面說到數據如何映射到 UI 時,不是故意賣關子,實在是要配合着 runtime 一起給您講解一下。邏輯與 UI 的結合這裏也只是粗略提一下,要留到後面的文章細講。

Runtime 與 UI

React 自身定位是用於構建 UI 的 Javascript 庫,它做的就是通過 data 驅動 UI 展示,react 的 data 通常都存儲在 state 中,我們剛纔已經通過邏輯編排拿到了獎品數據,對此 runtime 唯一要做的就是在內部 data 與外部 react 中間架一座橋。

將邏輯內部生成的數據全部存儲在內部 context 中,利用發佈訂閱模式,每當 context 有更新時,通知 UI 組件執行 setState,state 更新後,React 自動更新 UI。

runtime.subscribe('context', (val) => {
  // handle data
  this.setState({ data });
})

輕量的 Runtime

YOHO 的 runtime 很小,不到 10k,麻雀雖小,五臟俱全,接下里我們看看這小麻雀是如何支撐起流程編排的最後一公里 - “執行邏輯” ,又是如何和業務打交道的😊。

邏輯元件本質上就是基於一段代碼,給它加了一些描述信息,可是我的頁面中是不會內置這一段段代碼的,runtime 也不可能內置這些代碼,也就是說我們的執行上下文中是沒有元件對應的函數的;我們的業務是 No Code 形式,不出碼,所以 Runtime 內部實現了一個元件管理器,你可以通過他進行元件的註冊,在 DSL 執行過程中,它也會幫你進行元件檢查。

Runtime 對每一次編排實例(流程)的調用都是在沙箱中進行,實例的執行是互不干擾的,而執行過程中每個元件輸出的結果我們也做了隔離;其實我們還給元件之間互相通信提供了方法,但是目前不建議使用,因爲元件之間隨意通信有很大的副作用,我們目前還沒有足夠的產品形態去約束它。

在 runtime 設計過程中,我們預見業務會不斷地有各種需求進來,可是我們不希望 runtime 過於業務定製化,導致將來積重難返,所以設計了生命週期。在編排實例執行的各個階段,業務都可以進行干預。

使用發佈訂閱模式,而不是觀察者模式的原因,也是我們希望 runtime 足夠靈活。舉例來說,上下文因爲數據隔離,我們把它拆解爲各個子上下文,你可以把每一個 childContext 當做一個 topic 來訂閱,其他 childContext 更新並不會附帶影響當前的。childContext 。

除了以上,runtime 只做了一個事情 —— 邏輯走向的調度,就是有向無環圖中的 “有向”。正是基於以上的設計,我們的 runtime 足夠小,但是又足夠靈活,便於定製。

三板斧在本文中就先講這第一斧😄

後話

在做邏輯編排平臺的過程中,方案做過好幾版——從一開始兩週搞出 iMove 初版給老闆去展示,然後想要擁抱集團,和優秀的編排平臺 Logic Force 想着共建前端編排,因爲我們最終的產品形態差異過大,LF 想要支持也需要投入很大的精力,同時我們的業務想要快速驗證,所以又重新自研了一個輕量的邏輯編排平臺。期間有很多感悟,我們也都在邏輯編排的設計中添加進去了。

邏輯編排講到這裏還沒有結束,我們後面還會有系列文章,元件和編排器是如何設計的呀,和 UI 進行聯動,UI 側又是怎麼設計的呀,我們在邏輯編排方面又做了哪些前端特色的東西呀?如果你也感到好奇,敬請關注~

關注「Alibaba F2E」微信公衆號把握阿里巴巴前端新動向

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/9fv4719rsxwrr5oimoUnZg