無組件架構:你不需要知道的 “新一代” 前端架構模式
PS:這是一種很好玩的前端架構模式,可以創造出無限的樂趣。你不一定需要知道它,但是它真的很好玩。我寫這篇文章主要是因爲好玩,也沒有啥新東西。
很高興在過去的幾年裏,微前端已經成爲前端架構的一個共識式的架構模式,也不枉費我作爲國內最早的引路者,寫了國內第一個微前端框架和書籍,並推廣至今。所以,讓我們繼續往前走吧,探索整個領域更多的可能性。從無組件定義的英文名稱 Componentless,你就可以知道它所要對標的是類 Serverless 一樣的後端架構模式上。所以,在定義上和 Serverless 架構的定義頗爲相似。
無組件(Componentless)架構是一種架構模式,它是指大量依賴於三方組件(運行時依賴的組件而非編譯時依賴的組件,即編譯即服務)或暫存容器中運行的自定義代碼的前端應用。應用的三方組件如同三方 API 服務一樣,可各自獨立發佈、獨立部署,應用無需重新編譯、構建和部署。
這就是爲什麼,我們定義爲無組件架構的原因,你可以不需要編寫任何的組件,只需要編寫邏輯代碼或者 DSL 來實現它們的組合。甚至於,我們可以只提供一個 DSL + 通用的 URL,由瀏覽器根據 DSL 完成應用的全自動構建和運行。
看一個簡單的示例,便是 Quake 的 Transflow 設計: from('todo','blog').to(<quake-calendar>);
,讓組件標準化,數據標準化,自然而然地我們就不需要考慮任何組件的設計。
前端與後端架構的演進
先前,經常和別人交流起領域驅動設計(DDD)在前端的應用,也是頗爲有意思的。作爲 “九又四分之三” / 10 個 DDD 方面的磚家,過去,我一直覺得領域驅動設計並不適合於前端,整潔前端架構 纔是人們所需要的,但是設計 + 上手難度略大。在今年裏,又雙叕對多個後端應用使用了 DDD 的設計和規劃,又有了新的體會(雖然依舊不行)。前端可以有類似於 DDD 的方式,只是方式完全和後端不一樣。後端以模型和函數作爲兩種風格不同編程風格的基礎,前端則是以組件 + 事件作爲編程風格的基礎。組件是可消滅的,事件則由是設計事件流來編排。
Clean Frontend
所以,你並不直接將後端 DDD 的思想,直接套用在前端應用上,除非你應用的邏輯主要是在前端。
微服務與微前端
對於現今的大多數系統來說,它們依舊維續在一種:「後端微服務,前端 “大泥球” 」的狀態。後端的微服務已經按照「康威定律」拆爲一個個的微服務(當然了,不合理地拆分微服務是另外的一個問題),而前端還處在一個大泥球的狀態。所以,微前端便作爲其中的一種(並非唯一)技術來解決組織架構對齊,並實現快速發佈上線的架構模式。它可以將單體大應用拆分爲多個較小的自治應用,但它們依舊聚合爲一。可以用於解決遷移遺留系統、統一用戶體驗、幫助多團隊協作等。
在進行後端系統遷移時,我們使用 DDD(領域驅動設計)的方式尋找合理地微服務架構設計依據,微服務成爲我們改造遺留系統的方式。我們從一個模塊、一個功能開始,逐步地替換舊的單體應用,直至整個系統完成替換。這個替換模式對於前端應用來說,它也是頗爲類似的。
所以,在進行了微前端的改造後,架構上對齊了,人員上對齊了,皆大歡喜。
Team Align
再往前呢,我們應該如何繼續演進系統?
Serverless 與 ComponentLess
2017 年,在學習了 DDD 與 Serverless 之後,開源了《Serverless 應用開發指南》(https://serverless.ink/) 之後,我一直在思考在前端領域如何應用使用類似於 Serverless 的思想?於是,就有了關於跨框架組件庫的想法:《前端下半場:構建跨框架的 UI 庫》,只是國內這些寫組件庫的公司,並沒有這種大膽的想法,太可惜了 —— 只會版本號 + 1,別人做什麼跟着做什麼?還有一條有意思的故事線,在經歷了無代碼編程的火熱之後,我重新思考了一下前端和後端的未來:《前後端一體化:前後端分離將死?》。
起先,我以爲無代碼編程是一個 ComponentLess 方向,但是一研究發現並不是。無代碼編程傾向於可視化編程,而 ComponentLess 傾向於使用 DSL 編程。就這一點來說,我便偏向於使用 Web Components + WAM 技術來構建新的前端架構。
直到我最近在開源知識管理工具 Quake (https://github.com/phodal/quake)重新應用了這個思想之後,發現了特別有意思,我便想着寫一篇文章來介紹相關的理念 —— 畢竟,市場上已經接受了 Serverless 的觀念,接受了微前端的觀念。那麼,剩下的問題就變得非常的簡單了。
無組件架構
繼續回到開頭上的定義:
無組件(Componentless)架構是一種架構模式,它是指大量依賴於三方組件(運行時依賴的組件而非編譯時依賴的組件,即編譯即服務)或暫存容器中運行的自定義代碼的前端應用。應用的三方組件如同三方 API 服務一樣,可各自獨立發佈、獨立部署,應用無需重新編譯、構建和部署。
簡單來說,無組件所要做的事情就是將組件變成一種運行時服務,而非過去的編譯時依賴。當所有的組件都變成一種真正的基礎設施時,我們就不再需要這些組件,進而從應用開發側讓組件消失,達成了應用無需組件的狀態。如此一來,它也變成了一個低代碼(LowCode)式的系統,配合簡單的代碼生成,可以達到無碼的狀態(NoCode)。
從形式上來說,使用微前端相關的技術可以提供無組件架構所需要的一系列基礎技術。其中,最爲簡單的方式是使用:Web Components 。那麼,先讓我們來看一個基於 Web Components 的 Componentless 架構示例。
示例:如何邁向 Componentless 架構?
從過程來說,可以分爲三步:
-
使用 Web Component 分解應用
-
拆分更多的組件以消滅組件
-
構建生成式低代碼模式
剩下的部分就是填空式的編程了。
1. 使用 Web Component 分解應用
先來看個示例,比如我們的前端部分有 A、B 兩個微應用,顆粒度已經非常小的,但是依舊是應用級別的應用。應用 B 使用了 Web Components 技術構建,並且在微應用 B 裏引入了兩個三方的 Web Components 組件。常規的前端應用裏,如果我們更新了這兩個組件,對應的應用需要重新編譯,再發布上線。
Slot
而在現在,在 Custom Element + Shadow DOM 的加持下,我們只需要更新指向組件庫的 script
標籤的鏈接,或者緩存即可。
2. 拆分更多的組件以消滅組件
接着,讓我們進一步地優化,去除應用 A 和 應用 B 的所有內部組件,將這些組件外置,按功能模塊構建成一個個的組件集。這些組件集,我們可以按功能團隊劃分,或者劃到所爲的前端中臺,又或者是前端垃圾回收站。
WC ComponentLes
這些並不重要,現在我們的應用裏的「組件」已經非常之少了 —— 我們還剩下一些對於這些組件編排的組件 + 一些額外的業務邏輯。
3. 構建生成式低代碼模式
現在,再回顧一下 Serverless (AWS Lambda,它們沒給廣告費)中編寫的 “hello, world” 函數:
module.exports.hello = (event, context, callback) => {
callback(null, "hello, world");
};
在使用 Serverless Framework 這樣的框架時,我們只需要在這個函數上,填寫我們的業務邏輯即可,即填空式編程。對於前端來說,這個過程也是類似的,我們的數據有了,我們的目標組件有了,只需要一個有限性代碼生成的功能。即,我們只需要生成一個有待完善的空函數即可,如 Quake 中的 Transflow: from('todo','blog').to(<quake-calendar>)
,生成的函數和邏輯(部分代碼示例):
const tl_temp_1 = async (context, commands) => {
const el = document.createElement('quake-calendar');
...
return el;
}
在這時,只需要確保路由與函數不被修改,那麼剩下的部分就是對於數據處理的填空了。
遷移方式
除了上述的直接分解的方式,還有其它的漸進式遷移方式。
遷移方式 2:新嵌老
-
使用新的技術和框架創建應用的架子。
-
提取 Web Component 套入舊的組件,轉變化公共能力。
-
在新的應用中嵌入舊的輪子。
遷移方式 3:老嵌新
-
構建新的 Web Component 組件。配合 monorepo 管理
-
嵌入組件到現有應用中。
-
完善無組件架構機制。
-
構建低代碼編排模式。
無組件架構理念
從當前個人的理解,它的核心理念是: 組件即「服務」。即讓組件像服務一樣,可以自由部署,自由更新。在組件更新後,應用也從某種意義上達到了應用的更新。
除此,還有諸如於:
-
自動化環境隔離。聖誕節馬上就到了
-
生成式低代碼。真正意義的前端膠水
更多的內容,還有待探索。
無組件架構問題
除了上述的諸多優點,它還有一系列的缺點需要解決:
-
瀏覽器兼容性。Web Component 的兼容性問題
-
測試難度。自由的架構往往意味着測試上的成本,在這一點也與微服務、Serverless 類似,將需要由更多的端到端測試才能保障項目的質量。
-
組件模塊化的劃分依據。當我們構建出一個個的組件集之後,就需要尋找一種方式來合理規劃。
-
Monorepo 管理方式。repo 越多,管理上就會越複雜。需要引入 nx、pnpm 這樣的工具進行管理。
-
更新策略。即應用與組件集的更新策略應保持不一致。
-
……
優勢場景:結合低代碼
從某種意義上來說,無組件架構是可一種廣義低代碼的實現模式。因爲更獨立的組件模式,它構建出來的低代碼系統就更有意思:
-
配置即運行時。類似於 Oracle DB 的面向過程式,實現快速上線新特性。
-
代碼生成的填空式編程。如上所面的例子所述,可以生成基本的函數代碼,隨後開發人員補充代碼邏輯即可。
-
基於流編排的低代碼。同樣適用於傳統的低代碼架構模式。
-
DSL 式低代碼。如 Quake 中基於 DSL 來構建的。
只是呢,從模式上來說,也相差不了太多。
無組件模式
上面的都沒啥意思,在我們採納了 Web Component 作爲無組件架構的實施技術之後,在架構上將會有更多的施展空間。Web Component 已經是一個非常好的、類似於 Docker 的容器,可以玩各種 fancy 的容器化模式。我們在 Quake 嘗試了一些模式,它帶來了一系列的挑戰,卻也非常有意思。
適配器:兼容現有的組件。
基於 WC 自帶的特性,封裝現有的主流框架 Angular、React、Vue 等的組件,就可以快速提供這樣的能力,諸如於我們在 Quake 中提供的 QuakeTimeline
和 QuakeCalendar
等都是通過這種方式,將 React 組件封裝爲 Web Component 組件:
class ReactElement extends HTMLElement {
...
}
customElements.define('quake-calendar', ReactElement);
由於,對外暴露的是 WC 組件,就無所謂於使用的是何種前端框架。
大使模式
在雲原生模式裏,大使模式(Ambassador)可以創建代表消費者服務或應用程序,發送網絡請求的幫助服務。同樣的事件,也可以通過組件來封裝,
const fetchEl = document.createElement('fetch-api');
fetchEl.setAttribute("url", "/action/suggest);
fetchEl.addEventListener("fetchSuccess", (res: any) => {
let response = res.detail;
loading.onDidDismiss().then(() => {});
callback(response);
})
不過,我這麼寫就只是爲了好玩,創建一個 Loading 組件,在 Loading 裏插入 <fetch-api>
組件發起 HTTP 請求,請求成功後,把 DOM 銷燬。
這樣一來,我只需要替換這個請求組件,就可以替換所有的請求 API。
無限 “套娃” 模式
常規的模式下,我們在 A 組件裏調用了 B 組件,那麼理論上,我們就不需要在 B 組件裏調用 A 組件,會形成循環的引用,但是在 Web Components 中它成了一種功能。
如我們在 Quake 的 markdown 渲染引擎裏 <quake-render>
裏,按條件渲染了嵌入頁面的 <embed-link>
,而 <embed-link>
嵌入的頁面也是 markdown 的,所以需要一個 <quake-render>
,所以可以無限 “套娃”,直到瀏覽器的當前頁面掛了。
從使用方式上來說,A、B 兩個組件並不存在這樣的相互調用的關係。
PS:其實這是一個 bug,後來我覺得這是一個 featurs。
Sidecar 模式
在雲原生模式果,挎鬥模式是指將應用程序的組件部署到單獨的進程或容器中以提供隔離和封裝。這一點來說,對於 Web Components 也是非常簡單的。
其它模式
依舊很多,有空可以慢慢玩。
總結
跳出框架來思考問題,便會發現各種非常有意思的事情。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/YbkQFx26xWPx3TUIF34wnA