前端早早聊大會,前端成長的新起點,與掘金聯合舉辦,
本文 是前端早早聊的第 45 位講師 ,也是第六屆 - Serverless 專場,來自閑魚前端團隊的丹俠的分享 - 講稿簡要整理版(完整版含演示請看錄播視頻和 PPT):
概述
今天給大家分享一下閑魚如何在現有產品中落地 FaaS,希望我們的實踐可以給大家帶來借鑒和靈感,今天跟我一同分享的,還有我的同事,海潴,他是我們團隊的架構師,FaaS 層的通信架構就是由他設計并實作,在后續的 PPT 中,有關通信相關的技術點,會由他來帶給大家,
之前 5 場,大家已經聽到了像 Severless、云計算、云平臺等跟 FaaS 關系比較緊密的概念,接下來我和海潴講的內容里,不會涉及太多的基礎概念,我們的方案不限定具體某個技術堆疊或者是工具,也沒有要求使用特定的平臺及環境,我們會更傾向于解決具體業務問題,主要給大家分享一套設計思路和方法論,講的是在富互動場景的產品鏈路里,如何利用 FaaS,給現有的研發模式提供一些便利和想象空間,當然,FaaS 目前在前端領域,產生很多新的研發創新思路的同時,也是有一些不可忽視的問題存在,也是需要大家一起不斷思考和實踐,并逐漸完善,
閑魚的端技術和 FaaS 研發體系
首先讓大家了解下閑魚目前的端技術組成,閑魚給業內印象比較深的,可能是 Flutter 的研發體系,但是閑魚也有很多基于 Web 的應用場景,這些場景,還是基于前端比較熟悉的技術堆疊來實作的,比如 React,Vue,小程式,除了端內有不少 Web 的場景,在端外的投放,基本也都是基于前端 H5 技術來實作的,

大家知道,Flutter 官方提供的編程語言是 Dart,所以閑魚在 FaaS 體系構建之初,是把 Dart 作為 FaaS 層的編程語言,這樣客戶端就可以實作一體化的研發體驗,這跟前端使用 TS 結合 Node 是一樣的道理,在 FaaS 的實踐程序中,前端又是跟客戶端共享一套 FaaS 技術框架,所以為了讓前端同學也可以實作一體化的編程體驗,前端在工程上做了一層語法轉化,實作了用 TS 編程,構建時轉化成 Dart 的工程能力,這樣也就間接實作了一體化的研發體驗,
所以目前閑魚 FaaS 研發的技識訓境就是:客戶端和前端共用同一套 FaaS 框架, 我們的 FaaS 函式,是部署在一個叫蓋亞的研發部署平臺,這個平臺是阿里內部面向函式的研發運維平臺,這個對各位同學來說并不是唯一選擇,大家可以使用自家公司的,或者自己熟悉的平臺來部署 FaaS 函式,
傳統的 MVVM 的研發模式
先回顧一下傳統的前端研發模式,大部分同學,對 MVVM 應該比較熟悉,像 React、Vue 都是擅長使用這套模式管理前端代碼的,

重端側的研發程序中,會有三個概念:Model、View、ViewModel,這三個都是在端側定義并管理的,統一管理的好處是一個工程維護一套業務,研發效率比較高;缺點就是代碼管理成本比較高,復雜業務需要借助一些端側代碼框架,比如 Redux,或者像螞蟻先后推出開發框架 Dvajs、Umijs,都是幫助大家去管理端側代碼,但即使有這些框架的協助,有時對 UI 狀態和業務狀態的管理也會比較混亂,這跟業務的多變性、開發人員的綜合素質以及開發團隊對代碼管理的嚴格程度都有關系,
重端側的研發模式,有個問題就是技術堆疊的適配和遷移成本較高,比如從 Vue 遷移到 React 或者是小程式,不光要適配 UI 層,還需要依賴于之前的邏輯層抽象的比較好,否則這個程序肯定是比較痛苦的,這也是近幾年小程式比較火的一個原因之一,各家 App 都支持了小程式,可以做到一次開發,多端投放的效果,但是并不是所有的場景都是適合用小程式來實作的,
重端側研發還有一個問題是前端對介面的控制能力不足,服務端會針對業務提供定制的介面,復用度較差,資料格式轉化到 Model 的程序中,還需要端側做一些適配、轉化及容錯處理,
當然前端也可以通過 BFF 層,Backend For Frontend,顧名思義,服務前端的后端,但這是一層是非常輕量級的服務層,主要做的是資料的重組,欄位補全、格式校驗、標準化等能力,最終的資料能力還是沒有大的改變,只是把原來服務端不擅長的那部分資料處理作業給攬過來了,
基于 FaaS 的研發模式
基于 FaaS 的研發模式,它給前端的研發模式提供了新的思路,還是熟悉的 MVVM 研發模式,只是端側只剩下了View、把 Model 和 ViewModel 都遷移到了 FaaS 側,大部分更新UI的作業通過事件通信來實作,

