天天看點

基于dart生态的FaaS前端一體化建設

作者:閑魚技術-羲凡

背景

随着Flutter對現有業務的不斷參透,閑魚Serverless基建的重心也傾向了dart生态,先是将dart容器打包到伺服器上,實作dart程式設計語言的統一,在統一的容器之上實作程式設計架構一體化(nexus、story),以及後端領域服務一體化。基于dart生态下,前端的FaaS在研發傳遞其實并不高效,研發階段主要面臨的問題是:

程式設計語言不統一 程式設計語言本身雖然不是最大的障礙,但這也确實給前端開發者增加不少門檻,而且更重要的是語言背後的生态、環境與體系更是一道高高的牆。

工程割裂與背後環境複雜 端側一個工程,FaaS側也有一個獨立的工程,它們背後都有着自己的一套建構、調試、內建/釋出的工具鍊;除此之外FaaS還有自己配套的環境、runtime、架構作為支撐。開發者面對這樣複雜的FaaS研發環境與雙重的研發工作流是無法做到高效傳遞的。

程式設計語言一體化

Typescript作為Javascript的超集,彌補了Javascript的靜态類型檢查,同時擴充了很多OOP的文法特性,使得TS跟dart在文法特性上有非常多相似的地方,未後面的轉換提供了可能與便利。要實作語言層面轉換背後都會有一個小型的編譯器在支撐着,不過幸運的是Typescript官方已經提供文法解析器,通過它我們很容易就拿到一份可靠的AST,是以我們隻需要實作一個dart generator就行了。生成器大緻可以分為四個層面的工作:

  • 基礎文法轉換
  • 原生方法差異轉換
  • 業務架構橋接
  • 依賴庫與頭檔案橋接

基礎文法轉換 

這部分很好了解,就是最基本的文法層面轉換,用個最簡單的例子看下。

基于dart生态的FaaS前端一體化建設

原生方法差異抹平 

兩種語言在内置原生方法上也有很大差別,舉個例子:可以看到下面數組的執行個體方法在兩種語言體系上是不一緻的,除了數組插入還有很多很多原生方法是不一緻的。當然也沒太必要被這個難以想象的數量吓到,大多數情況:90%的場景隻會用到那10%的方法,完成了10%的轉換就能cover到90%的場景。

// ts
list2.push(10)

// dart
list2.add(10)           

要實作系統方法的差異轉化首先要識别出該方法是來自于哪個類,比如說

list2.push(10)

 我不可能隻檢查

push

 ,因為随便一個類/對象都可以實作一個push方法。我們必須識别出

list2.push

 的

push

 屬于

Array.push

,别忘了整個typescript編譯器中占比最大的類型檢查器

ts.TypeChecker

 ,它可以很好的幫我們解決這個問題。大緻思路如下:

基于dart生态的FaaS前端一體化建設

業務架構橋接 

在完成上面兩塊能力轉換後,正常裸寫一段邏輯進行轉換問題是不大的;但業務是不可能裸寫,業務需要架構,需要借助架構進行通訊、與容器打交道。需要借助架構進行業務抽象,更好的組織、管理業務邏輯。我們來看個例子:

DartMtopResult<String> result = await HsfServices.request(moduleName, parameter);           

上面的這段代碼是用于在dart側進行内部服務請求的,從代碼表明我們可以擷取到三部分資訊:

  1. 有一個HsfServices的類
  2. HsfServices有一個同步傳回結果的request方法,接收兩個參數
  3. 最終傳回DartMtopResult的資料結構

我們再翻一下

request

 的實作與

DartMtopResult

 的申明:

// DartMtopResult.dart
class DartMtopResult <T> implements xxxx {
  T data;
  bool success;
  String errMsg;
  String errCode;

  // more code hidden
}

// HsfServices.dart
class HsfServices {
    // more code hidden
  static Future<DartMtopResult<String>> request(String moduletName, String parameter) async {
    // more code hidden
  }
  // more code hidden
}           

就看這麼多足夠了,打個比方如果我希望在typescript側編寫一個能用

HsfServices.request

 發請求的ts代碼且不報錯,那應該怎麼做呢?像下面這樣申明一個:

// HsfServices.d.ts
export declare class HsfServices {
  static request(moduletName: string, parameter: string): Promise<DartMtopResult<string>>;
}
// DartMtopResult.d.ts
export declare class DartMtopResult<T> {
  data: T;
  success: boolean;
  errMsg: string;
  errCode: string;
}

// business.ts
import {HsfServices} from "HsfServices.d.ts"
import {DartMtopResult} from "DartMtopResult.d.ts"
const result: DartMtopResult<string> = await HsfServices.request<DartMtopResult<string>>('recycleGet', parameter);           

非常簡單就能讓業務邏輯正常寫下去并且不報錯。但你肯定會說這樣的代碼也沒法運作起來,是的,但我并不需要上面代碼運作起來,我需要的是将它轉成dart,并能在dart runtime中運作就可以了。大緻的橋接思路如下:

