前後端一體化:前後端分離將死?

PS:本文所針對的場景,都是複雜業務場景下的 Web 應用。簡單的 Web 應用不適合複雜的架構模式,它爲帶來巨大的成本。

原文地址:https://www.phodal.com/blog/kill-frontend-backend/

在接觸了領域驅動設計的概念,其中關於核心域的想法讓人頗爲激動。而在微服務架構中,核心域是一個或者多個服務的域,而位於核心域的核心則是領域模型。簡單地來說,對於一個系統來說,它的核心的核心的 ”核心“ 就是:領域模型。

過去,當我們談及領域模型的時候,我們往往將其置於 Web 後端的領域。當我 GET 了一些基本的理念之後,便嘗試整合到前端架構中。去年,我開始了 在項目上的第一次嘗試,並在 GitHub 上創建了 clean-frontend 項目,它以 Angular 作爲示例,介紹瞭如何開發一個整潔前端架構的前端應用。

引子 1:模型一致化

我習慣於在公司的項目中引入成熟的框架,從幾年前的 Angular.js 到近幾年的 Angular,完整的框架能爲企業帶來更少的維護成本,從而降低軟件開發成本。幾年前,我們引入了 Angular 之後,便開始大量地 TypeScript 項目實踐。作爲一個支持靜態類型的語言,它非常適合於開發大型應用、企業應用,在這一點上你可以從 React、Vue 等框架建議使用 TypeScript 看到一種趨勢。

採納了 TypeScript 之後,當前端從後端獲取到數據之後,那麼它需要將 JSON 數據映射到對應的 interface。如此一來,前端的模型也就有了對應的領域模型。而這個數據模型和後端的數據模型應該是完成一致的,但是實際上,它往往是落後的。後端的 API 發生的變更之後,才需要前端同步去修改模型。

而當我作爲一個前端的 Tech Lead 來考慮這個問題的時候,我首先想到的是:讀取後端的 Java 代碼,然後生成對應的前端模型。在沒有造 Chapi 的輪子之前,通過 TypeScript Compiler 轉換獲得對應的類型,是我覺得比較靠譜的方案。在有了 Chapi 之後,我覺得它們都是小問題了。

不過,然後我一想這樣做的意義並不大,還不如:套用後端的契約測試,造一個前端的自動化契約測試,即:mest。它通過 API 返回數據和 TypeScript 的 Interface 來完成對於契約的測試。一個簡單的測試數據如下:

url,interface
https://phodal.github.io/mest-test/error.json,mock/IError.ts

通過 HTTP 請求獲取對應的測試數據,再將其與本地的模型進行比較。

不過呢,我們還要使用 JavaScript 語言重寫部分 Java 代碼,那麼我們爲什麼不要 JavaScript / TypeScript 重寫一切呢?

引子 2:JavaScript / TypeScript 重寫一切

遺憾的是,使用 JavaScript 編寫後端應用(BFF、膠水層除外),在大部分的大公司是比較難的。內部的生態鏈和運維影響了技術決策,使用 C++ 不香嗎,使用 JavaScript 會存在無人在背後支持。

純 Node.js 後端應用

在過去的幾年裏,我建議:小公司的後端應用不要使用 JavaScript / TypeScript 編寫,因爲運維、監控、APM 等生態尚不夠完善。小公司應該優先投資於業務領域,在基礎設施的投入見效比較短,除非能控制好開發人員的流動性。

不過,不管怎樣,已經有大量的小公司因爲人力成本的原因,已經使用上了 Node.js 來開發後端應用。

Serverless 應用

而隨着 JavaScript 系生態的完善,基礎設施已經不是會成爲小公司的負擔,我便覺得這是一個好的時機。不過,我指的是採用 Serverlesss 架構,而非自建 JavaScript 系生態。Serverless 不僅幫助小企業解決了基礎設施的問題,還能爲小企業降低軟件運維成本。一旦企業做大之後,也可以自建採用 OpenFaas 等開源方案解決 Serverless 的供應商鎖定問題。

不論是 Node.js 後端應用還是 Serverless 應用,因爲使用的是同一種語言,我們可以輕鬆地在前後端之間共享代碼,可以是 Git submodule、NPM 包、遠程等等的方式。

引子 3:純編譯到 JavaScript

市面上有各種各樣支持 compile to js 的語言、框架,諸如於 Kotlin.js、Scala.js、Python.js 等等。

對於小前端的應用來說,這種架構非常的不錯。它相當於是漸進式的系統架構方案,當前採用了主流的前端框架,而非傳統的後端渲染機制,並統一了技術棧,降低了組織內部的學習成本。不過,它帶來額外的調試因素,畢竟每多一層封裝,系統的複雜度就需要 * 2。

而爲了讓框架的使用者支持不同的框架 React、Angular、Vue,這個框架還需要提供這些框架的 bind 或者是 wrapper,以提升框架使用者的幸福感。

但是,我們的挑戰依然是複雜的前端應用,以及它難以消除交互的複雜度。

引子 4:共享領域模型/模式庫