可以看到這種模式下,介面的服務能力也有一定的變化,也就是 FaaS 函式跟 Severless 的配合,服務端會提供領域級別的服務,這些領域服務一般設計成可復用,不跟具體的業務邏輯耦合,對業務的邏輯處理和資料的編排和重組,都是放在 FaaS 層,這樣前端的邏輯也放在了 FaaS,FaaS 的能力和職責是被放大了,前端可做的事情和想象空間也變得更大,
當然 FaaS 本身是一種無狀態的服務,在邏輯處理程序中如果需要使用一些狀態存盤的能力,就要借助于 BaaS 才能完成一些狀態保存的能力,
而且業務邏輯和資料編排的能力放在一起,可以讓整個業務邏輯不再割裂,可以更好地進行抽象設計,甚至編排,
FaaS 在淘系的應用之一(導購)
這里舉例一個淘系最早應用 FaaS 的場景:導購,導購的端側場景特點是以展示為主,一般用戶的行為主要是滾動螢屏瀏覽、點擊商品跳轉,所以它主要關注的技術點是:
-
商品個性化推薦演算法
-
商品屬性透出(資料補全)
-
排序規則
-
資料的分頁獲取
-
資料或者權益的投放排期

所以基于這樣的業務場景,FaaS 側要做的主要作業是對服務的編排,
-
欄位映射:FaaS 側的資料模型跟端側的 View 模塊之間的欄位映射,
-
模板管理:對某個業務模塊的實體管理
-
連接器:連接器是一些
if else邏輯或者是一些具體的行為(比如資料請求)
模板管理和連接器是可視化編排的重要素材
而資料編排,是下游的服務能力,是給 FaaS 層提供資料的資料中臺能力,不包含在 FaaS 應用層,所以我們之前進行調研的時候,也是考慮導購的 FaaS 業務模型,是否可以套用到我們閑魚產品的 FaaS 模型中呢?
接下來我拿閑魚的回收、寄賣業務來進行例舉分析一下,
閑魚的具體業務分析
以閑魚的回收寄賣業務為例:這是兩條相似的產品鏈路,他們共用 類目選擇、問卷估價 等業務場景,但回識訓多出 信用評估 和 代扣簽約 這兩個業務場景,

同時,回收寄賣涉及的類目也比較多,包括手機數碼、大家電、圖書、舊衣等,最后又有不同的服務商對接,也會影響流程頁中的渲染和互動方式,而且不同的類目,對應不同的業務方和運營同學,他們的產品策略也會有些差異,所以看似差不多的鏈路里,包含了很多的差異性因子,如果把這些因子做統一的資料處理和服務編排,可想而知,前端的邏輯會變得非常復雜,并且難以維護,所以現有的 FaaS 框架不足以管理復雜的產品鏈路,
綜合分析下來,是缺少兩個關鍵的組成:
-
缺少富互動場景的通信方案,因為復雜互動,用戶的行為回應,端側只剩下 View,無法進行直接處理,是需要頻繁跟 FaaS 進行通信才能實作狀態的變更,
-
缺少一套 FaaS 側的業務框架來抽象和管理整個業務,
產品互動與 FaaS 的通信模式
所以基于之前的分析和事后的思考,我們設計出一個模型,這個模型里,最關鍵的兩個概念是:業務框架 FaaS Story 和資料處理框架 Nexus,這兩個概念都在 FaaS 側進行抽象和實作,然后通過一個我們命名為 Logic Engine 的模塊,跟端側進行通信,

