Nexus协议,闲鱼一体化开发的幕后玩家
Serverless是這幾年興起的一個概念,Serverless可以幫助開發(fā)者減輕甚至擺脫傳統(tǒng)后端應(yīng)用開發(fā)所需要的服務(wù)器設(shè)備的設(shè)置和運維工作,并以服務(wù)接口的方式為開發(fā)者提供所需要的功能。它希望開發(fā)者更加專注于應(yīng)用邏輯本身,而不是被瑣碎的基礎(chǔ)設(shè)施細(xì)節(jié)所”綁架“。
而FaaS是Serverless的一種比較好的實踐方式。自從亞馬遜的AWS在14年推出Lambada之后,FaaS這種后端發(fā)開方式迅速被大家接受并應(yīng)用。它擁有更加輕量、事件驅(qū)動的特點。
閑魚選擇使用Flutter + FaaS體系來實現(xiàn)云端一體化的開發(fā)模式也正是看中了Flutter和FaaS技術(shù)本身都是輕量的、面向應(yīng)用的技術(shù)。與一體化本身希望開發(fā)者盡可能關(guān)注整體的業(yè)務(wù)邏輯非常契合。
Flutter + FaaS的云端一體化開發(fā)模式已經(jīng)在閑魚中被使用了一段時間。同事們之前也有過一些文章來介紹一體化開發(fā)在閑魚演進(jìn)和落地的過程。在這些文章中,都提到了Logic_engine、Nexus_Framework等字眼。它們一直默默得在業(yè)務(wù)開發(fā)同學(xué)的身后,支撐著一體化的落地和發(fā)展。
今天,我們就來介紹一下這個一體化的幕后推手--- Nexus協(xié)議,以及基于它衍生出來的框架和庫。
Nexus協(xié)議的由來
一開始說要做Flutter + FaaS一體化開發(fā)的時候,我們對”一體化“這三個字的認(rèn)知相對比較模糊,只是知道端側(cè)的同學(xué)可以用Dart這門語言來寫FaaS函數(shù),這樣的語言上的一體化。對于FaaS所能做的事,也僅僅停留在前端實施已久的BFF層面。那個階段,對于要做些什么,還是比較迷茫的。
阿里的同學(xué)經(jīng)常說:
你不知道能做些什么,是因為想得還不夠清楚
本著這樣的想法,一體化小組經(jīng)常聚在一起討(liao)論(tian),不管Flutter + FaaS有沒有一體化,反正我們小組先”一體化“了再說。
整個一體化的概念在討論中慢慢變得清晰,首現(xiàn)我們對于一體化進(jìn)行了定義,它應(yīng)該是這樣的一個形態(tài):
最終達(dá)到開發(fā)Flutter頁面和開發(fā)FaaS無明顯gap,像在開發(fā)一整個應(yīng)用的體驗。
語言一體化
由于Flutter本身是以Dart作為開發(fā)語言,那么我們自然也選擇它作為FaaS的開發(fā)語言。閑魚在之前已經(jīng)實踐過了Dart Server這種開發(fā)方式,在Dart runtime、相關(guān)開發(fā)工具方面有非常深厚的沉淀。組內(nèi)的同學(xué)將這個runtime經(jīng)過修改之后移植到了集團的FaaS平臺Gaia上。
開發(fā)同學(xué)不僅可以在端上使用hotreload進(jìn)行頁面快速調(diào)試,同樣可以使用這項功能在FaaS平臺上快速部署與調(diào)試,極大得提升了部署和調(diào)試的體驗。
開發(fā)模式與架構(gòu)一體化
在語言一體化的基礎(chǔ)上,我們同樣希望開發(fā)者在開發(fā)Flutter頁面和FaaS函數(shù)的時候,有著相同的心智。
在傳統(tǒng)的前后端分離開發(fā)模式中,端側(cè)的開發(fā)與后端開發(fā)有著比較明顯的不同,端側(cè)通過和后端約定數(shù)據(jù)結(jié)構(gòu)的方式獲取用于頁面渲染和處理用戶輸入的數(shù)據(jù)。這種模式下,雙方僅對數(shù)據(jù)進(jìn)行了依賴,各自屬于不同的系統(tǒng)。
在一體化的模式下,我們希望開發(fā)者能把端側(cè)頁面和FaaS函數(shù)當(dāng)成同一個系統(tǒng)來看待。它們應(yīng)該是一個有機的整體,共同完成一個頁面的功能。在職責(zé)上,端側(cè)代碼主要處理UI的渲染,FaaS函數(shù)主要處理邏輯與副作用。
開發(fā)者應(yīng)該可以像在一個系統(tǒng)內(nèi)一樣進(jìn)行相互的調(diào)用,就好像你在本地調(diào)用一個對象的函數(shù)那樣自然。
但顯然,端與FaaS現(xiàn)實中還是屬于兩個系統(tǒng)的,如何能夠做到像調(diào)用函數(shù)一樣自然呢?它們之間又以什么樣方式進(jìn)行觸發(fā)呢?
事件驅(qū)動
在常見的客戶端頁面開發(fā)過程中,端側(cè)邏輯總是圍繞著三個操作在進(jìn)行,不管代碼多少,寫成什么樣,這些邏輯代碼最終都會產(chǎn)生:
這樣的三個效果。
比如頁面的初始化過程,就是典型的?remote req=>state change=>render的過程。
當(dāng)然這是精簡之后的流程,由于一個http請求回來后的數(shù)據(jù)并不能直接作用于頁面state,通常還需要先對數(shù)據(jù)進(jìn)行一下處理。
這些動作都會由一個明顯的事件來觸發(fā),通常來說是用戶進(jìn)行的交互事件,不論是請求、頁面渲染,或者彈出一個Dialog,進(jìn)行一次頁面間的跳轉(zhuǎn),它們都不會自發(fā)得進(jìn)行(否則看上去有些詭異)。
而一個端上的事件,也可能會傳導(dǎo)到FaaS上,來驅(qū)動FaaS上的邏輯函數(shù)對這個事件進(jìn)行處理。當(dāng)我們把端和FaaS看成一個整體的時候,這個事件就是在一個系統(tǒng)中流轉(zhuǎn)。
于是我們總結(jié)出了第一張圖:
邏輯歸一與互相調(diào)用
在傳統(tǒng)的開發(fā)模型下,頁面邏輯、狀態(tài)、展示三者之間的流轉(zhuǎn)是在端側(cè)進(jìn)行的,后端負(fù)責(zé)了一部分的邏輯處理(通常這部分邏輯是需要對于各種領(lǐng)域接口進(jìn)行調(diào)用)。
而還有一部分領(lǐng)域數(shù)據(jù)到UI state的一些轉(zhuǎn)換邏輯,則是端、后端都會做一部分。這兩部分邏輯分散在兩端,通過某種弱的協(xié)議進(jìn)行連接。
引入了FaaS之后,自然可以把邏輯放到FaaS上實現(xiàn),那么請求回來的數(shù)據(jù)理論上可以直接作用于頁面渲染。
如果我們再進(jìn)一步,不如直接讓FaaS來指揮端上的UI怎么做好了。就好像FaaS是一個導(dǎo)演,而端側(cè)UI是一個提線木偶,FaaS怎么說,UI怎么變。這樣把業(yè)務(wù)相關(guān)的邏輯都搬上FaaS去,端側(cè)專注于如何將state渲染到UI上,兩個部分組合成為一個頁面整體,豈不是更加一體化?
于是我們有了第二張圖:
邏輯歸一到FaaS之后,FaaS已經(jīng)可以跳過傳統(tǒng)的弱協(xié)議,直面端側(cè)頁面了。對于后端來說,一個請求可以映射到一個具體的處理函數(shù)。我們可以不太嚴(yán)謹(jǐn)?shù)谜f,一直以來,客戶端是有調(diào)用后端函數(shù)的能力的。那么既然我們現(xiàn)在想讓FaaS來指揮端上的UI的變化,勢必也要讓FaaS具有調(diào)用端側(cè)函數(shù)的能力。
我們把一次調(diào)用抽象為一個Action,每一個Action的背后都有一個特定的函數(shù)為它提供真實的邏輯,也就是說,一個特定的Action,可以用來描述一個特定的函數(shù)與函數(shù)背后的邏輯代碼,Action本身就是一個函數(shù)簽名。
那么端側(cè)需要提供多少函數(shù)給FaaS呢?當(dāng)邏輯歸一到FaaS上之后,我們會發(fā)現(xiàn)端側(cè)的大部分實現(xiàn)都圍繞著兩部分進(jìn)行:
UI的展現(xiàn)是”純“的,它基本上都可以由一個頁面的state數(shù)據(jù)來描述,也就是說,大部分情況下,一個state就描述當(dāng)前UI的狀態(tài)。那么對于端側(cè)來說,只需要提供一個state到UI的映射函數(shù),理論上就可以讓FaaS具有更新端側(cè)UI的能力。也就是說,假設(shè)FaaS函數(shù)想要更新頁面,只需要下發(fā)一個state change的Action,帶上頁面所需要的所有state數(shù)據(jù),就可以達(dá)到效果。現(xiàn)實場景中,某些頁面的state數(shù)據(jù)可能巨大無比,不好直接傳輸。我們做了一個JsonPatch庫來解決這種場景下的問題,如果FaaS只修改了state中的一部分?jǐn)?shù)據(jù),則可以通過下發(fā)patch的方式由端來合成一個新的、完整的state。
對于副作用處理的部分,大部分的副作用都來自于比如Dialog,頁面跳轉(zhuǎn)等。這類操作是通用的,有共性的,我們同樣使用一類叫做native api的Action來描述,這些Action與它們背后的處理函數(shù),將面向所有使用了Nexus協(xié)議的頁面提供這樣的能力。
這兩類函數(shù)的抽象,已經(jīng)可以cover 80%的頁面需求了,而剩下的20%復(fù)雜交互的頁面,我們提供custom類型的Action來讓開發(fā)者進(jìn)行自定義。
總結(jié)
通過對于一體化的定義,以及拆分了需要的功能之后,Nexus協(xié)議就破土而出了。它是一個
基于Action的,提供Client/FaaS系統(tǒng)間調(diào)用的協(xié)議
Action的調(diào)度者-LogicEngine
我們有了可以用于兩個系統(tǒng)間進(jìn)行相互調(diào)用的協(xié)議,相當(dāng)于我們有了一門語言,這門語言只有我們自己認(rèn)識,所以還需要一個解釋器來執(zhí)行它。
LogiceEngine就是這樣的一個執(zhí)行器。
如果我們給它下一個定義,LogicEngine就是一個:
基于Nexus的Action協(xié)議的,提供Client、FaaS之間相互調(diào)用能力的庫
Engine本身不提供任何具體的邏輯能力,所有的邏輯能力都需要通過函數(shù)的形式注冊到Engine中,并綁定到一個具體類型的Action上去。
所以Engine的設(shè)計相對明確:
開發(fā)者通過post函數(shù)來發(fā)出一個Action,相當(dāng)于通過Engine調(diào)用了一個函數(shù),這個函數(shù)可能在本地,也可能在FaaS上。這并不是開發(fā)者需要關(guān)心的內(nèi)容。甚至于,這個調(diào)用會產(chǎn)生什么影響,也不是當(dāng)前調(diào)用者所需要關(guān)心的。
因為調(diào)用的發(fā)起者實際只是發(fā)出了自己的一個意圖,比如在實踐中,我們會在用戶按下"下單"按鈕的時候提交一個意圖(Action)。
這個意圖最終會產(chǎn)生什么樣的UI變化,FaaS會通過一個state change或者native api形式的Action直接調(diào)用到具體的實現(xiàn)函數(shù)去。
而端側(cè)注冊在Engine的函數(shù)不會很多,前面有提到過,大部分UI編程中的邏輯,都可以被歸納為三類。所以我們大多數(shù)時候只需要注冊三種固定類型的處理函數(shù)就可以了。
展望
有了Nexus協(xié)議、三類通用處理函數(shù)的抽象和LogicEngine,意圖=>作用中間過程就可以變得透明。
但這些遠(yuǎn)遠(yuǎn)不夠,
后續(xù)我們還希望對協(xié)議進(jìn)行升級,從現(xiàn)有的json提升到一個更加類型安全的協(xié)議上。
我們也希望有一個IDL工具,可以自動得將面向Action調(diào)用轉(zhuǎn)換成面向接口調(diào)用,讓開發(fā)者有更好的調(diào)用體驗。
我們還希望改變現(xiàn)有單向的請求=>應(yīng)答模型,讓FaaS可以自由得調(diào)用端側(cè)函數(shù),再次突破兩個系統(tǒng)之間的gap,變得更加一體化。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的Nexus协议,闲鱼一体化开发的幕后玩家的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flink 与 Hive 的磨合期
- 下一篇: 日均万亿条数据如何处理?爱奇艺实时计算平