基于dart生态的FaaS前端一體化建設

依賴庫與頭檔案橋接 

這部分工作是從業務架構橋接中衍生出來的,我們還是用一個例子來說明一下問題産生的原因。

// business.ts
import {HsfServices} from "@ali/faas-hsf"
import {DartMtopResult} from "@ali/faas-mtop-result"
const result: DartMtopResult<string> = await HsfServices.request<DartMtopResult<string>>('recycleGet', parameter);           
// business.dart
import 'package:hsf_services/hsf_services.dart';
import 'package:dart_mtop_result/dart_mtop_result.dart';
DartMtopResult<String> result = await HsfServices.request(moduleName, parameter);           

可以看到上面邏輯除了發請求部分要轉成dart,還有業務引用頭檔案需要橋接過去,而頭檔案的引入通常是靠pub依賴包(pubspec.yaml)安裝進來的,就意味着轉換器需要拿到

@ali/faas-hsf

 對應dart側的pub包與引入頭檔案。我們的解決思路大緻是這樣的:在

@ali/faas-hsf

 子產品中放入

faas.yaml

 檔案來指定對應的映射關系。

@ali/faas-hsf
|--lib/
|--faas.yaml
|--package.json

// faas.yaml
faas_pub:
    # 映射的dart側依賴包
    hsf_services: ^1.1.7
  # 映射引入頭檔案
    index: hsf_services.dart           

研發過程中再通過工程腳手架來自動完成這之間的映射關系的提取:頭檔案映射與依賴包映射。頭檔案映射最終會交給轉換器,而依賴包映射會交給背後自動維護着的dart工程(後面會提到背後自動維護的dart工程)。大概的思路如下圖所示:

基于dart生态的FaaS前端一體化建設

研發工程一體化

程式設計語言一體化隻是整個FaaS一體化研發的第一步,也隻有統一了程式設計語言之後,背後的生态(npm)、工具鍊(build)與工程才可能一體化。我們看下現狀:開發者面對的是兩個割裂的工程,兩套不同的環境、生态。這正如文章一開始所說的:程式設計語言本身不是最大的障礙,但語言背後的環境與生态卻是一道高高的牆。在我們統一程式設計語言之後,研發工程一體化就變得可行了。

基于dart生态的FaaS前端一體化建設

正如上圖所示,FaaS工程本身的複雜在于整個工程需要運作在一個本地容器之中,因為容器要為工程提供runtime、相應的工具鍊、架構依賴等能力。是以本地容器本身是必不可少的,我們能做的隻是盡可能讓開發者無感容器的存在;除此之外還要對兩個工程的邏輯做一定的融合,大緻可以抽象成四部分工作:

研發代碼層面融合 

代碼層面融合包括兩部分:業務邏輯融合、業務邏輯所依賴程式設計架構融合。分别展現在

faas_src

 存放業務邏輯的ts版,

package.json

 存放業務邏輯所依賴的程式設計架構(前面我們介紹到業務架構橋接最終就展現在端側的依賴包上)

├── faas_pub.yaml
├── faas_src
│   └── Home
│       └── index.ts
├── package.json
├── src
│   ├── components
│   └── pages
│       └── Home
│           ├── index.css
│           └── index.js
└── README.md           

FaaS側的工程黑盒化

使端側腳手架全權接管FaaS側的工程初始化、熱部署、調試資訊,暴露出來給開發者的隻有一套工具鍊,隻有兩個指令

init

dev

 ,讓開發者0門檻初始化出一套統一而可靠環境的FaaS工程。

基于dart生态的FaaS前端一體化建設

對接編譯器進行研發實時編譯 

這部分主要負責對接轉換器實時将ts編譯成dart,并同步到黑盒中的FaaS工程。在實時編譯過程有兩部分内容:一部分是純ts邏輯編譯成dart,另一部分是依賴包的同步安裝,其中

faas_pub.yaml

 由腳手架通過探測端側package.json中的faas依賴包來進行提取生成的,并不需要人工維護。

基于dart生态的FaaS前端一體化建設

串聯調試階段的編譯流

從儲存每一個改動到在浏覽器上能成功發起一個faas函數請求,這之間大緻經過這些步驟:監控改動、編譯代碼、産物部署的流,由統一的端側腳手架進行串聯起來。

基于dart生态的FaaS前端一體化建設

總結

經過程式設計語言的一體化後,我們不僅為開發者提供一種熟悉的技術棧,也為後面工程一體化提供了可能性;再經過工程一體化後,我們為開發者解決了工程割裂,解決背後複雜的FaaS本地運作環境,帶來與原研發模式基本一緻的研發體驗。

基于dart生态的FaaS前端一體化建設

後續

一體化之路還很多要去建設的,調試、釋出、復原等等;除此之外,FaaS畢竟還是運作在後端,最終通過網絡協定與端側通訊,那在兩份代碼中必然存在兩份資料結構申明,兩套封包解包邏輯;這為後面資料結構的一體化與自動化建設提供了很好的發揮餘地。

繼續閱讀