開始之前,我不得不強調一,領域模型是一種包含數據和行爲的抽血模型。

編譯到 JavaScript 是一種輕前端的方案,而對於重前端的項目來說,它們完成可以採用一種新的模式:共享領域模型。即將領域模型作爲模式庫,提供給前後端一起使用。即,我們只需要編譯所需要的部分。

這在我使用了 Kotlin 的多平臺技術(multiplatform)重寫了 Chapi 的 domain 層之後,我意識到了這是一個非常迷人的方案。我即使用在前端代碼中使用 Chapi 的領域模型,我還需要在後端的代碼中使用這套模型。原先,我需要手動翻譯一行行代碼,現在我並不需要這樣的一個步驟。只需要在 pom.xml、build.gradle 或者是 package.json 中引入依賴及其對應的版本即可。

在引入了這部分的代碼之後,我們再關注於 UI 交互部分即可。

引子 5:領域模型編譯到 WASM

考慮到並非所有的語言都能支持 compile to JavaScript,一種頗爲有效的方式就是使用 WASM。

WebAssembly 或稱 wasm 是一個實驗性的低端編程語言,應用於瀏覽器內的客戶端。WebAssembly 將讓開發者能運用自己熟悉的編程語言編譯,再藉虛擬機引擎在瀏覽器內運行。—— 維基百科

我嘗試將使用 Go 編寫的 Coca 編譯成 WASM,但是遇到一系列的問題(我已經忘了),體積似乎是個問題,所以我嘗試使用 Rust 去構建另外一種可能性。Rust 官方提供了 Rust Webpack Template,因此我可以將其集成到我現有的前端應用中。只是呢,似乎還沒有人會使用 Rust 去編寫後端應用。

但是 WASM 提供了一種更友好的方式,即我們不需要重寫現有的代碼,而是隻需要添加一些代碼,便可以將現有的後端模型代碼提到給前端使用。並且,與混淆後的 JavaScript 相比,它看上去更加安全 —— 學習成本更高一些。

引子 6:ComponentLess

在研究 Serverless 和微前端的期間,我突然有了一點想法,我對於客戶端領域的 Serverless 式架構有了一些基本的構想,叫:ComponentLess。儘管有了一些基礎的理念,但是還缺乏一個真實可用的 Demo,所以我並沒有定義出什麼是 ComponentLess。

起先,我以爲無代碼編程是一個 ComponentLess 方向,但是一研究發現並不是。無代碼編程傾向於可視化編程,而 ComponentLess 傾向於使用 DSL 編程。就這一點來說,我便偏向於使用 Web Components + WAM 技術來構建新的前端架構。

ComponentLess

從單體應用轉向微服務架構的一大特質是,組件(非單指 UI 組件,可以視爲服務)由函數調用轉向了 HTTP 調用。而 Serverless 進一步地將微服務的服務級別 HTTP 調用,細化爲函數級別的 HTTP 調用。

對於前端領域來說,也是如此。微前端將單體應用拆分一個個的獨立運行前端應用,我們可以隨意地組合這些應用。進一步地,結合諸如於 Web Component 這樣的組件級方案,便可以將拆分細分爲到 UI 組件的粒度。我們可以使用 (HTML Imports 已遺棄)script 標籤從遠程導入:

<script type="module" src="my-element.js"></script>
<my-element></my-element>

因此,在未來,不論是前端開發人員,還是開發人員,都可以通過集成組件的方式來開發應用。也就是說,我們只需要關注於編寫核心業務代碼即可,剩下的部分可以通過一些特殊的方式來實現。

DSL 抽象化代碼

ComponentLess + Serverless 是代碼即基礎設施開始的一個標誌。當代碼開始作爲基礎設施的一部分時,代碼便需要以某種方式才能組合到一起。在 Serverless Framework 中,開發人員通過配置接入服務端所使用的基礎信息。而 YAML 配置本身也是 DSL 的一種,缺乏靈活度,但是使用非常簡單。

事實上,這是兩個選擇:

  • 配置 + 編程語言。上手容易,遷移難

  • DSL。上手複雜,易於遷移

但是,無論如何我還是如何 DSL,它聽上去有着更豐富的 KPI。至於如何抽象化基礎設計代碼,可以參考《雲研發:研發即代碼》一文。

模型複用

不論是 ComponentLess 還是 Serverless,它們都是由函數、UI 組件變爲一個獨立可運行的單元。爲了與別人交互,它需要包含輸入和輸出。而輸入和輸出本身是需要一個數據模型作爲支撐的,以此才能完成整個系統的穩定性。

而這個話題已經回到我們開頭所討論的內容裏。

前後端分離將死?

在所有的引子裏, 我們已經準備了所有的論據,所以只需要:

  • 使用可以跨前後端的語言,構建領域模型

  • 將後端服務、前端設施細化爲更小的組件

  • 設計 DSL 將領域模型轉換到特定平臺的代碼

你就可以殺死前後端分離,就是這麼簡單。

前後端分離將死,不是現在,但是可能在五年後開始。

你說呢?

其它

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章