天天看點

Nexus協定,閑魚一體化開發的幕後玩家

作者:海潴

Serverless是這幾年興起的一個概念,Serverless可以幫助開發者減輕甚至擺脫傳統後端應用開發所需要的伺服器裝置的設定和運維工作,并以服務接口的方式為開發者提供所需要的功能。它希望開發者更加專注于應用邏輯本身,而不是被瑣碎的基礎設施細節所”綁架“。

而FaaS是Serverless的一種比較好的實踐方式。自從亞馬遜的AWS在14年推出Lambada之後,FaaS這種後端發開方式迅速被大家接受并應用。它擁有更加輕量、事件驅動的特點。

閑魚選擇使用Flutter + FaaS體系來實作雲端一體化的開發模式也正是看中了Flutter和FaaS技術本身都是輕量的、面向應用的技術。與一體化本身希望開發者盡可能關注整體的業務邏輯非常契合。

Flutter + FaaS的雲端一體化開發模式已經在閑魚中被使用了一段時間。同僚們之前也有過一些文章來介紹一體化開發在閑魚演進和落地的過程。在這些文章中,都提到了

Logic_engine

Nexus_Framework

等字眼。它們一直默默得在業務開發同學的身後,支撐着一體化的落地和發展。

今天,我們就來介紹一下這個一體化的幕後推手--- Nexus協定,以及基于它衍生出來的架構和庫。

Nexus協定的由來

一開始說要做Flutter + FaaS一體化開發的時候,我們對”一體化“這三個字的認知相對比較模糊,隻是知道端側的同學可以用Dart這門語言來寫FaaS函數,這樣的語言上的一體化。對于FaaS所能做的事,也僅僅停留在前端實施已久的BFF層面。那個階段,對于要做些什麼,還是比較迷茫的。

阿裡的同學經常說:

你不知道能做些什麼,是因為想得還不夠清楚

本着這樣的想法,一體化小組經常聚在一起讨(liao)論(tian),不管Flutter + FaaS有沒有一體化,反正我們小組先”一體化“了再說。

整個一體化的概念在讨論中慢慢變得清晰,首現我們對于一體化進行了定義,它應該是這樣的一個形态:

  1. 語言一體化
  2. 開發模式與架構一體化

最終達到開發Flutter頁面和開發FaaS無明顯gap,像在開發一整個應用的體驗。

由于Flutter本身是以Dart作為開發語言,那麼我們自然也選擇它作為FaaS的開發語言。閑魚在之前已經實踐過了Dart Server這種開發方式,在Dart runtime、相關開發工具方面有非常深厚的沉澱。組内的同學将這個runtime經過修改之後移植到了集團的FaaS平台Gaia上。

開發同學不僅可以在端上使用hotreload進行頁面快速調試,同樣可以使用這項功能在FaaS平台上快速部署與調試,極大得提升了部署和調試的體驗。

在語言一體化的基礎上,我們同樣希望開發者在開發Flutter頁面和FaaS函數的時候,有着相同的心智。

在傳統的前後端分離開發模式中,端側的開發與後端開發有着比較明顯的不同,端側通過和後端約定資料結構的方式擷取用于頁面渲染和處理使用者輸入的資料。這種模式下,雙方僅對資料進行了依賴,各自屬于不同的系統。

在一體化的模式下,我們希望開發者能把端側頁面和FaaS函數當成同一個系統來看待。它們應該是一個有機的整體,共同完成一個頁面的功能。在職責上,端側代碼主要處理UI的渲染,FaaS函數主要處理邏輯與副作用。

開發者應該可以像在一個系統内一樣進行互相的調用,就好像你在本地調用一個對象的函數那樣自然。

但顯然,端與FaaS現實中還是屬于兩個系統的,如何能夠做到像調用函數一樣自然呢?它們之間又以什麼樣方式進行觸發呢?

事件驅動

在常見的用戶端頁面開發過程中,端側邏輯總是圍繞着三個操作在進行,不管代碼多少,寫成什麼樣,這些邏輯代碼最終都會産生:

  1. 發起一個網絡請求(remote req)
  2. 調用一個公共函數(native api)
  3. 修改頁面資料并渲染(state change)

這樣的三個效果。

比如頁面的初始化過程,就是典型的

remote req

=>

state change

render

的過程。

當然這是精簡之後的流程,由于一個http請求回來後的資料并不能直接作用于頁面state,通常還需要先對資料進行一下處理。

這些動作都會由一個明顯的事件來觸發,通常來說是使用者進行的互動事件,不論是請求、頁面渲染,或者彈出一個Dialog,進行一次頁面間的跳轉,它們都不會自發得進行(否則看上去有些詭異)。

而一個端上的事件,也可能會傳導到FaaS上,來驅動FaaS上的邏輯函數對這個事件進行處理。當我們把端和FaaS看成一個整體的時候,這個事件就是在一個系統中流轉。

于是我們總結出了第一張圖:

Nexus協定,閑魚一體化開發的幕後玩家

邏輯歸一與互相調用

在傳統的開發模型下,頁面邏輯、狀态、展示三者之間的流轉是在端側進行的,後端負責了一部分的邏輯處理(通常這部分邏輯是需要對于各種領域接口進行調用)。