可以看到圖中的整個資料鏈路:端側的 page state,管理了組件的屬性和事件配置,這里的 Action 并不是一個 Function,而是對事件函式的一個配置,端側的 Logic Engine 會統一決議這個配置,并呼叫統一的事件函式,組裝成統一的資料包,向 FaaS 發起請求,
資料到了 FaaS 側,還是由 FaaS 側的 Logic Engine 進行資料包決議,路由匹配到對應的函式進行處理,函式基于 FaaS Story 這套業務框架進行設計,最終把處理后的資訊再次通過 FaaS 側的 Logic Engine 打包回傳給端側,端側的 Logic Engine 進行決議處理,最終回應具體的 Action(比如更新頁面狀態,或者是發起另一次資料通信,又或者是呼叫某個容器的 API 等),
業務模型 FaaS-Stroy

基于這樣的模型,我們把之前回收寄賣業務映射過來,整個業務,我們我們就定義為一個故事(Story),這個故事有多個 Scene 組成,
這里 Scene 的概念并不是一個單頁的概念,而是根據業務來進行定義的,一個頁面可能會承載多個 Scene,后面也會例舉多 Scene 的業務場景,
一個 Scene,主要由資料模型 Model、編排邏輯函式 Convertor 以及渲染邏輯函式 Render 組成,Model 映射原始的介面資料(也就是服務端的領域模型),一個場景函式中,可能會獲取多個 Model;Converter 處理業務邏輯并輸出跟端側 page state 一致的結構,它的作用也就是 MVVM 結構中的 ViewModel;Render 運行在端側,最終渲染頁面并掛載事件,我們的 Nexus 框架實作了統一的端側事件模型,UI 組件的事件函式同樣可以使用這個事件模型,也就是之前提到的 Action 配置,這樣組件從初始化到互動都可以由 FaaS 控制,
Story 的函式管理方式類似于端側 APP 的概念,可以全域上對這些 FaaS 函式進行管理和編排,比如提供一套統一的配置,來定義路由,以及處理函式之間的流轉關系,
一個多場景的頁面