而還有一部分領域資料到UI state的一些轉換邏輯,則是端、後端都會做一部分。這兩部分邏輯分散在兩端,通過某種弱的協定進行連接配接。

引入了FaaS之後,自然可以把邏輯放到FaaS上實作,那麼請求回來的資料理論上可以直接作用于頁面渲染。

如果我們再進一步,不如直接讓FaaS來指揮端上的UI怎麼做好了。就好像FaaS是一個導演,而端側UI是一個提線木偶,FaaS怎麼說,UI怎麼變。這樣把業務相關的邏輯都搬上FaaS去,端側專注于如何将state渲染到UI上,兩個部分組合成為一個頁面整體,豈不是更加一體化?

于是我們有了第二張圖:

Nexus協定,閑魚一體化開發的幕後玩家

邏輯歸一到FaaS之後,FaaS已經可以跳過傳統的弱協定,直面端側頁面了。對于後端來說,一個請求可以映射到一個具體的處理函數。我們可以不太嚴謹得說,一直以來,用戶端是有調用後端函數的能力的。那麼既然我們現在想讓FaaS來指揮端上的UI的變化,勢必也要讓FaaS具有調用端側函數的能力。

我們把一次調用抽象為一個

Action

,每一個

Action

的背後都有一個特定的函數為它提供真實的邏輯,也就是說,一個特定的

Action

,可以用來描述一個特定的函數與函數背後的邏輯代碼,

Action

本身就是一個函數簽名。

那麼端側需要提供多少函數給FaaS呢?當邏輯歸一到FaaS上之後,我們會發現端側的大部分實作都圍繞着兩部分進行:

  1. UI展現
  2. 副作用處理

UI的展現是”純“的,它基本上都可以由一個頁面的state資料來描述,也就是說,大部分情況下,一個state就描述目前UI的狀态。那麼對于端側來說,隻需要提供一個state到UI的映射函數,理論上就可以讓FaaS具有更新端側UI的能力。也就是說,假設FaaS函數想要更新頁面,隻需要下發一個

state change

Action

,帶上頁面所需要的所有state資料,就可以達到效果。現實場景中,某些頁面的state資料可能巨大無比,不好直接傳輸。我們做了一個

JsonPatch

庫來解決這種場景下的問題,如果FaaS隻修改了state中的一部分資料,則可以通過下發patch的方式由端來合成一個新的、完整的state。

對于副作用處理的部分,大部分的副作用都來自于比如Dialog,頁面跳轉等。這類操作是通用的,有共性的,我們同樣使用一類叫做

native api

Action

來描述,這些

Action

與它們背後的處理函數,将面向所有使用了Nexus協定的頁面提供這樣的能力。

這兩類函數的抽象,已經可以cover 80%的頁面需求了,而剩下的20%複雜互動的頁面,我們提供custom類型的

Action

來讓開發者進行自定義。

總結

通過對于一體化的定義,以及拆分了需要的功能之後,Nexus協定就破土而出了。它是一個

基于Action的,提供Client/FaaS系統間調用的協定

Action的排程者-LogicEngine

我們有了可以用于兩個系統間進行互相調用的協定,相當于我們有了一門語言,這門語言隻有我們自己認識,是以還需要一個解釋器來執行它。

LogiceEngine就是這樣的一個執行器。

如果我們給它下一個定義,LogicEngine就是一個:

基于Nexus的Action協定的,提供Client、FaaS之間互相調用能力的庫

Engine本身不提供任何具體的邏輯能力,所有的邏輯能力都需要通過函數的形式注冊到Engine中,并綁定到一個具體類型的

Action

上去。

是以Engine的設計相對明确:

  1. 對外,它提供函數注冊和基于消息(action)執行函數調用功能
  2. 對内,進行消息解析、函數比對和執行上下文管理

開發者通過

post

函數來發出一個

Action

,相當于通過Engine調用了一個函數,這個函數可能在本地,也可能在FaaS上。這并不是開發者需要關心的内容。甚至于,這個調用會産生什麼影響,也不是目前調用者所需要關心的。

因為調用的發起者實際隻是發出了自己的一個意圖,比如在實踐中,我們會在使用者按下"下單"按鈕的時候送出一個意圖(Action)。

這個意圖最終會産生什麼樣的UI變化,FaaS會通過一個

state change

或者

native api

形式的

Action

直接調用到具體的實作函數去。

而端側注冊在Engine的函數不會很多,前面有提到過,大部分UI程式設計中的邏輯,都可以被歸納為三類。是以我們大多數時候隻需要注冊三種固定類型的處理函數就可以了。

展望

有了Nexus協定、三類通用處理函數的抽象和LogicEngine,

意圖

作用

中間過程就可以變得透明。

但這些遠遠不夠,

後續我們還希望對協定進行更新,從現有的json提升到一個更加類型安全的協定上。

我們也希望有一個IDL工具,可以自動得将面向

Action

調用轉換成面向接口調用,讓開發者有更好的調用體驗。

我們還希望改變現有單向的

請求

應答

模型,讓FaaS可以自由得調用端側函數,再次突破兩個系統之間的gap,變得更加一體化。

繼續閱讀