之前在介紹閑魚業務的時候也提到了,我們有很多的類目,不同的類目,雖然主鏈路基本一致,每個類目對應的品類屬性差異還是比較大的,這會影響頁面的渲染和互動,同時不同類目對應的業務方也不同,產品策略和營銷策略都會有差異,比如有些類目的下單是需要上門取件,有些類目的評估流程中是需要拍照鑒定,有些類目在某個節日要做個特殊的活動等等,
所以我們從類目的緯度橫向分割了 FaaS 函式,不同類目的個性化邏輯在自己的 FaaS 函式中獨立管理,相互之間互不干擾,他們的公共部分被抽象到了公共類別庫,或者一些工具類別庫,
Nexus 框架
Nexus 是一種一體化應用開發協議,用于解決 UI / 邏輯 分離下,端 / FaaS 跨系統函式呼叫的問題,在它上面可以長很多的,基于特定業務場景的框架,比如上面介紹的 Story,還有另外一個同事撰寫的基于 fish-redux-view 結合 Logic Engine 的 Nexus Framework,
首先說一下為什么會有這樣一個協議:大家可以看上圖左邊,在端側長時間的發展程序中,大家都在致力于解決 UI/邏輯 如何更好得分離的問題,不管是最早的 MVC,還是 MVP,以及 MVVM,都想要解決這個問題,但是無論端側如何解決,嚴格得執行各種框架,被分離的邏輯都只是那部分存在于端側的業務邏輯,
我們如果把目光放得更大一些,會發現,端側的邏輯是分離出去了,但是網關層的呢?實際上大部分端側請求的介面,不管是下發資料,還是寫入資料,都不是直接面向領域層的呼叫,而是會經過網關再進行一次邏輯處理,最典型的比如,頁面資料請求,幾乎很少有頁面去直接面向領域層的多個介面直接進行資料請求,那么為什么呢?因為領域是面向具體的領域問題進行的設計,而端側需要是面向UI展示進行設計,這兩邊的設計天然是后鴻溝的,簡單一點說,領域層下發的很多資料,端側是不要的,而通常一個領域介面無法滿足一個頁面所需要的所有資料,所以才需要經過網關層進行處理,
所以大家發現沒有,不管端側怎么做分離,總會有一部分邏輯存在于網關層,那么如果網關層把所有領域資料都處理成 VO 給到端側好不好?當然好了,但是后端的同學就不樂意了,一來這個 UI 不是我寫的,我還得跟你溝通你需要點啥,每次 UI 改動還得我跟著改,二來寫這些東西我也沒啥成長,所以更多的時候,是端側一部分處理邏輯,網關層再做一部分,
這就帶來了一個完整業務中 UI / 邏輯 實際并不分離的問題,同時也會造成前后端在溝通、協作上的諸多問題,基于以上,我們思考的是,那不然就不要后端同學來寫網關層了,用 FaaS 讓前端的同學上去寫,自己要什么自己最清楚,也少了協同和溝通,提升效率,還能公用一部分的代碼,這就是一體化編程模型的來源,也就是左邊的圖所表達的,
現在我們打算讓 FaaS 來處理所有的業務邏輯,還有兩個問題需要解決:
-
事實上,業務如何被驅動,都來自于端側的事件,那么 FaaS 如何感受到來自事件的驅動
-
FaaS 的處理結果,最終是要在端側產生 Effect 才行,而這些 Effect 基本上只能由端側來執行,
所以我們一定是需要一個通信協議,來讓端能夠呼叫到 FaaS 上的邏輯函式,也能讓 FaaS 能夠呼叫端側的函式產生 Effect,
整個協議在資料部分基于 NexusBinderAction 體系,將每一個 Action 映射到一個邏輯 Handler 上,Action 是一種資料資訊載體,它內部的資訊實際上體現的是呼叫一個函式所需要的“函式簽名”和“函式入參”兩類關鍵資訊,某種意義上來說,它也是一種“跨系統的函式呼叫”協議,
但是它與傳統的 RPC 協議之間,有什么區別,或者特點呢?這與端側 UI 編程的特征有關,在端側編程中,我們發現,有三類的函式呼叫是可以被歸納的: 1、呼叫一個后端函式(即執行一段業務邏輯) 2、呼叫端側的公共能力函式 3、修改資料并重新渲染,
這三類函式是端側編程中被大量重復使用的函式,尤其是第三類,UI = F(state),這些的背后都隱含著一個操作,就是業務邏輯操作,不管是由某一個動作觸發的狀態改變,還是網路請求,還是諸如 dialog,頁面跳轉,這背后都需要一些邏輯代碼來進行判斷和修改,
基于對端側編程中常用的函式呼叫的抽象,我們得到了一個 Action 的有限集合,以及支撐這個協議的庫 Logic Engine,這里的“有限”非常重要,如果開發者在每次開發一個頁面的時候都需要重復得定義大量的 Action 和 Handler,那么一來會增加開發者的負擔,二來會出現很多重復的代碼,也不符合軟體開發中 DRY 的原則,
這樣開發方式實際又回到了原始的 Req -> Do something -> Effect,這條路上,所以整個 Action 體系中包含的種類一定要非常少,但它卻可以支持大部分的端側編程中對邏輯函式呼叫的需求,也就是現在 Nexus 協議的樣子,

根據上面對 Nexus 協議的抽象,可以很明顯得看到,無論運行在什么環境中,有兩種 Action 的處理邏輯是大體上不變的:
-
對于 remote - req 來說,它的邏輯就是決議出 Action 中與請求相關的 apiname、apiversion 以及 params 部分,然后呼叫一個外部注入進來的網路請求函式把這個呼叫發送到 FaaS 上去,可以看到,呼叫一個遠程的 FaaS 函式的程序大體上是不變的,也就是 remote - req,Handler 的邏輯可以內置在 Engine 中的的基礎,
-
從一個 state change 的 Action 中提取新的 UI state,并提交給 UI 進行更新,
另外兩種內置的通用 handler 并不來自于 nexus 協議的抽象,而是脫胎于日常開發,
-
state-diff,它的邏輯是把一個 json patch 合并到當前的 state 中,產生一個新的 state 并提交給 UI 去更新,因為整個端呼叫 FaaS 的程序中,FaaS 作為無狀態服務本身并不存盤狀態,那么所有計算所需要的狀態資訊都會由端發送到 FaaS 上,但是 FaaS 在計算完新的 state 之后,并沒有必要全量得回傳狀態資料,本身端側就保存有 state的原始版本,那么我們考慮,是不是可以通過 diff 的方式讓端側合成一份新的 state,可以有效得減少遠程呼叫的下行流量,為此,我們根據 RFC6901/6902 中關于 json pointer 和 json patch 的規范,在 dart 上實作了json_patch 庫,我們在閑魚內的“下單頁”做了測驗,“修改地址”操作將會涉及“地址資訊”、“紅包”、“運費”、“最終價格”資料,使用 diff 后大約可以減少 50% 的下行流量,
-
在一體化開發中,我們發現,經常會需要在端側順序得執行多個 action,最簡單的一個例子,端側在進行 FaaS呼叫的時候通常都會 show 一個 progress 來阻塞用戶的后續操作,那么當 FaaS 執行完業務邏輯并回傳準備好的 state 資料之后,會通過 engine 讓頁面進行繪制,但是慢著,大家有沒有發現哪里不對勁,這個 progress 還 show 在那里,誰來執行 hide progress 的操作呢?所以這種時候 FaaS 會需要下發一個batch型別的 action,順序得讓端側的 engine 執行 UI update 和 hide progress,
當然開發者可以基于 batch 做更多復雜得 action 編排,所以本質上 batch 型別的 action 提供給開發者一個順序編排執行邏輯的能力,避免了多次來回請求的開銷,也會讓執行邏輯變得更加得清晰,

上一頁我們已經把 nexus 協議想要解決的問題說清楚了,那么作為具體執行協議的 Engine,它所需要提供的功能就非常明顯了,首先它必須能夠執行一種協議到具體邏輯代碼的映射功能,這里面包含了協議的決議、函式的映射、函式的執行,最后我們還需要給它加上執行背景關系的管理功能,上圖中的紅色部分,是 Engine 對外提供的功能,包括允許外部進行函式注冊,以及外部可以呼叫某個 API 來進行函式呼叫,
綠色的部分為 Engine 內部需要提供的功能,包括協議的決議、目標函式的匹配和執行背景關系的管理,函式注冊、執行函式、決議訊息、函式匹配這四個功能是比較容易想到的,對于執行背景關系管理功能,Engine 實際上除了系結了 Action 這種協議載體之外,并不系結任何的框架或者端側環境,也就是說對 Engine 來說,你運行在 android native 還是 flutter 環境,對它都沒有影響,它依然可以完成自己對接 Action 協議的使命,這種設計是為了盡可能得給 engine 解綁,也可以釋放 Engine 的能力以提供業務方進行上層框架的自定義,
最后也是這里把它單獨領出來的一塊,橘黃色的部分,“內置的通用函式”,也就是上面所說的 remote-req、state-change、state-diff 和 batch,
代碼演示
端側的邏輯代碼

端側的 UI 跟 ViewModel 的資料模型映射

FaaS 側的邏輯
研發一體化及熱部署

問題思考
Q:端側互動的時延
A:減少通信次數,不涉及業務邏輯的行為,在端側完成,比如曝光、點擊埋點,
Q:端側通信的時序
A:通過,控制端側的請求順序,來保證資料的有效性:阻塞互動(Loading),關于異步時序的問題,我們內部也有一些討論,比如可以通過 CAS(Compare and Swap)或者事務的方式來保證通信資料的有效性,** **
-
Q:會話狀態的保存
-
借助獨立的 BaaS 服務存放需要的資料,需要引入 BaaS 服務,應用成本相對高一些,而且快取的有效期不太好控制,存盤量也比較大,但是穩定性和靈活性較強,
-
輕量的資料存放能力可以利用端側的頁面生命周期,我們在 Nexus 的通信協議里可以自定義需要頁面生命周期內持久化的資料,并且在請求的時候按需傳遞這些資料,
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
* **技術成長緩慢,遇到職業瓶頸**
前端知識迭代快,但自己前端知識掌握不成體系,無法快速掌握應用,成長緩慢
* **想跳槽大廠,但沒方法也沒方向**
一直重復初級的作業內容,專案經驗缺乏,達不到一線大廠的能力要求
在這里送大家一波福利
在這里特地講我自己這兩個月整理的相關面試題分享給大家,免費獲取哦~


獲取方式:
一、搜索QQ群,前端學習交流群:954854084
二、點擊加入,與前端大牛一起進步!
三、QQ掃描下方二維碼!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/21630.html
標籤:Html/Css
